Если этой информации будет недостаточно или у вас есть опыт в нативной разработке под конкретную платформу, то рекомендую заглянуть в другие части:
Flutter. Часть 1. Для Android-разработчиков
Flutter. Часть 2. Для iOS-разработчиков
Flutter. Часть 3. Для разработчиков React Native
Flutter. Часть 4. Для веб-разработчиков
Flutter. Часть 5. Для Xamarin.Forms-разработчиков
Содержание:
- Проект
- Views
- Как располагать виджеты? Какой эквивалент у XAML-файла?
- Как добавить или удалить виджет через код?
- Как анимировать виджет?
- Как рисовать на экране?
- Как менять прозрачность?
- Как создавать кастомные виджеты?
- Навигация
- Async UI
- Какой эквивалент Device.BeginInvokeOnMainThread()? Как выполнять код асинхронно?
- Как делать запросы к сети?
- Как отображать прогресс долгих операций?
- Структура проекта и ресурсы
- Где хранить ресурсы разного разрешения?
- Где хранить строки? Как их локализовывать?
- Где файл проекта?
- Жизненный цикл приложения
- Layouts
- Обработка жестов
- ListView и адаптеры
- Какой аналог у ListView?
- Как определить, на каком элементе был клик?
- Как динамически обновить ListView?
- Текст
- Как использовать кастомные шрифты?
- Как стилизовать текстовые виджеты?
- Какой аналог у Placeholder?
- Как показать ошибки валидации?
- Плагины Flutter
- Как получить доступ к GPS?
- Как получить доступ к камере?
- Как авторизоваться через Facebook?
- Как использовать Firebase?
- Platform-specific code
- Отладка
- Какие есть инструменты для отладки приложения?
- Как сделать hot reload?
- Как получить доступ к меню разработчика в приложении?
- Локальное хранилище
Проект
Вопрос:
Где точка входа?
Ответ:
Функция main()
.
Отличия:
В Xamarin.Forms это LoadApplication(new App());
.
Пример:
void main() { runApp(new MyApp()); }
Вопрос:
Ответ:
Во Flutter нет понятия Page и Element как таковых. Все компоненты — это виджеты. Во Flutter есть 2 вида виджетов: StatelessWidget и StatefulWidget. Они работают одинаково, отличие только в состоянии при рендеринге.
Отличия:
StatelessWidget имеет неизменное состояние. Подойдёт для отображения текста, логотипа и т.д. Т.е. если элемент на экране не должен изменяться за всё время отображения, значит, он вам подходит. Его можно использовать и как контейнер для виджетов с изменяемым состоянием.
StatefulWidget имеет класс State, в котором хранится информация о текущем состоянии. Если вы хотите изменить элемент на экране при выполнении какого-то действия (пришёл ответ с сервера, пользователь нажал на кнопку и т.д.) — это ваш вариант.
Пример:
StatelessWidget:
class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: 'Flutter Demo Home Page'), ); } }
StatefulWidget:
class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => new _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( // Take the value from the MyHomePage object that was created by // the App.build method, and use it to set the appbar title. title: new Text(widget.title), ), body: new Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( 'You have pushed the button this many times:', ), new Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: new FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: new Icon(Icons.add), ), ); } }
Views
Вопрос:
Как располагать виджеты? Какой эквивалент у XAML
-файла?
Ответ:
Во Flutter вёрстка происходит прямо в коде с помощью дерева виджетов.
Отличие:
В Xamarin.Forms чаще всего вёрстка делается в XAML
-файле. Во Flutter нет его аналога.
Дополнительная информация:
Список виджетов можно посмотреть тут.
Пример:
@override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("Sample App"), ), body: new Center( child: new MaterialButton( onPressed: () {}, child: new Text('Hello'), padding: new EdgeInsets.only(left: 10.0, right: 10.0), ), ), ); }
Вопрос:
Как добавить или удалить виджет через код?
Ответ:
С помощью обновления состояния родительского виджета и последующем перестроении дерева виджетов.
Отличия:
В Xamarin.Forms это можно сделать с помощью свойства Content
у элемента или методов Add()
и Remove()
.
Пример:
class SampleApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return new MaterialApp( title: 'Sample App', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { SampleAppPage({Key key}) : super(key: key); @override _SampleAppPageState createState() => new _SampleAppPageState(); } class _SampleAppPageState extends State<SampleAppPage> { // Default value for toggle bool toggle = true; void _toggle() { setState(() { toggle = !toggle; }); } _getToggleChild() { if (toggle) { return new Text('Toggle One'); } else { return new CupertinoButton( onPressed: () {}, child: new Text('Toggle Two'), ); } } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("Sample App"), ), body: new Center( child: _getToggleChild(), ), floatingActionButton: new FloatingActionButton( onPressed: _toggle, tooltip: 'Update Text', child: new Icon(Icons.update), ), ); } }
Вопрос:
Как анимировать виджет?
Ответ:
С помощью классов Animation и AnimationController.
Отличия:
В Xamarin.Forms используются ViewExtensions и методы, например FadeTo
или TranslateTo
.
Дополнительная информация:
Подробнее про анимацию можно почитать здесь.
Пример:
import 'package:flutter/material.dart'; void main() { runApp(new FadeAppTest()); } class FadeAppTest extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return new MaterialApp( title: 'Fade Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyFadeTest(title: 'Fade Demo'), ); } } class MyFadeTest extends StatefulWidget { MyFadeTest({Key key, this.title}) : super(key: key); final String title; @override _MyFadeTest createState() => new _MyFadeTest(); } class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin { AnimationController controller; CurvedAnimation curve; @override void initState() { controller = new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this); curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: new Center( child: new Container( child: new FadeTransition( opacity: curve, child: new FlutterLogo( size: 100.0, )))), floatingActionButton: new FloatingActionButton( tooltip: 'Fade', child: new Icon(Icons.brush), onPressed: () { controller.forward(); }, ), ); } }
Вопрос:
Как рисовать на экране?
Ответ:
С помощью классов CustomPaint и CustomPainter.
Отличия:
В Xamarin.Forms используется сторонний SkiaSharp. Во Flutter Skia Canvas используется напрямую из коробки.
Пример:
import 'package:flutter/material.dart'; void main() => runApp(new MaterialApp(home: new DemoApp())); class DemoApp extends StatelessWidget { Widget build(BuildContext context) => new Scaffold(body: new Signature()); } class Signature extends StatefulWidget { SignatureState createState() => new SignatureState(); } class SignatureState extends State<Signature> { List<Offset> _points = <Offset>[]; Widget build(BuildContext context) { return new GestureDetector( onPanUpdate: (DragUpdateDetails details) { setState(() { RenderBox referenceBox = context.findRenderObject(); Offset localPosition = referenceBox.globalToLocal(details.globalPosition); _points = new List.from(_points)..add(localPosition); }); }, onPanEnd: (DragEndDetails details) => _points.add(null), child: new CustomPaint(painter: new SignaturePainter(_points), size: Size.infinite), ); } } class SignaturePainter extends CustomPainter { SignaturePainter(this.points); final List<Offset> points; void paint(Canvas canvas, Size size) { var paint = new Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5.0; for (int i = 0; i < points.length - 1; i++) { if (points[i] != null && points[i + 1] != null) canvas.drawLine(points[i], points[i + 1], paint); } } bool shouldRepaint(SignaturePainter other) => other.points != points; }
Вопрос:
Как менять прозрачность?
Ответ:
С помощью виджета Opacity.
Отличия:
В Xamarin.Forms используется Opacity у VisualElement. Во Flutter нужно просто обернуть нужный виджет в Opacity.
Вопрос:
Как создавать кастомные виджеты?
Ответ:
Компоновать их внутри одного (вместо наследования).
Отличия:
В Xamarin.Forms можно наследоваться от интересующего нас VisualElement и дописать свою логику. Во Flutter виджет всегда наследуется от StatelessWidget и StatefulWidget. Т.е. нужно создать новый виджет и использовать в нём набор нужных виджетов в качестве параметров или полей.
Пример:
class CustomButton extends StatelessWidget { final String label; CustomButton(this.label); @override Widget build(BuildContext context) { return new RaisedButton(onPressed: () {}, child: new Text(label)); } }
Навигация
Вопрос:
Как навигировать между экранами?
Ответ:
Для навигации между экранами используются классы Navigator и Route.
Отличия:
В Xamarin.Forms используется NavigationPage.
Во Flutter есть два способа навигации, которые схожи с NavigationPage:
Navigator может сделать push() или pop() указанному вами маршруту.
Пример:
void main() { runApp(CupertinoApp( home: MyAppHome(), // becomes the route named '/' routes: <String, WidgetBuilder> { '/a': (BuildContext context) => MyPage(title: 'page A'), '/b': (BuildContext context) => MyPage(title: 'page B'), '/c': (BuildContext context) => MyPage(title: 'page C'), }, )); } Navigator.of(context).pushNamed('/b');
Вопрос:
Как навигировать в стороннее приложение?
Ответ:
С помощью нативных реализаций через MethodChannel либо плагина url_launcher.
Async UI
Вопрос:
Какой эквивалент Device.BeginInvokeOnMainThread()? Как выполнять код асинхронно?
Ответ:
В Dart реализована однопоточная модель исполнения, которая работает на изоляциях (Isolates). Для асинхронного выполнения используется async/await, с которым вы знакомы из C#.
Пример:
Выполнение запроса и возврат результата для обновления UI:
loadData() async { String dataURL = "https://jsonplaceholder.typicode.com/posts"; http.Response response = await http.get(dataURL); setState(() { widgets = json.decode(response.body); }); }
Когда ответ на запрос получен, нужно вызвать метод setState() для перерисовки дерева виджетов с новыми данными.
Пример загрузки и обновления данных в ListView:
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( primarySwatch: Colors.blue, ), home: SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { SampleAppPage({Key key}) : super(key: key); @override _SampleAppPageState createState() => _SampleAppPageState(); } class _SampleAppPageState extends State<SampleAppPage> { List widgets = []; @override void initState() { super.initState(); loadData(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Sample App"), ), body: ListView.builder( itemCount: widgets.length, itemBuilder: (BuildContext context, int position) { return getRow(position); })); } Widget getRow(int i) { return Padding( padding: EdgeInsets.all(10.0), child: Text("Row ${widgets[i]["title"]}") ); } loadData() async { String dataURL = "https://jsonplaceholder.typicode.com/posts"; http.Response response = await http.get(dataURL); setState(() { widgets = json.decode(response.body); }); } }
Вопрос:
Как делать запросы к сети?
Ответ:
С помощью плагина http.
Пример:
Подключение зависимости через pubspec.yaml
:
dependencies: ... http: ^0.11.3+16
Запрос:
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; [...] loadData() async { String dataURL = "https://jsonplaceholder.typicode.com/posts"; http.Response response = await http.get(dataURL); setState(() { widgets = json.decode(response.body); }); } }
Вопрос:
Как отображать прогресс долгих операций?
Ответ:
С помощью ProgressIndicator.
Отличия:
В Xamarin.Forms это можно сделать напрямую, разместив индикатор прогресса в XAML
.
Пример:
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; void main() { runApp(new SampleApp()); } class SampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Sample App', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { SampleAppPage({Key key}) : super(key: key); @override _SampleAppPageState createState() => new _SampleAppPageState(); } class _SampleAppPageState extends State<SampleAppPage> { List widgets = []; @override void initState() { super.initState(); loadData(); } showLoadingDialog() { return widgets.length == 0; } getBody() { if (showLoadingDialog()) { return getProgressDialog(); } else { return getListView(); } } getProgressDialog() { return new Center(child: new CircularProgressIndicator()); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("Sample App"), ), body: getBody()); } ListView getListView() => new ListView.builder( itemCount: widgets.length, itemBuilder: (BuildContext context, int position) { return getRow(position); }); Widget getRow(int i) { return new Padding(padding: new EdgeInsets.all(10.0), child: new Text("Row ${widgets[i]["title"]}")); } loadData() async { String dataURL = "https://jsonplaceholder.typicode.com/posts"; http.Response response = await http.get(dataURL); setState(() { widgets = json.decode(response.body); }); } }
Структура проекта и ресурсы
Вопрос:
Где хранить ресурсы разного разрешения?
Ответ:
В assets
.
Отличия:
В Xamarin.Forms нет унифицированного хранилища платформенных ресурсов, поэтому приходится хранить их в платформенных папках по отдельности. Во Flutter есть assets
. Папка assets
может располагаться в любом месте проекта, главное, прописать путь к ней в файле pubspec.yaml
.
Дополнительная информация:
Сопоставление размеров графических ресурсов в Android и Flutter.
Android density qualifier | Flutter pixel ratio |
ldpi | 0.75x |
mdpi | 1.0x |
hdpi | 1.5x |
xhdpi | 2.0x |
xxhdpi | 3.0x |
xxxhdpi | 4.0x |
Пример расположения ресурсов:
images/my_icon.png // Base: 1.0x image images/2.0x/my_icon.png // 2.0x image images/3.0x/my_icon.png // 3.0x image
Пример пути в pubspec.yaml
файле:
assets: - images/my_icon.jpeg
Пример использования AssetImage:
return AssetImage("images/a_dot_burr.jpeg");
Пример использования asset
напрямую:
@override Widget build(BuildContext context) { return Image.asset("images/my_image.png"); }
Вопрос:
Где хранить строки? Как их локализовывать?
Ответ:
Во Flutter нет определённого места для хранения строк в данный момент. Их предлагается хранить как статические поля в отдельном классе. Для локализации используются плагины, например flutter_localizations или l10n.
Отличия:
В Xamarin.Forms используется файл resx
.
Пример:
Хранение:
class Strings { static String welcomeMessage = "Welcome To Flutter"; }
Использование:
new Text(Strings.welcomeMessage)
Локализация:
dependencies: # ... flutter_localizations: sdk: flutter intl: "^0.15.6"
import 'package:flutter_localizations/flutter_localizations.dart'; new MaterialApp( localizationsDelegates: [ // Add app-specific localization delegate[s] here. GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: [ const Locale('en', 'US'), // English const Locale('he', 'IL'), // Hebrew // ... other locales the app supports ], // ... )
Вопрос:
Где файл проекта?
Ответ:
Во Flutter нет файла проекта, который бы открывал этот проект в среде разработки. Ближайший похожий файл — pubspec.yaml
— содержит в себе зависимости на плагины и детали проекта.
Жизненный цикл приложения
Вопрос:
Как обрабатывать события жизненного цикла?
Ответ:
С помощью WidgetsBinding и метода didChangeAppLifecycleState().
Дополнительная информация:
Во Flutter используется FlutterActivity в Android-коде и FlutterAppDelegate в iOS, за счёт этого движок Flutter делает обработку изменений состояния максимально незаметной. Но если вам всё же необходимо выполнить какую-либо работу в зависимости от состояния, то жизненный цикл немного отличается:
- inactive — этот метод есть только в iOS, в Android нет аналога;
- paused — аналогичен onPause() в Android;
- resumed — аналогичен onPostResume() в Android;
- suspending — аналогичен onStop в Android, в iOS нет аналога.
Более подробно в AppLifecycleStatus documentation.
Пример:
import 'package:flutter/widgets.dart'; class LifecycleWatcher extends StatefulWidget { @override _LifecycleWatcherState createState() => _LifecycleWatcherState(); } class _LifecycleWatcherState extends State<LifecycleWatcher> with WidgetsBindingObserver { AppLifecycleState _lastLifecycleState; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { setState(() { _lastLifecycleState = state; }); } @override Widget build(BuildContext context) { if (_lastLifecycleState == null) return Text('This widget has not observed any lifecycle changes.', textDirection: TextDirection.ltr); return Text('The most recent lifecycle state this widget observed was: $_lastLifecycleState.', textDirection: TextDirection.ltr); } } void main() { runApp(Center(child: LifecycleWatcher())); }
Layouts
Вопрос:
Какой аналог StackLayout?
Ответ:
Аналогом StackLayout с вертикальной ориентацией является Column, а с горизонтальной — Row.
Пример:
Column:
@override Widget build(BuildContext context) { return new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text('Column One'), new Text('Column Two'), new Text('Column Three'), new Text('Column Four'), ], ); }
Row:
@override Widget build(BuildContext context) { return new Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text('Row One'), new Text('Row Two'), new Text('Row Three'), new Text('Row Four'), ], ); }
Вопрос:
Какой аналог у Grid?
Ответ:
Пример:
GridView.count( // Create a grid with 2 columns. If you change the scrollDirection to // horizontal, this would produce 2 rows. crossAxisCount: 2, // Generate 100 widgets that display their index in the List children: List.generate(100, (index) { return Center( child: Text( 'Item $index', style: Theme.of(context).textTheme.headline, ), ); }), );
Вопрос:
Какой аналог у ScrollView?
Ответ:
Ближайший аналог — SingleChildScrollView. Но для построения скролящегося контента во Flutter чаще всего используется ListView.
Пример:
SingleChildScrollView:
@override Widget build(BuildContext context) { return new SingleChildScrollView( child: new Text('Long Content'), ); }
ListView:
@override Widget build(BuildContext context) { return new ListView( children: <Widget>[ new Text('Row One'), new Text('Row Two'), new Text('Row Three'), new Text('Row Four'), ], ); }
Обработка жестов
Вопрос:
Как обрабатывать клики?
Ответ:
Если виджет поддерживает метод onPressed
, то обработать клик можно с его помощью. В противном случае это можно сделать через GestureDetector.
Пример:
onPressed:
@override Widget build(BuildContext context) { return new RaisedButton( onPressed: () { print("click"); }, child: new Text("Button")); }
GestureDetector:
class SampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( body: new Center( child: new GestureDetector( child: new FlutterLogo( size: 200.0, ), onTap: () { print("tap"); }, ), )); } }
Вопрос:
Как обрабатывать жесты?
Ответ:
Используя GestureDetector. Им можно обрабатывать следующие действия:
Tap
Double tap
Long press
Vertical drag
Horizontal drag
Пример:
AnimationController controller; CurvedAnimation curve; @override void initState() { controller = new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this); curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn); } class SampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( body: new Center( child: new GestureDetector( child: new RotationTransition( turns: curve, child: new FlutterLogo( size: 200.0, )), onDoubleTap: () { if (controller.isCompleted) { controller.reverse(); } else { controller.forward(); } }, ), )); } }
ListView и адаптеры
Вопрос:
Какой аналог у ListView?
Ответ:
Отличия:
В Xamarin.Forms нужно создать ViewCell и (чаще всего) DataTemplateSelector и передать их в ListView. Во Flutter нужно просто передать список виджетов для отображения.
Пример:
import 'package:flutter/material.dart'; void main() { runApp(new SampleApp()); } class SampleApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return new MaterialApp( title: 'Sample App', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { SampleAppPage({Key key}) : super(key: key); @override _SampleAppPageState createState() => new _SampleAppPageState(); } class _SampleAppPageState extends State<SampleAppPage> { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("Sample App"), ), body: new ListView(children: _getListData()), ); } _getListData() { List<Widget> widgets = []; for (int i = 0; i < 100; i++) { widgets.add(new Padding(padding: new EdgeInsets.all(10.0), child: new Text("Row $i"))); } return widgets; } }
Вопрос:
Как определить, на каком элементе был клик?
Ответ:
Во Flutter виджет элемента должен обработать свой клик сам.
Отличия:
В Xamarin.Forms чаще всего используется ItemTapped.
Пример:
import 'package:flutter/material.dart'; void main() { runApp(new SampleApp()); } class SampleApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return new MaterialApp( title: 'Sample App', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { SampleAppPage({Key key}) : super(key: key); @override _SampleAppPageState createState() => new _SampleAppPageState(); } class _SampleAppPageState extends State<SampleAppPage> { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("Sample App"), ), body: new ListView(children: _getListData()), ); } _getListData() { List<Widget> widgets = []; for (int i = 0; i < 100; i++) { widgets.add(new GestureDetector( child: new Padding( padding: new EdgeInsets.all(10.0), child: new Text("Row $i")), onTap: () { print('row tapped'); }, )); } return widgets; } }
Вопрос:
Как динамически обновить ListView?
Ответ:
Обновить список данных и вызвать setState()
.
Отличия:
В Xamarin.Forms для этого используется ItemsSource. Во Flutter после setState()
виджет будет перерисован заново.
Пример:
import 'package:flutter/material.dart'; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( primarySwatch: Colors.blue, ), home: SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { SampleAppPage({Key key}) : super(key: key); @override _SampleAppPageState createState() => _SampleAppPageState(); } class _SampleAppPageState extends State<SampleAppPage> { List widgets = []; @override void initState() { super.initState(); for (int i = 0; i < 100; i++) { widgets.add(getRow(i)); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Sample App"), ), body: ListView(children: widgets), ); } Widget getRow(int i) { return GestureDetector( child: Padding( padding: EdgeInsets.all(10.0), child: Text("Row $i"), ), onTap: () { setState(() { widgets = List.from(widgets); widgets.add(getRow(widgets.length + 1)); print('row $i'); }); }, ); } }
Дополнительная информация:
Для формирования списка рекомендуется использовать ListView.Builder.
Пример:
import 'package:flutter/material.dart'; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( primarySwatch: Colors.blue, ), home: SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { SampleAppPage({Key key}) : super(key: key); @override _SampleAppPageState createState() => _SampleAppPageState(); } class _SampleAppPageState extends State<SampleAppPage> { List widgets = []; @override void initState() { super.initState(); for (int i = 0; i < 100; i++) { widgets.add(getRow(i)); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Sample App"), ), body: ListView.builder( itemCount: widgets.length, itemBuilder: (BuildContext context, int position) { return getRow(position); }, ), ); } Widget getRow(int i) { return GestureDetector( child: Padding( padding: EdgeInsets.all(10.0), child: Text("Row $i"), ), onTap: () { setState(() { widgets.add(getRow(widgets.length + 1)); print('row $i'); }); }, ); } }
Текст
Вопрос:
Как использовать кастомные шрифты?
Ответ:
Файл шрифтов нужно просто положить в папку (название придумайте сами) и указать к ней путь в pubspec.yaml
.
Пример:
fonts: - family: MyCustomFont fonts: - asset: fonts/MyCustomFont.ttf - style: italic
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Sample App"), ), body: Center( child: Text( 'This is a custom font text', style: TextStyle(fontFamily: 'MyCustomFont'), ), ), ); }
Вопрос:
Как стилизовать текстовые виджеты?
Ответ:
С помощью параметров:
- color;
- decoration;
- decorationColor;
- decorationStyle;
- fontFamily;
- fontSize;
- fontStyle;
- fontWeight;
- hashCode;
- height;
- inherit;
- letterSpacing;
- textBaseline;
- wordSpacing.
Вопрос:
Какой аналог у Placeholder?
Ответ:
Свойство hintText у InputDecoration.
Пример:
body: new Center( child: new TextField( decoration: new InputDecoration(hintText: "This is a hint"), ) )
Вопрос:
Как показать ошибки валидации?
Ответ:
Всё так же — с помощью InputDecoration и его состояния.
Пример:
class SampleApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( primarySwatch: Colors.blue, ), home: SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { SampleAppPage({Key key}) : super(key: key); @override _SampleAppPageState createState() => _SampleAppPageState(); } class _SampleAppPageState extends State<SampleAppPage> { String _errorText; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Sample App"), ), body: Center( child: TextField( onSubmitted: (String text) { setState(() { if (!isEmail(text)) { _errorText = 'Error: This is not an email'; } else { _errorText = null; } }); }, decoration: InputDecoration(hintText: "This is a hint", errorText: _getErrorText()), ), ), ); } _getErrorText() { return _errorText; } bool isEmail(String emailString) { String emailRegexp = r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'; RegExp regExp = RegExp(emailRegexp); return regExp.hasMatch(emailString); } }
Плагины Flutter
Вопрос:
Как получить доступ к GPS?
Ответ:
С помощью плагина geolocator.
Вопрос:
Как получить доступ к камере?
Ответ:
С помощью плагина image_picker.
Вопрос:
Как авторизоваться через Facebook?
Ответ:
С помощью плагина flutter_facebook_login.
Вопрос:
Как использовать Firebase?
Ответ:
Firebase поддерживает Flutter first party plugins:
- firebase_admob для Firebase AdMob;
- firebase_analytics для Firebase Analytics;
- firebase_auth для Firebase Auth;
- firebase_database для Firebase RTDB;
- firebase_storage для Firebase Cloud Storage;
- firebase_messaging для Firebase Messaging (FCM);
- flutter_firebase_ui для быстрой интеграции Firebase Auth (Facebook, Google, Twitter and e-mail);
- cloud_firestore для Firebase Cloud Firestore.
Platform-specific code
Вопрос:
Как определить, на какой платформе выполняется код?
Ответ:
С помощью класса поля platform
в Theme или класса Platform.
Пример:
Поле platform:
if (Theme.of(context).platform == TargetPlatform.iOS) { return 'iOS'; } else if (Theme.of(context).platform == TargetPlatform.android) { return 'android'; } else if (Theme.of(context).platform == TargetPlatform.fuchsia) { return 'fuchsia'; } else { return 'not recognised '; }
Класс Platform:
if (Platform.isIOS) { return 'iOS'; } else if (Platform.isAndroid) { return 'android'; } else if (Platform.isFuchsia) { return 'fuchsia'; } else { return 'not recognised '; }
Вопрос:
Как вызвать нативный платформенный код?
Ответ:
Через MethodChannel.
Дополнительная информация:
Подробнее тут.
Отладка
Вопрос:
Какие есть инструменты для отладки приложения?
Ответ:
Вопрос:
Как сделать hot reload
?
Ответ:
Если приложение запускалось из IntelliJ IDE, Android Studio или VSCode, то сочетанием ⌘s/ctrl-s
или нажатием на иконку hot reload
. Если запускалось из терминала, то вводом буквы r
.
Вопрос:
Как получить доступ к меню разработчика в приложении?
Ответ:
Если запуск был из IDE, то с помощью инструментов IDE. Если из консоли, то с помощью ввода h.
Дополнительная информация:
Полный список команд:
Действие | Команда в терминале | Функции и поля |
Иерархия виджетов | w | debugDumpApp() |
Дерево рендеринга | t | debugDumpRenderTree() |
Слои | L | debugDumpLayerTree() |
Accessibility | S (traversal order) or U (inverse hit test order) | debugDumpSemantics() |
Инспектор виджетов | i | WidgetsApp.showWidgetInspectorOverride |
Отображение линий построения | p | debugPaintSizeEnabled |
Симуляция разных ОС | o | defaultTargetPlatform |
Перфоманс | P | WidgetsApp. showPerformanceOverlay |
Скриншот flutter.png | s | |
Закрытие приложения | q |
Локальное хранилище
Вопрос:
Как хранить key-value
-данные в приложении?
Ответ:
С помощью плагина shared_preferences.
Отличия:
В Xamarin.Forms используется Xam.Plugins.Settings
.
Пример:
Подключение зависимости:
dependencies: flutter: sdk: flutter shared_preferences: ^0.4.3
Использование:
SharedPreferences prefs = await SharedPreferences.getInstance(); _counter = prefs.getInt('counter'); prefs.setInt('counter', ++_counter); setState(() { _counter = _counter; });
Вопрос:
Как хранить сложные данные?
Ответ:
С помощью плагинов БД, например sqflite или hive.
Вот, пожалуй, ответы на основные вопросы. На этом серия интерпретаций заканчивается. Надеюсь, они были полезны всем интересующимся этим фреймворком разработчикам. Возможно, даже сподвигли начать писать на Flutter и завербовалм вас в дружное комьюнити Flutter-разработчиков. А я пойду думать над новыми статьями, чтобы развивать сообщество и делать мир приложений лучше. Да не сломает Xamarin ваш Forms!
ссылка на оригинал статьи https://habr.com/ru/company/funcorp/blog/493518/
Добавить комментарий