Введение в виджеты
Виджеты описывают, как должно выглядеть их представление с учетом их текущей конфигурации и состояния. Когда состояние виджета изменяется, виджет перестраивает свое описание, которое платформа сравнивает с предыдущим описанием, чтобы определить минимальные изменения, необходимые в базовом дереве рендеринга для перехода из одного состояния в другое.
Flutter отличается от других фреймворков тем, что его пользовательский интерфейс встроен в код, а не (например) в файл XML или что-то подобное. Виджеты — это основные строительные блоки пользовательского интерфейса Flutter. По мере прохождения этой кодовой лаборатории вы узнаете, что почти все во Flutter — это виджеты. Виджет — это неизменяемый объект, описывающий определенную часть пользовательского интерфейса. Вы также узнаете, что виджеты Flutter являются составными, что означает, что вы можете комбинировать существующие виджеты для создания более сложных виджетов.
Hello World
Минимальное приложение Flutter просто вызывает runApp()
функцию с виджетом:
Функция runApp()
берет данное Widget
и делает его корнем дерева виджетов. В этом примере дерево виджетов состоит из двух виджетов, самого Center
виджета и его дочернего элемента, Text
виджета. Фреймворк заставляет корневой виджет закрывать экран, что означает, что текст «Hello, world» оказывается по центру экрана. В этом случае необходимо указать направление текста; когда MaterialApp
виджет используется, об этом позаботятся, как показано ниже.
При написании приложения вы обычно создаете новые виджеты, которые являются подклассами либо , StatelessWidget
либо StatefulWidget
, в зависимости от того, управляет ли ваш виджет каким-либо состоянием. Основная задача виджета — реализовать build()
функцию, которая описывает виджет с точки зрения других виджетов более низкого уровня. Фреймворк строит эти виджеты по очереди, пока процесс не достигнет нижнего предела в виджетах, представляющих базовый элемент RenderObject
, который вычисляет и описывает геометрию виджета.
Основные виджеты
Flutter поставляется с набором мощных базовых виджетов, из которых обычно используются следующие:
Text
Виджет Text
позволяет вам создать серию стилизованного текста в вашем приложении.
Row
,Column
Эти гибкие виджеты позволяют создавать гибкие макеты как в горизонтальном ( Row
), так и в вертикальном ( Column
) направлениях. Дизайн этих объектов основан на веб-модели компоновки flexbox.
Stack
Вместо линейной ориентации (горизонтальной или вертикальной) Stack
виджет позволяет размещать виджеты друг над другом в порядке рисования. Затем вы можете использовать Positioned
виджет для дочерних элементов, Stack
чтобы расположить их относительно верхнего, правого, нижнего или левого края стека. Стеки основаны на модели макета абсолютного позиционирования в Интернете.
Container
Виджет Container
позволяет создать прямоугольный визуальный элемент. Контейнер можно украсить BoxDecoration
, например фоном, рамкой или тенью. A Container
также может иметь поля, отступы и ограничения, применяемые к его размеру. Кроме того, а Container
можно преобразовать в трехмерное пространство с помощью матрицы.
Ниже приведены несколько простых виджетов, объединяющих эти и другие виджеты:
import 'package:flutter/material.dart';
class MyAppBar extends StatelessWidget {
const MyAppBar({required this.title, Key? key}) : super(key: key);
// Fields in a Widget subclass are always marked "final".
final Widget title;
@override
Widget build(BuildContext context) {
return Container(
height: 56.0, // in logical pixels
padding: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(color: Colors.blue[500]),
// Row is a horizontal, linear layout.
child: Row(
// <Widget> is the type of items in the list.
children: [
const IconButton(
icon: Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null, // null disables the button
),
// Expanded expands its child
// to fill the available space.
Expanded(
child: title,
),
const IconButton(
icon: Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
);
}
}
class MyScaffold extends StatelessWidget {
const MyScaffold({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Material is a conceptual piece
// of paper on which the UI appears.
return Material(
// Column is a vertical, linear layout.
child: Column(
children: [
MyAppBar(
title: Text(
'Example title',
style: Theme.of(context) //
.primaryTextTheme
.headline6,
),
),
const Expanded(
child: Center(
child: Text('Hello, world!'),
),
),
],
),
);
}
}
void main() {
runApp(
const MaterialApp(
title: 'My app', // used by the OS task switcher
home: SafeArea(
child: MyScaffold(),
),
),
);
}

Многие виджеты Material Design должны быть внутри MaterialApp
для правильного отображения, чтобы наследовать данные темы. Поэтому запускайте приложение с расширением MaterialApp
.
Обработка жестов
Большинство приложений включают ту или иную форму взаимодействия пользователя с системой. Первым шагом в создании интерактивного приложения является обнаружение жестов ввода. Посмотрите, как это работает, создав простую кнопку:
import 'package:flutter/material.dart';
class MyButton extends StatelessWidget {
const MyButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
print('MyButton was tapped!');
},
child: Container(
height: 50.0,
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: Colors.lightGreen[500],
),
child: const Center(
child: Text('Engage'),
),
),
);
}
}
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: MyButton(),
),
),
),
);
}
Виджет GestureDetector
не имеет визуального представления, но вместо этого обнаруживает жесты, сделанные пользователем. Когда пользователь нажимает Container
, GestureDetector
вызывается onTap()
обратный вызов, в данном случае вывод сообщения на консоль. Вы можете использовать GestureDetector
для обнаружения различных жестов ввода, включая нажатия, перетаскивания и масштабирования.
Изменение виджетов в ответ на ввод
Чтобы создать более сложный опыт — например, более интересно реагировать на пользовательский ввод — приложения обычно содержат некоторое состояние. Flutter использует StatefulWidgets
, чтобы уловить эту идею. StatefulWidgets
— это специальные виджеты, умеющие генерировать State
объекты, которые затем используются для хранения состояния. Рассмотрим этот базовый пример, используя ElevatedButton
упомянутое ранее:
import 'package:flutter/material.dart';
class Counter extends StatefulWidget {
// This class is the configuration for the state.
// It holds the values (in this case nothing) provided
// by the parent and used by the build method of the
// State. Fields in a Widget subclass are always marked
// "final".
const Counter({Key? key}) : super(key: key);
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
// This call to setState tells the Flutter framework
// that something has changed in this State, which
// causes it to rerun the build method below so that
// the display can reflect the updated values. If you
// change _counter without calling setState(), then
// the build method won't be called again, and so
// nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called,
// for instance, as done by the _increment method above.
// The Flutter framework has been optimized to make
// rerunning build methods fast, so that you can just
// rebuild anything that needs updating rather than
// having to individually changes instances of widgets.
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
const SizedBox(width: 16),
Text('Count: $_counter'),
],
);
}
}
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: Counter(),
),
),
),
);
}

Объединяя все это
Далее следует более полный пример, объединяющий эти концепции: гипотетическое приложение для покупок отображает различные продукты, предлагаемые для продажи, и поддерживает корзину покупок для предполагаемых покупок. Начните с определения класса представления ShoppingListItem
:
import 'package:flutter/material.dart';
class Product {
const Product({required this.name});
final String name;
}
typedef CartChangedCallback = Function(Product product, bool inCart);
class ShoppingListItem extends StatelessWidget {
ShoppingListItem({
required this.product,
required this.inCart,
required this.onCartChanged,
}) : super(key: ObjectKey(product));
final Product product;
final bool inCart;
final CartChangedCallback onCartChanged;
Color _getColor(BuildContext context) {
// The theme depends on the BuildContext because different
// parts of the tree can have different themes.
// The BuildContext indicates where the build is
// taking place and therefore which theme to use.
return inCart //
? Colors.black54
: Theme.of(context).primaryColor;
}
TextStyle? _getTextStyle(BuildContext context) {
if (!inCart) return null;
return const TextStyle(
color: Colors.black54,
decoration: TextDecoration.lineThrough,
);
}
@override
Widget build(BuildContext context) {
return ListTile(
onTap: () {
onCartChanged(product, inCart);
},
leading: CircleAvatar(
backgroundColor: Colors.greenAccent,
child: Text(product.name[0]),
),
title: Text(product.name, style: _getTextStyle(context)),
);
}
}
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Center(
child: ShoppingListItem(
product: const Product(name: 'Chips'),
inCart: true,
onCartChanged: (product, inCart) {},
),
),
),
),
);
}
Виджет ShoppingListItem
следует общему шаблону для виджетов без состояния. Он сохраняет значения, полученные в своем конструкторе, в final
переменных-членах, которые затем использует во время своей build()
функции. Например, inCart
логическое значение позволяет переключаться между двумя визуальными представлениями: одно использует основной цвет из текущей темы, а другое — серый.
Вот пример родительского виджета, в котором хранится изменяемое состояние:
import 'package:flutter/material.dart';
class Product {
const Product({required this.name});
final String name;
}
typedef CartChangedCallback = Function(Product product, bool inCart);
class ShoppingListItem extends StatelessWidget {
ShoppingListItem({
required this.product,
required this.inCart,
required this.onCartChanged,
}) : super(key: ObjectKey(product));
final Product product;
final bool inCart;
final CartChangedCallback onCartChanged;
Color _getColor(BuildContext context) {
// The theme depends on the BuildContext because different
// parts of the tree can have different themes.
// The BuildContext indicates where the build is
// taking place and therefore which theme to use.
return inCart //
? Colors.black54
: Theme.of(context).primaryColor;
}
TextStyle? _getTextStyle(BuildContext context) {
if (!inCart) return null;
return const TextStyle(
color: Colors.black54,
decoration: TextDecoration.lineThrough,
);
}
@override
Widget build(BuildContext context) {
return ListTile(
onTap: () {
onCartChanged(product, inCart);
},
leading: CircleAvatar(
backgroundColor: _getColor(context),
child: Text(product.name[0]),
),
title: Text(
product.name,
style: _getTextStyle(context),
),
);
}
}
class ShoppingList extends StatefulWidget {
const ShoppingList({required this.products, Key? key}) : super(key: key);
final List<Product> products;
// The framework calls createState the first time
// a widget appears at a given location in the tree.
// If the parent rebuilds and uses the same type of
// widget (with the same key), the framework re-uses
// the State object instead of creating a new State object.
@override
_ShoppingListState createState() => _ShoppingListState();
}
class _ShoppingListState extends State<ShoppingList> {
final _shoppingCart = <Product>{};
void _handleCartChanged(Product product, bool inCart) {
setState(() {
// When a user changes what's in the cart, you need
// to change _shoppingCart inside a setState call to
// trigger a rebuild.
// The framework then calls build, below,
// which updates the visual appearance of the app.
if (!inCart) {
_shoppingCart.add(product);
} else {
_shoppingCart.remove(product);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Shopping List'),
),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 8.0),
children: widget.products.map((Product product) {
return ShoppingListItem(
product: product,
inCart: _shoppingCart.contains(product),
onCartChanged: _handleCartChanged,
);
}).toList(),
),
);
}
}
void main() {
runApp(const MaterialApp(
title: 'Shopping App',
home: ShoppingList(
products: [
Product(name: 'Eggs'),
Product(name: 'Flour'),
Product(name: 'Chocolate chips'),
],
),
));
}
Класс ShoppingList
extends StatefulWidget
означает, что этот виджет хранит изменяемое состояние. Когда ShoppingList
виджет впервые вставляется в дерево, платформа вызывает createState()
функцию для создания нового экземпляра _ShoppingListState
для связи с этим местоположением в дереве. (Обратите внимание, что подклассы State
обычно имеют начальные символы подчеркивания, чтобы указать, что они являются частными деталями реализации.) Когда родитель этого виджета перестраивается, родитель создает новый экземпляр ShoppingList
, но инфраструктура повторно использует _ShoppingListState
экземпляр, который уже находится в дереве, а не вызывает createState
снова.
Реагирование на события жизненного цикла виджета
После вызова createState()
фреймворк StatefulWidget
вставляет новый объект состояния в дерево, а затем вызывает initState()
объект состояния. Подкласс State
может переопределить initState
для выполнения работы, которая должна произойти только один раз. Например, переопределите initState
настройку анимации или подписку на службы платформы. Реализации initState
должны начинаться с вызова super.initState
.
Когда объект состояния больше не нужен, платформа вызывает dispose()
объект состояния. Переопределите dispose
функцию, чтобы выполнить очистку. Например, переопределить, dispose
чтобы отменить таймеры или отказаться от подписки на службы платформы. Реализации dispose
обычно заканчиваются вызовом super.dispose
.
Для получения дополнительной информации см State
.
Last updated