Movatterモバイル変換


[0]ホーム

URL:


Skip to main content

docs.flutter.dev uses cookies from Google to deliver and enhance the quality of its services and to analyze traffic.

Learn more
Flutter for UIKit developers

Flutter 3.38 and Dart 3.10 are here!Learn more

Flutter for UIKit developers

Learn how to apply iOS and UIKit developer knowledge when building Flutter apps.

iOS developers with experience using UIKit who want to write mobile apps using Flutter should review this guide. It explains how to apply existing UIKit knowledge to Flutter.

Note

Flutter is a framework for building cross-platform applications that uses the Dart programming language. To understand some differences between programming with Dart and programming with Swift, check outLearning Dart as a Swift Developer andFlutter concurrency for Swift developers.

Your iOS and UIKit knowledge and experience are highly valuable when building with Flutter. Flutter also makes a number of adaptations to app behavior when running on iOS. To learn how, seePlatform adaptations.

Tip

Use this guide as a cookbook. Jump around and find questions that address your most relevant needs.

Overview

#

As an introduction, watch the following video. It outlines how Flutter works on iOS and how to use Flutter to build iOS apps.

Watch on YouTube in a new tab: "Flutter for iOS developers"

Views vs. Widgets

#

In UIKit, most of what you create in the UI is done using view objects, which are instances of theUIView class. These can act as containers for otherUIView classes, which form your layout.

In Flutter, the rough equivalent to aUIView is aWidget. Widgets don't map exactly to iOS views, but while you're getting acquainted with how Flutter works you can think of them as "the way you declare and construct UI".

However, these have a few differences to aUIView. To start, widgets have a different lifespan: they are immutable and only exist until they need to be changed. Whenever widgets or their state change, Flutter's framework creates a new tree of widget instances. In comparison, a UIKit view is not recreated when it changes, but rather it's a mutable entity that is drawn once and doesn't redraw until it is invalidated usingsetNeedsDisplay().

Furthermore, unlikeUIView, Flutter's widgets are lightweight, in part due to their immutability. Because they aren't views themselves, and aren't directly drawing anything, but rather are a description of the UI and its semantics that get "inflated" into actual view objects under the hood.

Flutter includes theMaterial Components library. These are widgets that implement theMaterial Design guidelines. Material Design is a flexible design systemoptimized for all platforms, including iOS.

But Flutter is flexible and expressive enough to implement any design language. On iOS, you can use theCupertino widgets library to produce an interface that looks likeApple's iOS design language.

Updating widgets

#

To update your views in UIKit, you directly mutate them. In Flutter, widgets are immutable and not updated directly. Instead, you have to manipulate the widget's state.

This is where the concept of Stateful vs Stateless widgets comes in. AStatelessWidget is just what it sounds like—a widget with no state attached.

StatelessWidgets are useful when the part of the user interface you are describing does not depend on anything other than the initial configuration information in the widget.

For example, with UIKit, this is similar to placing aUIImageView with your logo as theimage. If the logo is not changing during runtime, use aStatelessWidget in Flutter.

If you want to dynamically change the UI based on data received after making an HTTP call, use aStatefulWidget. After the HTTP call has completed, tell the Flutter framework that the widget'sState is updated, so it can update the UI.

The important difference between stateless and stateful widgets is thatStatefulWidgets have aState object that stores state data and carries it over across tree rebuilds, so it's not lost.

If you are in doubt, remember this rule: if a widget changes outside of thebuild method (because of runtime user interactions, for example), it's stateful. If the widget never changes, once built, it's stateless. However, even if a widget is stateful, the containing parent widget can still be stateless if it isn't itself reacting to those changes (or other inputs).

The following example shows how to use aStatelessWidget. A commonStatelessWidget is theText widget. If you look at the implementation of theText widget, you'll find it subclassesStatelessWidget.

dart
Text('I like Flutter!',style:TextStyle(fontWeight:FontWeight.bold),);

If you look at the code above, you might notice that theText widget carries no explicit state with it. It renders what is passed in its constructors and nothing more.

But, what if you want to make "I Like Flutter" change dynamically, for example when clicking aFloatingActionButton?

To achieve this, wrap theText widget in aStatefulWidget and update it when the user clicks the button.

For example:

dart
classSampleAppextendsStatelessWidget{// This widget is the root of your application.constSampleApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnconstMaterialApp(title:'Sample App',home:SampleAppPage());}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{// Default placeholder textStringtextToShow='I Like Flutter';void_updateText(){setState((){// Update the texttextToShow='Flutter is Awesome!';});}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:Center(child:Text(textToShow)),floatingActionButton:FloatingActionButton(onPressed:_updateText,tooltip:'Update Text',child:constIcon(Icons.update),),);}}

Widget layout

#

In UIKit, you might use a Storyboard file to organize your views and set constraints, or you might set your constraints programmatically in your view controllers. In Flutter, declare your layout in code by composing a widget tree.

The following example shows how to display a simple widget with padding:

dart
@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:Center(child:CupertinoButton(onPressed:(){},padding:constEdgeInsets.only(left:10,right:10),child:constText('Hello'),),),);}

You can add padding to any widget, which mimics the functionality of constraints in iOS.

You can view the layouts that Flutter has to offer in thewidget catalog.

Removing Widgets

#

In UIKit, you calladdSubview() on the parent, orremoveFromSuperview() on a child view to dynamically add or remove child views. In Flutter, because widgets are immutable, there is no direct equivalent toaddSubview(). Instead, you can pass a function to the parent that returns a widget, and control that child's creation with a boolean flag.

The following example shows how to toggle between two widgets when the user clicks theFloatingActionButton:

dart
classSampleAppextendsStatelessWidget{// This widget is the root of your application.constSampleApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnconstMaterialApp(title:'Sample App',home:SampleAppPage());}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{// Default value for toggle.booltoggle=true;void_toggle(){setState((){toggle=!toggle;});}Widget_getToggleChild(){if(toggle){returnconstText('Toggle One');}returnCupertinoButton(onPressed:(){},child:constText('Toggle Two'));}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:Center(child:_getToggleChild()),floatingActionButton:FloatingActionButton(onPressed:_toggle,tooltip:'Update Text',child:constIcon(Icons.update),),);}}

Animations

#

In UIKit, you create an animation by calling theanimate(withDuration:animations:) method on a view. In Flutter, use the animation library to wrap widgets inside an animated widget.

In Flutter, use anAnimationController, which is anAnimation<double> that can pause, seek, stop, and reverse the animation. It requires aTicker that signals when vsync happens and produces a linear interpolation between 0 and 1 on each frame while it's running. You then create one or moreAnimations and attach them to the controller.

For example, you might useCurvedAnimation to implement an animation along an interpolated curve. In this sense, the controller is the "master" source of the animation progress and theCurvedAnimation computes the curve that replaces the controller's default linear motion. Like widgets, animations in Flutter work with composition.

When building the widget tree you assign theAnimation to an animated property of a widget, such as the opacity of aFadeTransition, and tell the controller to start the animation.

The following example shows how to write aFadeTransition that fades the widget into a logo when you press theFloatingActionButton:

dart
import'package:flutter/material.dart';classSampleAppextendsStatelessWidget{// This widget is the root of your application.constSampleApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnconstMaterialApp(title:'Fade Demo',home:MyFadeTest(title:'Fade Demo'),);}}classMyFadeTestextendsStatefulWidget{constMyFadeTest({super.key,requiredthis.title});finalStringtitle;@overrideState<MyFadeTest>createState()=>_MyFadeTest();}class_MyFadeTestextendsState<MyFadeTest>withSingleTickerProviderStateMixin{lateAnimationControllercontroller;lateCurvedAnimationcurve;@overridevoidinitState(){super.initState();controller=AnimationController(duration:constDuration(milliseconds:2000),vsync:this,);curve=CurvedAnimation(parent:controller,curve:Curves.easeIn);}@overridevoiddispose(){controller.dispose();super.dispose();}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:Text(widget.title)),body:Center(child:FadeTransition(opacity:curve,child:constFlutterLogo(size:100),),),floatingActionButton:FloatingActionButton(onPressed:(){controller.forward();},tooltip:'Fade',child:constIcon(Icons.brush),),);}}

For more information, seeAnimation & Motion widgets, theAnimations tutorial, and theAnimations overview.

Drawing on the screen

#

In UIKit, you useCoreGraphics to draw lines and shapes to the screen. Flutter has a different API based on theCanvas class, with two other classes that help you draw:CustomPaint andCustomPainter, the latter of which implements your algorithm to draw to the canvas.

To learn how to implement a signature painter in Flutter, see Collin's answer onStackOverflow.

dart
import'package:flutter/material.dart';voidmain()=>runApp(constMaterialApp(home:DemoApp()));classDemoAppextendsStatelessWidget{constDemoApp({super.key});@overrideWidgetbuild(BuildContextcontext)=>constScaffold(body:Signature());}classSignatureextendsStatefulWidget{constSignature({super.key});@overrideState<Signature>createState()=>SignatureState();}classSignatureStateextendsState<Signature>{List<Offset?>_points=<Offset?>[];@overrideWidgetbuild(BuildContextcontext){returnGestureDetector(onPanUpdate:(details){setState((){RenderBox?referenceBox=context.findRenderObject()asRenderBox;OffsetlocalPosition=referenceBox.globalToLocal(details.globalPosition,);_points=List.from(_points)..add(localPosition);});},onPanEnd:(details)=>_points.add(null),child:CustomPaint(painter:SignaturePainter(_points),size:Size.infinite,),);}}classSignaturePainterextendsCustomPainter{SignaturePainter(this.points);finalList<Offset?>points;@overridevoidpaint(Canvascanvas,Sizesize){finalPaintpaint=Paint()..color=Colors.black..strokeCap=StrokeCap.round..strokeWidth=5;for(inti=0;i<points.length-1;i++){if(points[i]!=null&&points[i+1]!=null){canvas.drawLine(points[i]!,points[i+1]!,paint);}}}@overrideboolshouldRepaint(SignaturePainteroldDelegate)=>oldDelegate.points!=points;}

Widget opacity

#

In UIKit, everything has.opacity or.alpha. In Flutter, most of the time you need to wrap a widget in anOpacity widget to accomplish this.

Custom Widgets

#

In UIKit, you typically subclassUIView, or use a pre-existing view, to override and implement methods that achieve the desired behavior. In Flutter, build a custom widget bycomposing smaller widgets (instead of extending them).

For example, how do you build aCustomButton that takes a label in the constructor? Create a CustomButton that composes aElevatedButton with a label, rather than by extendingElevatedButton:

dart
classCustomButtonextendsStatelessWidget{constCustomButton(this.label,{super.key});finalStringlabel;@overrideWidgetbuild(BuildContextcontext){returnElevatedButton(onPressed:(){},child:Text(label));}}

Then useCustomButton, just as you'd use any other Flutter widget:

dart
@overrideWidgetbuild(BuildContextcontext){returnconstCenter(child:CustomButton('Hello'));}

Managing dependencies

#

In iOS, you add dependencies with CocoaPods by adding to yourPodfile. Flutter uses Dart's build system and the Pub package manager to handle dependencies. The tools delegate the building of the native Android and iOS wrapper apps to the respective build systems.

While there is a Podfile in the iOS folder in your Flutter project, only use this if you are adding native dependencies needed for per-platform integration. In general, usepubspec.yaml to declare external dependencies in Flutter. A good place to find great packages for Flutter is onpub.dev.

Navigation

#

This section of the document discusses navigation between pages of an app, the push and pop mechanism, and more.

Navigating between pages

#

In UIKit, to travel between view controllers, you can use aUINavigationController that manages the stack of view controllers to display.

Flutter has a similar implementation, using aNavigator andRoutes. ARoute is an abstraction for a "screen" or "page" of an app, and aNavigator is awidget that manages routes. A route roughly maps to aUIViewController. The navigator works in a similar way to the iOSUINavigationController, in that it canpush() andpop() routes depending on whether you want to navigate to, or back from, a view.

To navigate between pages, you have a couple options:

  • Specify aMap of route names.
  • Directly navigate to a route.

The following example builds aMap.

dart
voidmain(){runApp(CupertinoApp(home:constMyAppHome(),// becomes the route named '/'routes:<String,WidgetBuilder>{'/a':(context)=>constMyPage(title:'page A'),'/b':(context)=>constMyPage(title:'page B'),'/c':(context)=>constMyPage(title:'page C'),},),);}

Navigate to a route bypushing its name to theNavigator.

dart
Navigator.of(context).pushNamed('/b');

TheNavigator class handles routing in Flutter and is used to get a result back from a route that you have pushed on the stack. This is done byawaiting on theFuture returned bypush().

For example, to start alocation route that lets the user select their location, you might do the following:

dart
Object?coordinates=awaitNavigator.of(context).pushNamed('/location');

And then, inside yourlocation route, once the user has selected their location,pop() the stack with the result:

dart
Navigator.of(context).pop({'lat':43.821757,'long':-79.226392});

Navigating to another app

#

In UIKit, to send the user to another application, you use a specific URL scheme. For the system level apps, the scheme depends on the app. To implement this functionality in Flutter, create a native platform integration, or use anexisting plugin, such asurl_launcher.

Manually pop back

#

CallingSystemNavigator.pop() from your Dart code invokes the following iOS code:

objc
UIViewController*viewController=[UIApplicationsharedApplication].keyWindow.rootViewController;if([viewControllerisKindOfClass:[UINavigationControllerclass]]){[((UINavigationController*)viewController)popViewControllerAnimated:NO];}

If that doesn't do what you want, you can create your ownplatform channel to invoke arbitrary iOS code.

Handling localization

#

Unlike iOS, which has theLocalizable.strings file, Flutter doesn't currently have a dedicated system for handling strings. At the moment, the best practice is to declare your copy text in a class as static fields and access them from there. For example:

dart
classStrings{staticconstStringwelcomeMessage='Welcome To Flutter';}

You can access your strings as such:

dart
Text(Strings.welcomeMessage);

By default, Flutter only supports US English for its strings. If you need to add support for other languages, include theflutter_localizations package. You might also need to add Dart'sintl package to use i10n machinery, such as date/time formatting.

yaml
dependencies:flutter_localizations:sdk:flutterintl:any# Use version of intl from flutter_localizations.

To use theflutter_localizations package, specify thelocalizationsDelegates andsupportedLocales on the app widget:

dart
import'package:flutter/material.dart';import'package:flutter_localizations/flutter_localizations.dart';classMyWidgetextendsStatelessWidget{constMyWidget({super.key});@overrideWidgetbuild(BuildContextcontext){returnconstMaterialApp(localizationsDelegates:<LocalizationsDelegate<dynamic>>[// Add app-specific localization delegate[s] hereGlobalMaterialLocalizations.delegate,GlobalWidgetsLocalizations.delegate,],supportedLocales:<Locale>[Locale('en','US'),// EnglishLocale('he','IL'),// Hebrew// ... other locales the app supports],);}}

The delegates contain the actual localized values, while thesupportedLocales defines which locales the app supports. The above example uses aMaterialApp, so it has both aGlobalWidgetsLocalizations for the base widgets localized values, and aMaterialWidgetsLocalizations for the Material widgets localizations. If you useWidgetsApp for your app, you don't need the latter. Note that these two delegates contain "default" values, but you'll need to provide one or more delegates for your own app's localizable copy, if you want those to be localized too.

When initialized, theWidgetsApp (orMaterialApp) creates aLocalizations widget for you, with the delegates you specify. The current locale for the device is always accessible from theLocalizations widget from the current context (in the form of aLocale object), or using theWindow.locale.

To access localized resources, use theLocalizations.of() method to access a specific localizations class that is provided by a given delegate. Use theintl_translation package to extract translatable copy toarb files for translating, and importing them back into the app for using them withintl.

For further details on internationalization and localization in Flutter, see theinternationalization guide, which has sample code with and without theintl package.

ViewControllers

#

This section of the document discusses the equivalent of ViewController in Flutter and how to listen to lifecycle events.

Equivalent of ViewController in Flutter

#

In UIKit, aViewController represents a portion of user interface, most commonly used for a screen or section. These are composed together to build complex user interfaces, and help scale your application's UI. In Flutter, this job falls to Widgets. As mentioned in the Navigation section, screens in Flutter are represented by Widgets since "everything is a widget!" Use aNavigator to move between differentRoutes that represent different screens or pages, or maybe different states or renderings of the same data.

Listening to lifecycle events

#

In UIKit, you can override methods to theViewController to capture lifecycle methods for the view itself, or register lifecycle callbacks in theAppDelegate. In Flutter, you have neither concept, but you can instead listen to lifecycle events by hooking into theWidgetsBinding observer and listening to thedidChangeAppLifecycleState() change event.

The observable lifecycle events are:

inactive

The application is in an inactive state and is not receiving

user input. This event only works on iOS, as there is no equivalent event on Android.

paused

The application is not currently visible to the user,

is not responding to user input, but is running in the background.

resumed

The application is visible and responding to user input.

suspending

The application is suspended momentarily.

The iOS platform has no equivalent event.

For more details on the meaning of these states, seeAppLifecycleState documentation.

Layouts

#

This section discusses different layouts in Flutter and how they compare with UIKit.

Displaying a list view

#

In UIKit, you might show a list in either aUITableView or aUICollectionView. In Flutter, you have a similar implementation using aListView. In UIKit, these views have delegate methods for deciding the number of rows, the cell for each index path, and the size of the cells.

Due to Flutter's immutable widget pattern, you pass a list of widgets to yourListView, and Flutter takes care of making sure that scrolling is fast and smooth.

dart
import'package:flutter/material.dart';voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnconstMaterialApp(title:'Sample App',home:SampleAppPage());}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{List<Widget>_getListData(){finalList<Widget>widgets=[];for(inti=0;i<100;i++){widgets.add(Padding(padding:constEdgeInsets.all(10),child:Text('Row$i')),);}returnwidgets;}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:ListView(children:_getListData()),);}}

Detecting what was clicked

#

In UIKit, you implement the delegate method,tableView:didSelectRowAtIndexPath:. In Flutter, use the touch handling provided by the passed-in widgets.

dart
import'dart:developer'asdeveloper;import'package:flutter/material.dart';voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnconstMaterialApp(title:'Sample App',home:SampleAppPage());}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{List<Widget>_getListData(){List<Widget>widgets=[];for(inti=0;i<100;i++){widgets.add(GestureDetector(onTap:(){developer.log('row tapped');},child:Padding(padding:constEdgeInsets.all(10),child:Text('Row$i'),),),);}returnwidgets;}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:ListView(children:_getListData()),);}}

Dynamically updating ListView

#

In UIKit, you update the data for the list view, and notify the table or collection view using thereloadData method.

In Flutter, if you update the list of widgets inside asetState(), you quickly see that your data doesn't change visually. This is because whensetState() is called, the Flutter rendering engine looks at the widget tree to see if anything has changed. When it gets to yourListView, it performs an== check, and determines that the twoListViews are the same. Nothing has changed, so no update is required.

For a simple way to update yourListView, create a newList inside ofsetState(), and copy the data from the old list to the new list. While this approach is simple, it is not recommended for large data sets, as shown in the next example.

dart
import'dart:developer'asdeveloper;import'package:flutter/material.dart';voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnconstMaterialApp(title:'Sample App',home:SampleAppPage());}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{List<Widget>widgets=<Widget>[];@overridevoidinitState(){super.initState();for(inti=0;i<100;i++){widgets.add(getRow(i));}}WidgetgetRow(inti){returnGestureDetector(onTap:(){setState((){widgets=List.from(widgets);widgets.add(getRow(widgets.length));developer.log('row$i');});},child:Padding(padding:constEdgeInsets.all(10),child:Text('Row$i')),);}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:ListView(children:widgets),);}}

The recommended, efficient, and effective way to build a list uses aListView.Builder. This method is great when you have a dynamic list or a list with very large amounts of data.

dart
import'dart:developer'asdeveloper;import'package:flutter/material.dart';voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnconstMaterialApp(title:'Sample App',home:SampleAppPage());}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{List<Widget>widgets=[];@overridevoidinitState(){super.initState();for(inti=0;i<100;i++){widgets.add(getRow(i));}}WidgetgetRow(inti){returnGestureDetector(onTap:(){setState((){widgets.add(getRow(widgets.length));developer.log('row$i');});},child:Padding(padding:constEdgeInsets.all(10),child:Text('Row$i')),);}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:ListView.builder(itemCount:widgets.length,itemBuilder:(context,position){returngetRow(position);},),);}}

Instead of creating aListView, create aListView.builder that takes two key parameters: the initial length of the list, and anItemBuilder function.

TheItemBuilder function is similar to thecellForItemAt delegate method in an iOS table or collection view, as it takes a position, and returns the cell you want rendered at that position.

Finally, but most importantly, notice that theonTap() function doesn't recreate the list anymore, but instead.adds to it.

Creating a scroll view

#

In UIKit, you wrap your views in aScrollView that allows a user to scroll your content if needed.

In Flutter the easiest way to do this is using theListView widget. This acts as both aScrollView and an iOSTableView, as you can lay out widgets in a vertical format.

dart
@overrideWidgetbuild(BuildContextcontext){returnListView(children:const<Widget>[Text('Row One'),Text('Row Two'),Text('Row Three'),Text('Row Four'),],);}

For more detailed docs on how to lay out widgets in Flutter, see thelayout tutorial.

Gesture detection and touch event handling

#

This section discusses how to detect gestures and handle different events in Flutter, and how they compare with UIKit.

Adding a click listener

#

In UIKit, you attach aGestureRecognizer to a view to handle click events. In Flutter, there are two ways of adding touch listeners:

  1. If the widget supports event detection, pass a function to it, and handle the event in the function. For example, theElevatedButton widget has anonPressed parameter:
dart
@overrideWidgetbuild(BuildContextcontext){returnElevatedButton(onPressed:(){developer.log('click');},child:constText('Button'),);}
  1. If the Widget doesn't support event detection, wrap the widget in a GestureDetector and pass a function to theonTap parameter.
dart
classSampleTapAppextendsStatelessWidget{constSampleTapApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnScaffold(body:Center(child:GestureDetector(onTap:(){developer.log('tap');},child:constFlutterLogo(size:200),),),);}}

Handling other gestures

#

UsingGestureDetector you can listen to a wide range of gestures such as:

  • Tapping

    onTapDown

    A pointer that might cause a tap has contacted the

    screen at a particular location.

    onTapUp

    A pointer that triggers a tap has stopped contacting the

    screen at a particular location.

    onTap

    A tap has occurred.

    onTapCancel

    The pointer that previously triggered theonTapDown

    won't cause a tap.

  • Double tapping

    onDoubleTap

    The user tapped the screen at the same location twice in

    quick succession.

  • Long pressing

    onLongPress

    A pointer has remained in contact with the screen

    at the same location for a long period of time.

  • Vertical dragging

    onVerticalDragStart

    A pointer has contacted the screen and might begin to

    move vertically.

    onVerticalDragUpdate

    A pointer in contact with the screen

    has moved further in the vertical direction.

    onVerticalDragEnd

    A pointer that was previously in contact with the

    screen and moving vertically is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen.

  • Horizontal dragging

    onHorizontalDragStart

    A pointer has contacted the screen and might begin

    to move horizontally.

    onHorizontalDragUpdate

    A pointer in contact with the screen

    has moved further in the horizontal direction.

    onHorizontalDragEnd

    A pointer that was previously in contact with the

    screen and moving horizontally is no longer in contact with the screen.

The following example shows aGestureDetector that rotates the Flutter logo on a double tap:

dart
classSampleAppextendsStatefulWidget{constSampleApp({super.key});@overrideState<SampleApp>createState()=>_SampleAppState();}class_SampleAppStateextendsState<SampleApp>withSingleTickerProviderStateMixin{lateAnimationControllercontroller;lateCurvedAnimationcurve;@overridevoidinitState(){super.initState();controller=AnimationController(vsync:this,duration:constDuration(milliseconds:2000),);curve=CurvedAnimation(parent:controller,curve:Curves.easeIn);}@overrideWidgetbuild(BuildContextcontext){returnScaffold(body:Center(child:GestureDetector(onDoubleTap:(){if(controller.isCompleted){controller.reverse();}else{controller.forward();}},child:RotationTransition(turns:curve,child:constFlutterLogo(size:200),),),),);}}

Themes, styles, and media

#

Flutter applications are easy to style; you can switch between light and dark themes, change the style of your text and UI components, and more. This section covers aspects of styling your Flutter apps and compares how you might do the same in UIKit.

Using a theme

#

Out of the box, Flutter comes with a beautiful implementation of Material Design, which takes care of a lot of styling and theming needs that you would typically do.

To take full advantage of Material Components in your app, declare a top-level widget,MaterialApp, as the entry point to your application.MaterialApp is a convenience widget that wraps a number of widgets that are commonly required for applications implementing Material Design. It builds upon aWidgetsApp by adding Material specific functionality.

But Flutter is flexible and expressive enough to implement any design language. On iOS, you can use theCupertino library to produce an interface that adheres to theHuman Interface Guidelines. For the full set of these widgets, see theCupertino widgets gallery.

You can also use aWidgetsApp as your app widget, which provides some of the same functionality, but is not as rich asMaterialApp.

To customize the colors and styles of any child components, pass aThemeData object to theMaterialApp widget. For example, in the code below, the color scheme from seed is set to deepPurple and divider color is grey.

dart
import'package:flutter/material.dart';classSampleAppextendsStatelessWidget{constSampleApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Sample App',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.deepPurple),dividerColor:Colors.grey,),home:constSampleAppPage(),);}}

Using custom fonts

#

In UIKit, you import anyttf font files into your project and create a reference in theinfo.plist file. In Flutter, place the font file in a folder and reference it in thepubspec.yaml file, similar to how you import images.

yaml
fonts:-family:MyCustomFontfonts:-asset:fonts/MyCustomFont.ttf-style:italic

Then assign the font to yourText widget:

dart
@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:constCenter(child:Text('This is a custom font text',style:TextStyle(fontFamily:'MyCustomFont'),),),);}

Styling text

#

Along with fonts, you can customize other styling elements on aText widget. The style parameter of aText widget takes aTextStyle object, where you can customize many parameters, such as:

  • color
  • decoration
  • decorationColor
  • decorationStyle
  • fontFamily
  • fontSize
  • fontStyle
  • fontWeight
  • hashCode
  • height
  • inherit
  • letterSpacing
  • textBaseline
  • wordSpacing

Bundling images in apps

#

While iOS treats images and assets as distinct items, Flutter apps have only assets. Resources that are placed in theImages.xcasset folder on iOS, are placed in an assets' folder for Flutter. As with iOS, assets are any type of file, not just images. For example, you might have a JSON file located in themy-assets folder:

my-assets/data.json

Declare the asset in thepubspec.yaml file:

yaml
assets:-my-assets/data.json

And then access it from code using anAssetBundle:

dart
import'dart:async'showFuture;import'package:flutter/services.dart'showrootBundle;Future<String>loadAsset()async{returnawaitrootBundle.loadString('my-assets/data.json');}

For images, Flutter follows a simple density-based format like iOS. Image assets might be1.0x,2.0x,3.0x, or any other multiplier. Flutter'sdevicePixelRatio expresses the ratio of physical pixels in a single logical pixel.

Assets are located in any arbitrary folder— Flutter has no predefined folder structure. You declare the assets (with location) in thepubspec.yaml file, and Flutter picks them up.

For example, to add an image calledmy_icon.png to your Flutter project, you might decide to store it in a folder arbitrarily calledimages. Place the base image (1.0x) in theimages folder, and the other variants in sub-folders named after the appropriate ratio multiplier:

images/my_icon.png       // Base: 1.0x imageimages/2.0x/my_icon.png  // 2.0x imageimages/3.0x/my_icon.png  // 3.0x image

Next, declare these images in thepubspec.yaml file:

yaml
assets:-images/my_icon.png

You can now access your images usingAssetImage:

dart
image:AssetImage('images/a_dot_burr.png'),

or directly in anImage widget:

dart
@overrideWidgetbuild(BuildContextcontext){returnImage.asset('images/my_image.png');}

For more details, seeAdding Assets and Images in Flutter.

Form input

#

This section discusses how to use forms in Flutter and how they compare with UIKit.

Retrieving user input

#

Given how Flutter uses immutable widgets with a separate state, you might be wondering how user input fits into the picture. In UIKit, you usually query the widgets for their current values when it's time to submit the user input, or action on it. How does that work in Flutter?

In practice forms are handled, like everything in Flutter, by specialized widgets. If you have aTextField or aTextFormField, you can supply aTextEditingController to retrieve user input:

dart
class_MyFormStateextendsState<MyForm>{// Create a text controller and use it to retrieve the current value.// of the TextField!finalmyController=TextEditingController();@overridevoiddispose(){// Clean up the controller when disposing of the Widget.myController.dispose();super.dispose();}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Retrieve Text Input')),body:Padding(padding:constEdgeInsets.all(16),child:TextField(controller:myController),),floatingActionButton:FloatingActionButton(// When the user presses the button, show an alert dialog with the// text the user has typed into our text field.onPressed:(){showDialog(context:context,builder:(context){returnAlertDialog(// Retrieve the text the user has typed in using our// TextEditingController.content:Text(myController.text),);},);},tooltip:'Show me the value!',child:constIcon(Icons.text_fields),),);}}

You can find more information and the full code listing inRetrieve the value of a text field.

Placeholder in a text field

#

In Flutter, you can easily show a "hint" or a placeholder text for your field by adding anInputDecoration object to the decoration constructor parameter for theText widget:

dart
Center(child:TextField(decoration:InputDecoration(hintText:'This is a hint')),)

Showing validation errors

#

Just as you would with a "hint", pass anInputDecoration object to the decoration constructor for theText widget.

However, you don't want to start off by showing an error. Instead, when the user has entered invalid data, update the state, and pass a newInputDecoration object.

dart
import'package:flutter/material.dart';voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnconstMaterialApp(title:'Sample App',home:SampleAppPage());}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{String?_errorText;boolisEmail(Stringem){StringemailRegexp=r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|'r'(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|'r'(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';RegExpregExp=RegExp(emailRegexp);returnregExp.hasMatch(em);}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:Center(child:TextField(onSubmitted:(text){setState((){if(!isEmail(text)){_errorText='Error: This is not an email';}else{_errorText=null;}});},decoration:InputDecoration(hintText:'This is a hint',errorText:_errorText,),),),);}}

Threading & asynchronicity

#

This section discusses concurrency in Flutter and how it compares with UIKit.

Writing asynchronous code

#

Dart has a single-threaded execution model, with support forIsolates (a way to run Dart code on another thread), an event loop, and asynchronous programming. Unless you spawn anIsolate, your Dart code runs in the main UI thread and is driven by an event loop. Flutter's event loop is equivalent to the iOS main loop—that is, theLooper that is attached to the main thread.

Dart's single-threaded model doesn't mean you are required to run everything as a blocking operation that causes the UI to freeze. Instead, use the asynchronous facilities that the Dart language provides, such asasync/await, to perform asynchronous work.

For example, you can run network code without causing the UI to hang by usingasync/await and letting Dart do the heavy lifting:

dart
Future<void>loadData()async{finalUridataURL=Uri.parse('https://jsonplaceholder.typicode.com/posts');finalhttp.Responseresponse=awaithttp.get(dataURL);setState((){data=(jsonDecode(response.body)asList).cast<Map<String,Object?>>();});}

Once theawaited network call is done, update the UI by callingsetState(), which triggers a rebuild of the widget subtree and updates the data.

The following example loads data asynchronously and displays it in aListView:

dart
import'dart:convert';import'package:flutter/material.dart';import'package:http/http.dart'ashttp;voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnconstMaterialApp(title:'Sample App',home:SampleAppPage());}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{List<Map<String,Object?>>data=[];@overridevoidinitState(){super.initState();loadData();}Future<void>loadData()async{finalUridataURL=Uri.parse('https://jsonplaceholder.typicode.com/posts');finalhttp.Responseresponse=awaithttp.get(dataURL);setState((){data=(jsonDecode(response.body)asList).cast<Map<String,Object?>>();});}WidgetgetRow(intindex){returnPadding(padding:constEdgeInsets.all(10),child:Text('Row${data[index]['title']}'),);}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:ListView.builder(itemCount:data.length,itemBuilder:(context,index){returngetRow(index);},),);}}

Refer to the next section for more information on doing work in the background, and how Flutter differs from iOS.

Moving to the background thread

#

Since Flutter is single threaded and runs an event loop (like Node.js), you don't have to worry about thread management or spawning background threads. If you're doing I/O-bound work, such as disk access or a network call, then you can safely useasync/await and you're done. If, on the other hand, you need to do computationally intensive work that keeps the CPU busy, you want to move it to anIsolate to avoid blocking the event loop.

For I/O-bound work, declare the function as anasync function, andawait on long-running tasks inside the function:

dart
Future<void>loadData()async{finalUridataURL=Uri.parse('https://jsonplaceholder.typicode.com/posts');finalhttp.Responseresponse=awaithttp.get(dataURL);setState((){data=(jsonDecode(response.body)asList).cast<Map<String,Object?>>();});}

This is how you typically do network or database calls, which are both I/O operations.

However, there are times when you might be processing a large amount of data and your UI hangs. In Flutter, useIsolates to take advantage of multiple CPU cores to do long-running or computationally intensive tasks.

Isolates are separate execution threads that do not share any memory with the main execution memory heap. This means you can't access variables from the main thread, or update your UI by callingsetState(). Isolates are true to their name, and cannot share memory (in the form of static fields, for example).

The following example shows, in a simple isolate, how to share data back to the main thread to update the UI.

dart
Future<void>loadData()async{finalReceivePortreceivePort=ReceivePort();awaitIsolate.spawn(dataLoader,receivePort.sendPort);// The 'echo' isolate sends its SendPort as the first message.finalSendPortsendPort=awaitreceivePort.firstasSendPort;finalList<Map<String,dynamic>>msg=awaitsendReceive(sendPort,'https://jsonplaceholder.typicode.com/posts',);setState((){data=msg;});}// The entry point for the isolate.staticFuture<void>dataLoader(SendPortsendPort)async{// Open the ReceivePort for incoming messages.finalReceivePortport=ReceivePort();// Notify any other isolates what port this isolate listens to.sendPort.send(port.sendPort);awaitfor(finaldynamicmsginport){finalStringurl=msg[0]asString;finalSendPortreplyTo=msg[1]asSendPort;finalUridataURL=Uri.parse(url);finalhttp.Responseresponse=awaithttp.get(dataURL);// Lots of JSON to parsereplyTo.send(jsonDecode(response.body)asList<Map<String,dynamic>>);}}Future<List<Map<String,dynamic>>>sendReceive(SendPortport,Stringmsg){finalReceivePortresponse=ReceivePort();port.send(<dynamic>[msg,response.sendPort]);returnresponse.firstasFuture<List<Map<String,dynamic>>>;}

Here,dataLoader() is theIsolate that runs in its own separate execution thread. In the isolate, you can perform more CPU intensive processing (parsing a big JSON, for example), or perform computationally intensive math, such as encryption or signal processing.

You can run the full example below:

dart
import'dart:async';import'dart:convert';import'dart:isolate';import'package:flutter/material.dart';import'package:http/http.dart'ashttp;voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnconstMaterialApp(title:'Sample App',home:SampleAppPage());}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{List<Map<String,Object?>>data=[];@overridevoidinitState(){super.initState();loadData();}boolgetshowLoadingDialog=>data.isEmpty;Future<void>loadData()async{finalReceivePortreceivePort=ReceivePort();awaitIsolate.spawn(dataLoader,receivePort.sendPort);// The 'echo' isolate sends its SendPort as the first message.finalSendPortsendPort=awaitreceivePort.firstasSendPort;finalList<Map<String,dynamic>>msg=awaitsendReceive(sendPort,'https://jsonplaceholder.typicode.com/posts',);setState((){data=msg;});}// The entry point for the isolate.staticFuture<void>dataLoader(SendPortsendPort)async{// Open the ReceivePort for incoming messages.finalReceivePortport=ReceivePort();// Notify any other isolates what port this isolate listens to.sendPort.send(port.sendPort);awaitfor(finaldynamicmsginport){finalStringurl=msg[0]asString;finalSendPortreplyTo=msg[1]asSendPort;finalUridataURL=Uri.parse(url);finalhttp.Responseresponse=awaithttp.get(dataURL);// Lots of JSON to parsereplyTo.send(jsonDecode(response.body)asList<Map<String,dynamic>>);}}Future<List<Map<String,dynamic>>>sendReceive(SendPortport,Stringmsg){finalReceivePortresponse=ReceivePort();port.send(<dynamic>[msg,response.sendPort]);returnresponse.firstasFuture<List<Map<String,dynamic>>>;}WidgetgetBody(){boolshowLoadingDialog=data.isEmpty;if(showLoadingDialog){returngetProgressDialog();}else{returngetListView();}}WidgetgetProgressDialog(){returnconstCenter(child:CircularProgressIndicator());}ListViewgetListView(){returnListView.builder(itemCount:data.length,itemBuilder:(context,position){returngetRow(position);},);}WidgetgetRow(inti){returnPadding(padding:constEdgeInsets.all(10),child:Text("Row${data[i]["title"]}"),);}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:getBody(),);}}

Making network requests

#

Making a network call in Flutter is easy when you use the popularhttp package. This abstracts away a lot of the networking that you might normally implement yourself, making it simple to make network calls.

To add thehttp package as a dependency, runflutter pub add:

flutter pub add http

To make a network call, callawait on theasync functionhttp.get():

dart
Future<void>loadData()async{finalUridataURL=Uri.parse('https://jsonplaceholder.typicode.com/posts');finalhttp.Responseresponse=awaithttp.get(dataURL);setState((){data=(jsonDecode(response.body)asList).cast<Map<String,Object?>>();});}

Showing the progress on long-running tasks

#

In UIKit, you typically use aUIProgressView while executing a long-running task in the background.

In Flutter, use aProgressIndicator widget. Show the progress programmatically by controlling when it's rendered through a boolean flag. Tell Flutter to update its state before your long-running task starts, and hide it after it ends.

In the example below, the build function is separated into three different functions. IfshowLoadingDialog istrue (whenwidgets.length == 0), then render theProgressIndicator. Otherwise, render theListView with the data returned from a network call.

dart
import'dart:convert';import'package:flutter/material.dart';import'package:http/http.dart'ashttp;voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnconstMaterialApp(title:'Sample App',home:SampleAppPage());}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{List<Map<String,Object?>>data=[];@overridevoidinitState(){super.initState();loadData();}boolgetshowLoadingDialog=>data.isEmpty;Future<void>loadData()async{finalUridataURL=Uri.parse('https://jsonplaceholder.typicode.com/posts');finalhttp.Responseresponse=awaithttp.get(dataURL);setState((){data=(jsonDecode(response.body)asList).cast<Map<String,Object?>>();});}WidgetgetBody(){if(showLoadingDialog){returngetProgressDialog();}returngetListView();}WidgetgetProgressDialog(){returnconstCenter(child:CircularProgressIndicator());}ListViewgetListView(){returnListView.builder(itemCount:data.length,itemBuilder:(context,index){returngetRow(index);},);}WidgetgetRow(inti){returnPadding(padding:constEdgeInsets.all(10),child:Text("Row${data[i]["title"]}"),);}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:getBody(),);}}
Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.38.1. Page last updated on 2025-10-28.View source orreport an issue.


[8]ページ先頭

©2009-2025 Movatter.jp