В условиях нынешней "волны" Flutter, эта серия статей направлена на то, чтобы вы чувствовали себя уверенно после прочтения.
Эта серия полностью расскажет вам, как начать работу с Flutter, как быстро создать полноценное приложение Flutter с нуля, а также предоставит высококачественный открытый проект Flutter GSYGithubAppFlutter. Мы будем предлагать советы по разработке Flutter и решения проблем, а затем углубимся в анализ исходного кода и практические примеры, чтобы полностью раскрыть возможности Flutter.
Автор работал над различными проектами на основе Flutter, React Native и Weex. Кросс-платформенная совместимость Flutter, вне всякого сомнения, является лучшей среди всех этих технологий. При первоначальной разработке и отладке, проведённой исключительно на платформе Android, первый запуск на iOS прошёл без единой ошибки, и даже без проблем с совместимостью интерфейса пользователя. Для тех, кто имел опыт работы с кросс-платформенными технологиями, это должно показаться невероятным. Кроме того, функция горячего перезапуска (HotReload) в Flutter работает так плавно, что кажется почти невозможным, и мы можем смело заявить, что она превосходит аналогичные функции других платформ!
Специальные статьи о мире Flutter## I. Основной раздел
Основной раздел включает в себя установку среды разработки, язык Dart и основы Flutter.
Установка среды разработки для Flutter очень проста, особенно для разработчиков Android. Вам просто нужно установить плагины в Android Studio, клонировать Flutter проект с GitHub на локальную машину и выполнить команду flutter doctor
. На самом деле, руководство по установке среды разработки Flutter на сайте FlutterChina уже достаточно подробно объясняет процесс установки для различных платформ.
Особое внимание следует обратить на необходимость настройки прокси для Flutter для пользователей из Китая из-за некоторых ограничивающих факторов. Также стоит отметить, что пользователи из Китая должны использовать https://pub.flutter-io.cn для поиска третьих сторон пакетов Flutter. Ниже приведены адреса, которые необходимо добавить в переменные окружения.
# Для Windows — добавьте эти строки в переменные окружения, для macOS — добавьте их в bash_profile или zsh
export PUB_HOSTED_URL=https://pub.flutter-io.cn # Обязательно для пользователей из Китая
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn # Обязательно для пользователей из Китая
```### 2. Flutter на языке Dart
В мире кросс-платформенного развития, где JavaScript доминирует, появление языка Dart стало настоящей волной свежего воздуха. Как новичок, Dart имеет множество схожих черт с языками Java, Kotlin и JavaScript, что делает его особенно удобным для разработчиков Android и фронтенд-разработчиков.Официальные документы предлагают руководства по переходу от таких технологий, как iOS и React Native, к использованию Flutter, поэтому не стоит беспокоиться — знание Dart не станет препятствием для освоения Flutter, а даже если вы не знакомы с Dart, можно просто анализировать код.
Давайте рассмотрим некоторые ключевые особенности Dart, используемые в контексте Flutter:
### 2.1 Базовые типы данных
- **var**: Можно использовать для объявления переменных, например `var tag = "666"`. Это аналогично JS и Kotlin, и Dart также считается полунамечаемым языком, поддерживающим замыкания.
- **Строго типизированный язык**: Хотя Dart является строго типизированным языком, он позволяет использовать `var`, чтобы компилятор сам определял тип данных. Также есть тип `dynamic`, который представляет собой объект, и проверка типа происходит во время выполнения, а не компиляции.
- **Числовые типы**: В Dart числовые типы делятся на `int` и `double`. Java-тип `long` эквивалентен Dart-типу `int`. В Dart нет типа `float`.
- **Логический тип**: Только значения типа `bool` могут использоваться в условиях (`if`). Например, использование строки `"null"` в условии (`var g = "null"; if(g) { }`) недопустимо в Dart.
- **String в switch**: В Dart оператор `switch` поддерживает значения типа `String`.
### 2.2 Переменные- **Getter и Setter**: В Dart не требуется явно указывать методы `getter` и `setter` для свойств, как это делается в Kotlin. Все базовые типы и классы в Dart наследуются от `Object`, имеют значение по умолчанию `null`, и автоматически предоставляют методы `getter` и `setter`. Однако, если свойство объявлено как `final` или `const`, то доступен только метод `getter`.- **Final и Const**: В Dart используются ключевые слова `final` и `const` для объявления констант. Например, `final name = 'GSY'; const value = 1000000;`. Ключевое слово `static const` используется для объявления статических констант, где значение `const` известно во время компиляции, а значение `final` известно только во время выполнения.
- **Преобразование чисел в строки**: При использовании чисел в качестве строк в Dart, необходимо явно преобразовать число в строку. Например, `int i = 0; print("aaaa" + i);` не будет работать, вместо этого следует использовать `print("aaaa" + i.toString());`. Это отличие от Java и JavaScript, поэтому при работе с динамическими типами важно помнить, что числа не должны использоваться как строки.
- В Dart массивы и списки эквивалентны, поэтому `var list = [];` и `List list = new List()` можно рассматривать как одинаковые.
#### 2.3. Методы
- В Dart операторы `??` и `??=` используются для проверки значений, например: `AA ?? "999"` возвращает `"999"`, если значение AA отсутствует; `AA ??= "999"` присваивает значение `"999"` переменной AA, если она пустая.
- Методы в Dart могут иметь значения по умолчанию и именованные аргументы. Например, метод `getDetail(String userName, reposName, {String branch = "master"}) {}` имеет параметр `branch`, который по умолчанию равен `"master"`. Типы параметров могут быть указаны явно или опущены. Пример вызова: `getRepositoryDetailDao("aaa", "bbbb", branch: "dev")`.- В отличие от Java, в Dart нет ключевых слов, таких как `public` или `private`. Вместо этого `_` используется для указания приватного доступа, но есть также аннотация `@protected`.
- В Dart можно использовать несколько конструкторов для одного класса. По умолчанию может быть только один конструктор, но вы можете создать дополнительные конструкторы, такие как `Model.empty()`, чтобы создать объект с пустыми параметрами:
```dart
class ModelA {
String name;
String tag;
// Конструктор по умолчанию
ModelA(this.name, this.tag);
// Создает объект с пустыми параметрами
ModelA.empty();
// Создает объект со значением name
ModelA.forName(this.name);
}
Flutter поддерживает async
/await
, что позволяет выполнять асинхронные операции. Например, следующий код показывает использование async
/await
:
/// Моделирует ожидание двух секунд и возвращает "ok!"
Future<String> request() async {
await Future.delayed(Duration(seconds: 1));
return "ok!";
}
/// После получения "ok!", меняет его на "ok from request"
Future<String> doSomething() async {
final String data = await request();
return "ok from request";
}
/// Выводит результат
void renderSomething() {
doSomething().then((value) {
print(value); // выводит "ok from request"
});
}
setState
похож на аналогичный метод в React Native. Он используется для управления состоянием компонента через изменения данных между кадрами.- В Flutter все представления создаются с помощью виджетов, которые возвращаются методом build
. Это похоже на то, как в React Native компоненты возвращаются методом render
.Stream
) также поддерживают async*/yield
, что позволяет выполнять асинхронные операции последовательно. Этот механизм будет подробно рассмотрен ниже.### 3. Flutter WidgetВ Flutter все отображение основано на компоненте Widget, который является основой всего приложения. Используется реактивная модель для рендера.Можно изменять данные и использовать метод setState()
, чтобы установить новые данные. Flutter автоматически обновляет Widget на основе связанных данных. Поэтому вам нужно реализовать интерфейс Widget и связать его с данными.
Widgets делятся на состоятельные и безостоятельные. В Flutter каждый экран представляет собой кадр, а безостоятельный widget остаётся в этом кадре. При изменении данных состоятельный widget создаёт новый экземпляр, но состояние сохраняет данные между кадрами.
Вот небольшой совет: когда вы вводите
stl
в поле кода, автоматически появится шаблон создания безостоятельного компонента. А если ввестиstf
, то появится шаблон создания состоятельного Widget.При форматировании кода, запятые внутри и вне скобок влияют на позицию перехода на новую строку.
Если стандартная длина строки слишком короткая, можно изменить её в настройках -> Editor -> Code Style -> Dart -> Wrapping and Braces -> Hard wrap at.
StatelessWidget
, используя метод build
, который возвращает готовый к использованию компонент. Возможно, вы ещё не знакомы с встроенными компонентами Flutter, но не беспокоитесь, мы подробно рассмотрим это позже. Сейчас важно понять, что бесостоятельный Widget очень прост.Widgets могут быть вложены друг в друга через свойство child
. Некоторые Widgets допускают только один child
, как Container
ниже; другие позволяют несколько children
, как Column
layout. В нижеприведенном коде Container
включает в себя Text
.import 'package:flutter/material.dart';
class DEMOWidget extends StatelessWidget {
final String text;
// Данные передаются через конструктор
DEMOWidget(this.text);
@override
Widget build(BuildContext context) {
// Здесь возвращается нужный компонент
// Запятая в конце может влиять на форматирование кода.
return Container(
// Белый фон
color: Colors.white,
// В Dart, ?? указывает, что если text пустое, вернуть значение после него.
child: Text(text ?? "Это безотказный DEMO"),
);
}
}
Продолжим тему безотказных виджетов с простым примером реализации, где основной упор делается на управлении состоянием через класс State
. Через метод build
этого класса создается интерфейс. В состоянии можно динамически менять данные, а вызов setState
приведёт к перестроению и обновлению виджета.
Ниже представлен код, который меняет текстовое содержимое через две секунды после запуска:
Кроме того, данный код демонстрирует жизненный цикл состояния, включающий следующие ключевые точки:* initState: Инициализация, которая происходит один раз при запуске.
StatelessWidget
или StatefulWidget
. Вам следует сконцентрироваться на том, чтобы накапливать вашу логику отображения внутри метода build
, добавлять данные в виджет и использовать setState
для изменения данных, что приведёт к изменению интерфейса.import 'dart:async';
import 'package:flutter/material.dart';
class DemoStatefulWidget extends StatefulWidget {
final String text;
// Передача значений через конструктор
DemoStatefulWidget(this.text);
// Создание состояния
@override
_DemoStatefulWidgetState createState() => _DemoStatefulWidgetState(text);
}
class _DemoStatefulWidgetState extends State<DemoStatefulWidget> {
String text;
_DemoStatefulWidgetState(this.text);
@override
void initState() {
// Инициализация, вызывается один раз
super.initState();
// Задержка на одну секунду
new Future.delayed(Duration(seconds: 1), () {
setState(() {
text = "Значение было изменено";
});
});
}
@override
void dispose() {
// Удаление
super.dispose();
}
@override
void didChangeDependencies() {
// Вызов после инициализации
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
return Container(
child: Text(text ?? "Это состояние DEMO"),
);
}
}
| Тип | Характеристики |
| ------------ | -------------------------------------------------------------------------------------------------|
| Container | Может содержать только один потомственный виджет. По умолчанию заполняет все доступное пространство, включает padding, margin, цвет, размеры и декорацию. |
| Padding | Может содержать только один потомственный виджет. Используется только для установки отступов, часто используется для вложения child с заданием отступов. |
| Center | Может содержать только один потомственный виджет. Используется только для центрирования, часто используется для вложения child с центрированием. |
| Stack | Может содержать несколько потомственных виджетов. Виджеты накладываются друг на друга. |
| Column | Может содержать несколько потомственных виджетов. Вертикальное расположение. |
| Row | Может содержать несколько потомственных виджетов. Горизонтальное расположение. |
| Expanded | Может содержать только один потомственный виджет. Заполняет Column и Row. |
| ListView | Может содержать несколько потомственных виджетов. Само название говорит само за себя. |
```* Container: наиболее часто используемый по умолчанию контроллер, но фактически он представляет собой шаблон, состоящий из нескольких встроенных контроллеров, который может содержать только один `child`, поддерживает конфигурацию отступов, марджина, цвета, размеров и декорации. В Flutter не все контроллеры имеют атрибуты размеров, отступов, марджина, цвета и т.д., поэтому существуют такие виджеты как Padding, Center и т.д.
```dart
new Container(
// Отступы всех сторон равны 10
margin: EdgeInsets.all(10.0),
height: 120.0,
width: 500.0,
// Прозрачная черная маска
decoration: new BoxDecoration(
// Круглость радиуса равна 4.0
borderRadius: BorderRadius.all(Radius.circular(4.0)),
// Установлен цвет декорации, поэтому нельзя установить цвет контейнера
color: Colors.black,
// Обводка
border: new Border.all(color: Color(GSYColors.subTextColor), width: 0.3)),
child: new Text("666666"));
// Направление главной оси, вертикальное для Column, горизонтальное для Row mainAxisAlignment: MainAxisAlignment.start, // По умолчанию максимальный размер или минимальный размер, зависящий от дочерних виджетов mainAxisSize: MainAxisSize.max, // Направление дополнительной оси, горизонтальное для Column, вертикальное для Row crossAxisAlignment: CrossAxisAlignment.center,
* Expanded в Column и Row представляет собой средство заполнения всего пространства, когда два таких компонента присутствуют, они распределяются поровну. Также можно установить свойство flex
, чтобы определить соотношение.new Column(
/// Центрирование по главной оси (вертикальное центрирование)
mainAxisAlignment: MainAxisAlignment.center,
/// Минимальный размер по главной оси
mainAxisSize: MainAxisSize.min,
/// Центрирование по поперечной оси
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
/// По умолчанию flex равен 1
new Expanded(child: new Text("1111"), flex: 2),
new Expanded(child: new Text("2222"))
]
);
Далее мы создадим более сложный виджет. Для этого сначала определим приватный метод _getBottomItem
, который вернет виджет типа Expanded
. Поскольку нам потребуется заполнение этого виджета в ряду (Row).
Как указано в комментариях, основной контейнер предназначен для отображения центральной иконки и текста, между которыми есть отступ в 5.0 единиц:``` /// Возвращает виджет, представляющий собой центральный элемент с иконкой и текстом _getBottomItem(IconData icon, String text) { /// Заполнение всего пространства в ряду (по горизонтали) return new Expanded( flex: 1, /// Центрирование child: new Center( /// Горизонтальное расположение child: new Row( /// Центрирование по главной оси (горизонтальное центрирование) mainAxisAlignment: MainAxisAlignment.center, /// Расширение до максимального размера mainAxisSize: MainAxisSize.max, /// Центрирование по поперечной оси crossAxisAlignment: CrossAxisAlignment.center, children: [ /// Иконка, размер 16.0, серый цвет new Icon( icon, size: 16.0, color: Colors.grey, ), /// Отступ new Padding(padding: new EdgeInsets.only(left: 5.0)), /// Текст new Text( text, // Установка стиля шрифта: серый цвет, размер 14.0 style: new TextStyle(color: Colors.grey, fontSize: 14.0), // Пропуск символов при переполнении overflow: TextOverflow.ellipsis, // Ограничение количества строк до одной maxLines: 1, ), ], ), ), ); }
* Сначала используется контейнер `Container`, содержащий `Card`, чтобы быстро и просто реализовать закругленные углы и тень.
* Затем следует `FlatButton`, который обеспечивает возможность нажатия, а также `Padding`, обеспечивающий отступы.
* Далее с помощью `Column` вертикально объединены два дочерних виджета — один `Container` и один `Row`.
* Внутри `Row` используются виджеты, возвращаемые методом `_getBottomItem`, эффект которых представлен ниже.
```dart
@override
Widget build(BuildContext context) {
return new Container(
/// Картинка в карточке
child: new Card(
/// Добавление эффекта при нажатии
child: new FlatButton(
onPressed: () { print("Нажата!"); },
child: new Padding(
padding: new EdgeInsets.only(left: 0.0, top: 10.0, right: 10.0, bottom: 10.0),
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
/// Описание текста
new Container(
child: new Text(
"Это описание",
style: TextStyle(
color: Color(GSYColors.subTextColor),
fontSize: 14.0,
),
/// Максимально три строки, если больше — "... "
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
margin: new EdgeInsets.only(top: 6.0, bottom: 2.0),
alignment: Alignment.topLeft),
new Padding(padding: EdgeInsets.all(10.0)),
``````markdown
## Три равномерно распределённых горизонтальных значков и текста
```dart
new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_getBottomItem(Icons.star, "1000"),
_getBottomItem(Icons.link, "1000"),
_getBottomItem(Icons.subject, "1000"),
],
),

В Flutter ваш макет часто создается путём последовательной вложенности таких компонентов, конечно же, существуют более продвинутые способы создания макетов, но здесь мы ограничимся базовым подходом.### Flutter страница
В Flutter помимо виджетов для размещения, существуют виджеты для взаимодействия и отображения полной страницы, такие как `MaterialApp`, `Scaffold`, `AppBar`, `Text`, `Image` и `FlatButton`. Ниже представлены краткие описания этих виджетов и пример создания одной страницы.
```| Тип | Основные характеристики |
| ------------- | --------------------------------------------------------- |
| MaterialApp | Обычно используется в качестве главного входа приложения, позволяет конфигурировать темы, многозначность, маршруты и т.д. |
| Scaffold | Виджет для размещения пользовательских страниц, который включает appbar, snackbar, drawer и другие элементы Material Design. |
| AppBar | Обычно используется в appbar Scaffold, содержит заголовок, кнопку возврата на предыдущую страницу и т.д., но это не всё, также используются tabbar и другие. |
| Text | Отображение текста, которое обычно используется, основное назначение — установка стилей шрифта через TextStyle. |
| RichText | Более сложный текст, создается путем объединения нескольких TextSpans для создания сценария с богатым содержанием. |
| TextField | Поле ввода текста: `new TextField(controller: //текстовый контроллер, obscureText: true);`. |
| Image | Загрузка изображения: `new FadeInImage.assetNetwork(placeholder: "предварительное изображение", fit: BoxFit.fitWidth, image: "url");`. |
| FlatButton | Кнопка нажатия: `new FlatButton(onPressed: () {}, child: new Container());`. |Теперь попробуем создать простую полноценную страницу. Ниже приведен код:
* Сначала мы создаем StatefulWidget: `DemoPage`.
* Затем в `_DemoPageState` используем метод `build` для создания `Scaffold`.
* Внутри Scaffold находится `AppBar` и `ListView`.
* AppBar представляет собой область с заголовком, где установлен виджет Text.
* Body является `ListView`, который возвращает 20 ранее созданных виджетов `DemoItem`.
```dart
import 'package:flutter/material.dart';
import 'package:gsy_github_app_flutter/test/DemoItem.dart';
class DemoPage extends StatefulWidget {
@override
_DemoPageState createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
@override
Widget build(BuildContext context) {
/// Начало страницы
/// Если это новая страница, она будет иметь встроенные кнопки возврата
return new Scaffold(
/// Цвет фона
backgroundColor: Colors.blue,
/// Шапка, конечно же, не только шапка
appBar: new AppBar(
/// Этот title — это виджет
title: new Text("Заголовок"),
),
/// Официальное начало страницы
/// ListView с 20 элементами
body: new ListView.builder(
itemBuilder: (context, index) {
return new DemoItem();
},
itemCount: 20,
),
);
}
}
Наконец мы создаем StatelessWidget как входной файл, реализуя MaterialApp, который устанавливает DemoPage в качестве домашней страницы, и запускает его через метод main.
```dart
import 'package:flutter/material.dart';
import 'package:gsy_github_app_flutter/test/DemoPage.dart';
void main() {
runApp(new DemoApp());
}
class DemoApp extends StatelessWidget {
DemoApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return new MaterialApp(home: DemoPage());
}
}
```
Хорошо, первая часть наконец завершена. Здесь основное объяснение состоит из простых базовых вещей, подходящих для новичков, желающих попробовать. В будущих частях вас ждут более сложные практические примеры.
### Рекомендованные ресурсы
* GitHub: [https://github.com/CarGuo/](https://github.com/CarGuo/)
* **Открытый проект Flutter: https://github.com/CarGuo/GSYGithubAppFlutter**
* **Множество примеров учебных проектов Flutter: https://github.com/CarGuo/GSYFlutterDemo**
* **Открытая электронная книга по практикам Flutter: https://github.com/CarGuo/GSYFlutterBook**
#### Рекомендация полного открытого проекта:
* [Связанный с данной статьей: GSYGithubAppFlutter](https://github.com/CarGuo/GSYGithubAppFlutter)
* [GSYGithubAppWeex](https://github.com/CarGuo/GSYGithubAppWeex)
* [GSYGithubAppReactNative](https://github.com/CarGuo/GSYGithubApp)

Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )