How to build Adaptive UI with Flutter - Sous-titres bilingues

All right, welcome, everyone.
Today, we're going to be covering some simple, yet
powerful flutter widgets for building responsive apps that is apps that adapt to the size and shape of a user's device.
We're also going to be talking about some new APIs that help you interact with folding phones.
And we'll also be talking about how your app can adapt to different types of inputs, such as mouse and keyboard.
We'll talk it all off with some quick rules to assist you on your journey to building an amazing adaptive flutter app.
My is Tyler Holland.
And I'm Ree Baker from the Flutter team.
Thank you.
Thank you.
You've 12 years of experience in Flutter.
As you may know, Flutter enables you to build on multiple platforms with one code base.
Today we're going to highlight how you can take an app built for a phone and make it beautiful on a wide range of devices,
including Flutter already has some great existing resources,
so cover some of those and call out the notes in places you can follow up after while we're talking.
To start us off, let's imagine you have a pretty simple phone app.
It has a vertical scrolling list view with a large amount of items.
We'll be using this demo app throughout the presentation as an example.
For this app looks great on the simulated device, but devices with cutouts and notches are pretty common.
When we run this app on those latest devices, the app content gets blocked by the cutouts at the top of the screen.
So how do we fix that?
If you don't know about safe area, no worries.
If do and think it's simple, then get excited to dig deep about how Flutter makes safe area simple and easy to use.
So safe area insets its child widgets to avoid intrusions like notches,
camera cutouts, operating system UI, like status bar on Android or rounded corners of the display.
If you don't want that behavior for all four sides, you can override it, but this is the constructor.
It you to control it.
There.
So how is Safe Area doing all of this?
Let's look under the hood.
This is a simplified version of Safe Area's build method.
It's a pretty small widget.
So is it able to accomplish so much?
The key is this media query dot padding.
Media query is a powerful tool and gives you lots of information including the app's current window size,
exposes accessibility settings like high contrast mode, text scaling, and you're using an accessibility service like talk back.
Plus, it contains features of your device's display, like or folds.
We'll be referring to media query often and exploring more of its abilities.
Safiary uses some of this data to figure out how to inset its child widget.
Specifically, it uses this padding property.
And that padding property is the amount that's covered up.
Cutouts notches corners that I described earlier.
So if needed queries padding is really doing that hard work of identification Why not use it directly?
Because safe area does one clever thing that makes it beneficial to use over raw media query data it modifies its child
media query so that the padding doesn't, so it appears as if the padding doesn't exist.
What this means in practice is you can nest safe areas and only the top most one will apply
padding when needed to avoid notches like in the system you want.
It may not seem like a big deal, but in practice, it's nice to know it's usually safe to use safe area.
As your app grows and you move widgets around or you refactor code,
you have to worry about having to understand exactly the intricacies of the tree, safe area will do the right thing on your behalf.
Whereas if you used media query.padding directly, you would run into stack padding.
Jumping from the code example I showed earlier,
a place to start,
if is a new concept or if you haven't heard of this widget before, is in the body of your scaffold widget.
You have to put it this high in the tree.
It needs to wrap whatever content you want to protect from those insects.
But a really good place to start.
For example,
if you want your app to stretch underneath the cutouts,
you can move safe area to wrap whatever content makes sense and let your app take up the full screen.
This is what the app bar widget does by default, which is why we recommend wrapping the body and not the entire scaffold.
So fantastic.
The looks much better on modern phones.
It won't be cut off.
It scales well to future devices, including ones that haven't been shipped yet.
So let's see what it looks like on Pixel Tabler.
Oh, no, that doesn't look great.
The Android large screen app quality guidelines say we shouldn't have Texas and box and boxes take up the full screen with.
So how do we do this in a adaptive way?
I'll it over to Tyler.
Cool.
Thanks, Reid.
So, luckily, we have a couple of options.
We use the grid view widget to transform our existing list view into more reasonably sized items,
or we can use the max width property on a container to not make the list go edge to edge.
So, let's start off with GridView.
The GridView widget is very similar to ListView,
but instead of only showing a list of widgets arranged linearly, it can arrange the widgets in a two-dimensional array.
GridView also has constructors that are similar to ListViews, which makes porting it easy.
ListViews default alt constructor maps to GridView.ext and ListView.Builder maps to GridView.Builder.
GridView has some additional constructors, too, for more custom layouts.
If interested in finding out more, please check out the GridView page on the API.Flutter.dev.
So, for our example app, we're using a ListView.Builder.
So, we'll use a ListView.
If your app has a large number of items,
it's to use the dot builder function to only build the item widgets that are actually visible.
So what's your place list view with grid view in our code?
So most of the parameters are the same between the two widgets, so it's a straightforward one.
However, we do need to figure out what to set for this grid delegate parameter.
Thankfully, there are some powerful grid delegates that are pre-made that we can use.
Namely, here we go.
Sliver grid delegate with fixed cross-axis count and sliver grid delegate with max cross-axis extent.
So, they're both a mouthful, but all you need to know
is that one ends with cross-axis count,
which you assign a specific number of columns to your grid,
and the max cross-axis extent lets you define a max item width, which is the cross-axis in our case.
So, it's tempting to use that grid delegate that lets us set the column counts directly.
check if the device is a tablet, and hard code the value to something like six if it is.
But we don't want to do that.
We to build an adaptive app that works on all sorts of devices, not just whatever our definition for a tablet device type is.
So let's take a step back and think about why we would want to increase or decrease the number of columns in our grid.
It's really the size of the window that we're currently rendered in, not the size of the physical device.
This is a critical distinction because many Android and iOS devices support some sort of multi-window mode now,
which will cause your app to be rendered in a space smaller than what the physical size of the display is.
Web and desktop apps can also be resized in all sorts of voice too, and Flutter runs there.
So this is why it's critical that Media Query contains the app window size rather than the physical device size.
So instead of setting a hard-coded number of columns,
let's have Flutter handle the adaptive part for us, with the sliver grid delegate with max cross-axis extent.
So, we just need to figure out what we want the maximum width of our items to be.
This can be a hard-coded number, and going to go with 250 for our app.
What you choose for your maximum width will depend on the layout of your items and what you think looks best.
So with those changes, here's what the app looks like now.
It's still a bit crowded on the very large display of a pixel tablet though.
Let's bring in a max with constraint to help with that.
So all we need to do is wrap our grid view in a constrained box and give it a constraint with the maximum width set.
You can also use a container instead of a constrained box if you want some other functionality
like as they both do the same thing under the hood.
For our maximum width, Material 3 recommends a value of 840 for the expanded screen size.
So going to go with that.
You can find out more about Materials Layout Guidance at m3.material.io.
Now this looks much better.
Notice how because of how we set up our grid view, the of columns automatically adapts to the new width of the grid.
That's adaptive UI in action.
So to wrap up what we learned in this section,
grid view and constrained boxes max width can do a lot of the heavy lifting for making your apps scale appropriately for larger screen sizes,
while still maintaining the existing look and feel for smaller devices, like phones.
Speaking of smaller devices, Reid's going to walk us through foldables.
Thank you, Tyler.
So Android and Flutter both say in their design guidance not to lock screen orientation and if you attended the
Talk on Android's adaptive guidance earlier today.
They that they reiterated this a couple of times in their talk But we see that developers in the wild sometimes do So,
here's some typical app code that might lock your screen orientation.
If one side is less than 600 logical pixels,
inside of your app scaffold you would call this handle size change method, and you'd pass in media query.size of, like, looks reasonable, right?
Like, there's not an obvious bug that's showing up here.
At least this code remembered that you should add portrait up and portrait down,
not just portrait, because that way your phone isn't locked in exactly one orientation.
But let's see what this very common code does on a picture.
Oh, that doesn't look right.
This folder display is okay when we're in this portrait folded mode,
but we unfold,
let's break down what's happening there so that we're using media query, like we discussed earlier, to figure out the window size of our app.
When we restrict the orientation.
Under the hood set preferred orientations is causing Android to use portrait compatibility mode.
That portrait compatibility mode puts you in this letterbox state.
Unfortunately, once you are in this state, media query will never get a size that's bigger.
Android is trying to help because we can't.
told it, we only wanted to support portrait orientations.
But we limited that orientation, we made our own lives harder.
Of course, you can avoid all of this by keeping all orientation supported.
But you're already restricting orientations, let me give you another way to avoid this without a lot of code.
This is one of the few situations where you need the physical display dimensions.
and not the window dimensions,
so in Flutter 3.13, which is two releases back, there's a new display API that allows you to get the physical screen dimensions.
This object contains size, pixel ratio, refresh rate, and couple of other interesting parameters.
So, here's some example code for how to get the display object.
One key thing here is that you're finding the display of the view you care about.
This is a forward-looking API that will hopefully handle the ongoing multi-display and multi-view
work that Flutter's been working on for a number of years.
Using that API, we modify it.
This is the exact same code as before.
The difference is we're passing in that we have the display object.
display, and we're going to calculate the small size and logical pixels, and that will help us avoid that letterboxing issue.
Now our app looks like we expect.
If you're like me and a working code is much more helpful than slides on a screen.
of a talk.
The app, written by our friends at JSCener, is example of a beautiful adaptive open source letter app that does this correctly.
So can check that code out on GitHub.
But adaptive design is more than making widgets respond to space.
We also need to be able to change what widgets are used.
Tyler, can you walk us through how to do that?
Sure thing, Reid.
All right, so some common use cases for dynamic widgets include dialogues, navigation UI and building custom layouts.
Let me show you the building blocks for making these while showing lots of code and doing it in a that maintains really building.
So to start off with, how do we even figure out what our apps window size is while in a widgets build method?
We two main options, media query, like covered before, and layout builder.
So let's start with media query.
Media query contains a lot of data.
But this example,
we're only interested in size property, which gives us the size of the app window in logical pixels, also known as density independent pixels.
That's the measurement for pixels that works best for us, as a logical pixel is roughly the same visual size across devices.
So how do we get access to the size property, through media query dot size of context?
Using this method causes the given build context to rebuild any time the size property changes.
It's pretty neat.
Our examples will demonstrate how this rebuilding works as it's a bit indirect.
Those of you familiar with Flutter may have used MediaQuery.of context before, so why are we using size of instead?
Well, as we alluded to earlier, MediaQuery
data, and the dot of method has that same rebuilding functionality on the build context that size of does.
If we use of instead,
our widgets would rebuild when any of the media query properties change, which would result in a lot of unnecessary rebuilds.
Thankfully, Media Query has specialized functions for each of the individual properties, so
can use size of to only rebuild when specifically the size property changes.
Moving on to layout builder, it helps us accomplish a similar goal's Media Query size of, but with some important distinctions.
Rather than always giving us the size of apps window, layout builder gives us the layout constraints from the parent widget.
This means that you will get sizing information based on the specific spot in the widget tree that you add the layout builder to.
Additionally, the layout builder gives you a box constraints instead of a size.
So you're given a valid width and height range, the minimum maximum, for your content, rather than just a fixed size.
This can be useful for some more custom widgets, but for our purposes today, we're just gonna be using the maximum values.
So now that we have the tools for figuring out the size of our rendering area, let's jump into those examples.
First off, dialogs.
So for this example,
let's imagine you have some sort of content that you want to show as a full screen experience on smaller devices and as a modal on larger screens.
Flutter has a dialogue class with two helpful constructors,
one default one that will create a modal dialogue and another called dialogue.fullscreen that will take up the entire screen.
But how do we use two completely different constructors?
We don't want to have to duplicate all of our code for our UI inside the dialogue.
Let's take a look at the constructors and see if there's any way we can share some code.
They look pretty different, but they share a child parameter.
Since Flutter is built for widget composition, we can actually reuse nearly all of our dialog code.
So for this example, we'll turn our image detail view into a dialog that's full screen and a floating window on larger ones.
Let's dive in.
To get it ready for reuse, we'll pull it out into its own stateless widget with a const constructor.
The const constructor is ideal for performance.
Since flutter know, it need to reconstruct the swidget instance It's not required if you have a different use case here, but it's definitely preferred.
So now that we've isolated our content widget, we can figure out how to switch between the two different dialogue constructors.
So we need to decide if we're going to use media query or layout builder here.
We'd like the dialogue to be full screen when our app window is small, regardless of where the button to launch the dialogue is.
Therefore, it makes sense to use me to query size of, so can base the conditional on the app window itself.
So with that figured out, let's implement it.
In order to show the dialog, we'll call the show dialog function.
This will live in our grid view image items on press.
So this requires a build context and a widget builder, so let's add those.
So now we need to fill the build code to return a widget.
We already know what our content looks like, at least.
So let's create an easy reference to that to help with readability.
It's time to make the decision on when exactly we want to show a full screen dialogue versus non full screen.
The material guidelines suggest 600 logical pixels as a breakpoint between the extra small and small window sizes, so let's try that.
We're using media query size of function here to get the, we went over earlier to get the size of the app window.
So now we just need the conditional to switch between the two dialogue constructors based on our show full screen dialogue variable.
We're constraining the size of our non-fullscreen dialog as well, using constrain to box we learned about earlier.
So that should be it.
Let's see how it looks.
Awesome.
The dialog now becomes fullscreen when we hit that 600 width.
And this is happening because the build context we gave to meet query size of is rebuilt whenever the app window size changes,
causing a reevaluation of our conditional statement.
If you ran this code in the browser,
you could resize the window and see if the go from full screen to windowed as the width changes even while the dialog is open.
That seemed like a pretty good methodology for switching between widgets based on app window size.
Let's break down the steps we took and see if we can apply them to our next examples.
So first, we abstracted.
We analyzed the constructors for the widgets we wanted to use and abstracted out what we could share, like dialog content widget.
Next, we figured out the method of measurement.
We determined if we should use media queries size of or layout builder.
depending on if we want the size of the whole app window, or if we want more local sizing.
Last, we added branching logic.
We used material design layout guidelines to determine when we should switch between the widgets.
And think that was it.
So with those steps figured out, let's apply them to the next example, navigation UI.
So let's follow that abstract measure branch methodology we created and see how it goes.
In this example, we want to switch between a navigation bar when the app is small and a navigation rail when the app is big.
What can the abstract and share between these two widgets?
So we know we want a shared list of destinations.
Let's create a destination class to hold this info and to find a destination as having a label and an icon.
Now to create the navigation bar and navigation rail.
Since each of these widgets have a large number of constructor parameters unique to each,
I'm going to define each of them in their own stateless widgets to keep the code clean.
Now, to add in the shared data, we'll create its list of destinations programmatically with
the destinations class we created earlier, set the selected index, and add a callback for when a destination is selected.
That's a good amount of abstraction, so let's move on to the next step.
Should we choose media query size of or layout builder here?
Well, we want the navigation to change based on the size of the whole app window.
So use media query size up here.
So it's implemented.
Here we have our top level scaffold widget which will contain our bottom nav bar and side nav rail.
Let's add our media query size.
But wait, when should this use side nav rail variable be true?
Sounds it's time for step three, branching.
The material guidelines again suggest
that using a bottom nav bar for windows less than 600 logical bits is wide and a nav rail for those 600 or greater.
It sounds like a good place to start.
So, let's add that number to our used side navrail variable and move on to add the
branching logic that we will use in our two widgets created earlier.
Adding branching into the scaffold is a bit trickier than just a conditional because of how much the scaffold can do.
To support the bottom nav bar,
we'll use a ternary operator in the scaffold's bottom navigation bar and this will add the bottom nav bar if we aren't using a side nav rail or set the bottom
nav to null if we are using a side nav rail.
Navigation rail is a little bit different because it actually lives in the body content of the sky.
We'll wrap our existing content in a row and wrap that with an expanded so it takes them all to available space.
We'll then conditionally add in the side nav rail based on our variable.
And now we have a navigation solution that adapts to our apps window size.
We left out a lot of details in that example.
example, but there's a great code lab called building an animated responsive app layout
with Material 3 if you want a more in-depth and step-by-step guide to building an adaptive nav rail and bottom nav combo.
It even shows you how to animate between the two.
For our last example, let's build a custom layout for our image details view.
We'll put our caption description and tags below the image if our container is not
and to the right side of the image if it is wide enough.
So following that process again, let's extract the shared widgets to allow for reuse.
In this case, it's the image, caption, description text, and tags widgets.
Next, should we use media query or layout builder?
For this content widget, we the sizing to be based on whatever space is given to specifically our widget, not the app window in general.
So in this case, we'll use layout builder.
Now to figure out when we want to switch between the layouts.
First, let's do a rough sketch of the layouts and see what makes sense.
We want the wider layout to look like the layout on the left, with the image on the left and the text to the right.
For our less wide layout, we to look like the layout on the right, with image and texts all stacked on top of each other.
It seems like a simple solution here would be to use the wide layout when our given space is wider than it is tall,
and use the tall layout in the other way.
So let's see what that looks like in code.
First, we'll create our branching logic.
The L-builders constraints parameter has the data we're looking for, the max height and the max width our widget is allowed to take up.
Let's add in is more tall than wide variable to store the answer to that question.
Next, we'll branch on that value, and then add in our layouts.
Now our details screen has a nice responsive layout,
and since we used layout builder,
we know we can take this widget and put it anywhere in our app and size it however we want,
and it will still look great.
So, let's review those steps for using a different widget based on app window size.
First, abstract.
Look at the constructors for the widgets.
Abstract out what you want to share, like in our nav example or content widgets for dialogues.
Second, measure.
Determine if you should use media query size of or layout builder.
Use media query to get the size of the whole app window or layout builder for more local sizing.
Last, branch.
Add the logic for when you want to switch between the widgets using logical pixels.
You refer to the material design layout guidelines for measurements.
And it.
So with those steps,
you'll be able to use different widgets at different size break points I'll pass it back over to Reid to talk about another important adaptive concept inputs.
Chances are, if you built an app for a phone, you were primarily thinking about touch input as your only input mechanism.
adding tablet support includes expanding input controls.
So in the tier 3,
which the lowest level of Android's large format device support,
it includes that you need to support mouse and stylus input as one of your required input modes.
Sorry, I want to do Okay.
Sorry.
It massive silos input.
So what's really cool is that flutter,
because you're one shared If you build mouse support for Android tablets and you decide to ship on Flutter desktop or the web,
that code transfers over.
The same is true.
If already shipping a Flutter web app, odds are your Android app already has mouse and keyboard support built in.
And as a developer, it's cool note.
You can use one to help validate the other.
So it can be really easy to be like on your desktop and iterate really quickly.
This is one of the cool perks of Flutter.
So I'll start with If you're using Material App Theme and its buttons and selectors,
then you already have support out of the box for various additional input states, and they work for you for free.
But what if you have a more custom widget?
Let me show you how to add interactivity for hover and dpad selection.
So here, we have this grid of images from our demo app.
The existing code I'm showing you to show you, it's very complicated, it's a container with some formatting and an image.
Here's the kind of abbreviated version of the grid view code for readability.
What code doesn't do is handle dpad or mouse hover states.
So before we get to what widgets you need to trigger UI change,
Let's add this Boolean, I'm to focused, and a setter, which use to trigger the UI for a change.
So this is a little method we're adding.
If the image is focused, we're going to give the corners a little bit of a radius.
And that's all.
This is as simple as it takes.
So, when I was first figuring out how to implement the custom selection in hover states, I
the focus and focus scope widgets, which if you search, this is kind of what comes up inside of Google.
They ended up not being the best widget for me.
They're super powerful, but it wasn't what I wanted.
Instead, I found this class, this widget called focusable action detector, It's frequently used in the material library and it combines the functions of actions, shortcuts,
mouse region, focus, and gives you things like key bindings and callbacks for handling those many state changes.
It bundles it all up and makes your code much easier to handle.
So we're going to add a focusable action.
For simplicity, I'm going to use that set focus method for both hover and highlight state.
So it's highlighted, call set focus.
On hover highlight, we're also going to set focus.
You can have those be different if you want.
So here is a little video of the corner radiuses kind of.
That's okay, but this is a talk about making your app beautiful.
Let's make this a little bit more obvious.
We're to add some transparency.
It's easier to see what's selected and let's animate those transitions because that's really too abrupt.
So our container becomes an animated container.
Pretty and we wrap the whole thing in an opacity widget and set the opacity if focused is true.
So here is a little video of that happening.
As you can tell, it's much prettier.
You can tell what's selected and the corners animate in and out.
Oh, sorry.
So for our final topic, Tyler, what are some best practices you can share?
Yeah, thanks, Reed.
So through my own experience and was talking with my coworkers, a topics came up that are more mechanic or less mechanical and more philosophical.
First off, as mentioned earlier, don't lock the orientation of An adaptive app should look good on windows of all different sizes and shapes.
So while locking an app to portrait mode can help narrow the scope of a minimum viable product,
it can increase the effort required to make your app adaptive in the future.
For example, the assumption that phones will only render your app in a full screen portrait mode is far from a guarantee.
Multi-window app support is becoming common, and foldables have many use cases that work best with multiple apps running side-by-side.
If you absolutely need to lock your app to portrait mode,
remember to use that new Display API covered earlier in the talk, instead of something like Media Query, which might have used before.
We've included a link to the blog post detailing the change as well in the show notes.
Secondly, while building your app, try to break down large complex widgets into simpler smaller ones.
As in our navigation rail dialogue and custom layout examples earlier,
refactoring your widgets can reduce a lot of the complexity that comes with adopting an adaptive UI by sharing core pieces of code.
There are other benefits as well.
On the performance side, having lots of small const widgets can help improve rebuild times over having large complex widgets.
Footer is able to reuse const widget instances, while a larger complicated widget will have to be set up every time there's a rebuild.
Organizing your UI into smaller bite-sized pieces helps keep the complexity of each widget down.
Less complex widgets are more readable, easier to refactor, and less likely to have surprising behavior.
There aren't any specific guidelines on what makes a complex widget,
but what we like to do is document the behavior of a widget in a dark
and judge the complexity of that based on the documentation.
So is it just a few sentences and relatively quick to understand?
It's probably a size as is.
Do I need to write a small story to cover all of the different behaviors at which it can exhibit?
It's probably too big and we should try to break it down a little bit more.
Next, avoid writing code that checks for if the device you're running on is a phone or
a tablet or any other specific type of device to make layout decisions.
What space your app is actually given to render isn't always tied to the full screen size of the device.
Flutter can run on many different platforms,
and your app may be running in a resizable window on Chrome OS, side-by-side with another app.
in a-window mode, or in picture-on-picture-on phones.
Therefore, device type and app window size aren't really strongly connected.
Instead, use media query to get the size of that window your app is currently running in.
We had some examples of that earlier in the talk with Navigation UI and Dialogues.
This isn't just helpful for UI coverage.
either.
Check out last year's talk,
Flutter lessons for federated plug-in development to find out how abstracting out device capabilities can help your business logic code as well.
Finally, avoid using Media Query's Orientation field or Orientation Builder to switch between different app layouts.
This is similar to the guidance of not checking device types to determine screen size.
The device's orientation also doesn't necessarily inform you of how much space your app window has.
Instead, use media query size of or layout builder and use adaptive break points like the ones the material website recommends.
Breaking UI into compact, medium, and expanded layouts is a good place to start.
The compact layout can be used when your app window is less than 600 logic pixels wide,
mediums between 600 and 840, and expanded is used for everything 840 and above.
Again, those are just good places to start and not mandatory.
You can use whatever breakpoints and groupings make sense for your app.
Maybe you're building some sort of gaming app that once you're in on TVs.
It makes sense to add some sort of leanback breakpoint that kicks in at 1,280 logical pixels width for 720p displays,
or some combination of app window size and pixel ratio, which is also available through media queries device pixel ratio of.
So with these best practices, hopefully your journey to making Adaptive Flutter app will be as smooth as possible.
If you'd like to learn more Flutter tips and best practices,
check out the Flutter channel on YouTube or the new documentation on flutter.dev slash adaptive for further guidance.
We just did a really big redesign of these docs with the latest best practice and code samples, so definitely check it out.
So if you've been asleep, it's the very end of the day, let's recap what we've covered.
We discovered how to keep our UI safe from screen cutouts using the safe area widget.
We adapted our portrait-locked phone UI to something that's great on larger screens with GridView and Constrainbox.
We learned a new display API that helps our apps work better with foldable devices.
We worked through examples of how to change our app layout based on window size using media query and layout.
We added support for different input methods and input modalities to share code across devices and learned a simple method for selection animation.
And finally, we finished everything off, everything else off with some quick tips in So thank you, everyone, for joining our talk.
Langue de traduction
Sélectionner

Débloquez plus de fonctionnalités

Installez l'extension Trancy pour débloquer plus de fonctionnalités, y compris les sous-titres IA, les définitions de mots IA, l'analyse grammaticale IA, la parole IA, etc.

feature cover

Compatible avec les principales plateformes vidéo

Trancy offre non seulement le support des sous-titres bilingues pour des plateformes telles que YouTube, Netflix, Udemy, Disney+, TED, edX, Kehan, Coursera, mais propose également la traduction de mots/phrases IA, la traduction immersive de texte intégral et d'autres fonctionnalités pour les pages web régulières. C'est un véritable assistant d'apprentissage des langues tout-en-un.

Tous les navigateurs de plateforme

Trancy prend en charge tous les navigateurs de plateforme, y compris l'extension du navigateur Safari iOS.

Modes de visualisation multiples

Prend en charge les modes théâtre, lecture, mixte et autres modes de visualisation pour une expérience bilingue complète.

Modes de pratique multiples

Prend en charge la dictée de phrases, l'évaluation orale, le choix multiple, la dictée et d'autres modes de pratique.

Résumé vidéo IA

Utilisez OpenAI pour résumer les vidéos et saisir rapidement le contenu clé.

Sous-titres IA

Générez des sous-titres IA précis et rapides pour YouTube en seulement 3 à 5 minutes.

Définitions de mots IA

Appuyez sur les mots dans les sous-titres pour rechercher des définitions, avec des définitions alimentées par l'IA.

Analyse grammaticale IA

Analysez la grammaire des phrases pour comprendre rapidement le sens des phrases et maîtriser les points de grammaire difficiles.

Plus de fonctionnalités web

En plus des sous-titres vidéo bilingues, Trancy propose également la traduction de mots et la traduction intégrale de texte pour les pages web.

Prêt à commencer

Essayez Trancy aujourd'hui et découvrez ses fonctionnalités uniques par vous-même

Télécharger