Оценок пока нет Добавление интерактивности в ваше приложение Flutter

Что вы узнаете

  • Как реагировать на нажатия.
  • Как создать собственный виджет.
  • Разница между виджетами без состояний и состояний.

Как изменить приложение, чтобы оно реагировало на ввод данных пользователем? В этом руководстве вы добавите интерактивность в приложение, которое содержит только неинтерактивные виджеты. В частности, вы измените значок, чтобы сделать его доступным, создав собственный виджет с сохранением состояния, который управляет двумя виджетами без сохранения состояния.

В руководстве по макету показано, как создать макет для следующего снимка экрана.

Приложение для разметки

Когда приложение запускается впервые, звезда светится красным, указывая на то, что это озеро ранее было добавлено в избранное. Число рядом со звездой указывает, что 41 человек одобрил это озеро и добавили себе в избранное. После завершения этого урока нажатие на звезду удаляет ее предпочтительный статус, заменяя сплошную звезду контуром и уменьшая количество. Повторное нажатие на избранное озеро, сплошную звезду и увеличиваете количество.

Для этого вы создадите один пользовательский виджет, который включает в себя как звезду, так и счетчик, которые сами являются виджетами. Нажатие на звездочку изменяет состояние обоих виджетов, поэтому один и тот же виджет должен управлять обоими.

Вы можете получить право прикоснуться к коду на Step 2: Subclass StatefulWidget. Если вы хотите попробовать разные способы управления состоянием, перейдите к разделу Managing state.

Виджеты с сохранением состояния и без сохранения состояния

Виджет может быть либо с состоянием, либо без состояния. Если виджет может измениться — например, когда пользователь взаимодействует с ним, то этот виджет с состоянием.

Виджет без состояния никогда не меняется. Icon, IconButton и Text являются примерами виджетов без состояния. Виджеты без состояния — это подкласс StatelessWidget.

Виджет с состоянием является динамическим: например, он может изменить свой внешний вид в ответ на события, вызванные пользовательским взаимодействием или когда он получает данные. CheckboxRadioSliderInkWellForm, и TextField являются примерами виджетов с состоянием. Stateful виджеты — это подкласс StatefulWidget.

Состояние виджета сохраняется в объекте State, отделяя состояние виджета от его внешнего вида. Состояние состоит из значений, которые могут изменяться, например, текущее значение ползунка или флажок. Когда состояние виджета изменяется, объект состояния вызывает setState(), сообщая платформе о перерисовке виджета.

Создание виджета с состоянием

В чем смысл?

  • Виджет с состоянием реализуется двумя классами: подклассом StatefulWidget и подклассом State.
  • Класс состояния содержит изменяемое состояние виджета и метод build() виджета.
  • Когда состояние виджета изменяется, объект состояния вызывает setState(), сообщая платформе о перерисовке виджета.

В этом разделе вы создадите собственный виджет с отслеживанием состояния. Вы замените два виджета без сохранения состояния — сплошную красную звезду и числовой счетчик рядом с ней — одним пользовательским виджетом с контролем состояния, который управляет строкой с двумя дочерними виджетами: IconButton и Text.

Реализация пользовательского виджета с состоянием требует создания двух классов:

  • Подкласс StatefulWidget, который определяет виджет.
  • Подкласс State, который содержит состояние для этого виджета и определяет метод build() виджета.

В этом разделе показано, как создать виджет с сохранением состояния, называемый FavoriteWidget, для приложения озера. После настройки первым шагом будет выбор способа управления состоянием для FavoriteWidget.


Шаг 0: будьте готовы

Если вы уже создали приложение из Layout tutorial (step 6) , перейдите к следующему разделу.

  1. Убедитесь, что вы настроили свою среду.
  2. Создайте базовое приложение Flutter «Hello World».
  3. Замените файл lib/main.dart на main.dart.
  4. Замените файл pubspec.yaml на pubspec.yaml.
  5. Создайте каталог изображений в вашем проекте и добавьте lake.jpg.

Когда у вас есть подключенное и включенное устройство или вы запустили симулятор iOS (часть установки Flutter), вы готовы к работе!

Шаг 1: Решите, какой объект управляет состоянием виджета

С состоянием виджета можно управлять несколькими способами, но в нашем примере сам виджет, FavoriteWidget, будет управлять своим собственным состоянием. В этом примере переключение звездочки — это изолированное действие, которое не влияет на родительский виджет или остальную часть пользовательского интерфейса, поэтому виджет может обрабатывать свое состояние внутренне.

Шаг 2: подкласс StatefulWidget

Класс FavoriteWidget управляет своим собственным состоянием, поэтому он переопределяет createState() для создания объекта State. Фреймворк вызывает createState(), когда он хочет построить виджет. В этом примере createState() возвращает экземпляр _FavoriteWidgetState, который вы реализуете на следующем шаге.

class FavoriteWidget extends StatefulWidget {
  @override
  _FavoriteWidgetState createState() => _FavoriteWidgetState();
}

[info] Примечание. Члены или классы, начинающиеся с подчеркивания (_), являются частными. Для получения дополнительной информации, см. Libraries and visibility ( Библиотеки и видимость ), раздел в Dart language tour (языковой тур Dart). [/info]

Шаг 3: Состояние подкласса

Класс _FavoriteWidgetState хранит изменяемые данные, которые могут изменяться в течение времени жизни виджета. Когда приложение запускается впервые, пользовательский интерфейс отображает сплошную красную звезду, указывающую, что озеро имеет статус «избранное», а также 41 отметка «избранное». Эти значения хранятся в полях _isFavorited и _favoriteCount:

class _FavoriteWidgetState extends State {
  bool _isFavorited = true;
  int _favoriteCount = 41;
  // ···
}

Класс также определяет метод build(), который создает строку, содержащую красный IconButton и Text. Вы используете IconButton (вместо Icon), потому что он имеет свойство onPressed, которое определяет функцию обратного вызова (_toggleFavorite) для обработки касания. Далее вы определите функцию обратного вызова.

class _FavoriteWidgetState extends State {
  // ···
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Container(
          padding: EdgeInsets.all(0),
          child: IconButton(
            icon: (_isFavorited ? Icon(Icons.star) : Icon(Icons.star_border)),
            color: Colors.red[500],
            onPressed: _toggleFavorite,
          ),
        ),
        SizedBox(
          width: 18,
          child: Container(
            child: Text('$_favoriteCount'),
          ),
        ),
      ],
    );
  }
}

[info] Совет: Помещение Text в SizedBox и установка его ширины предотвращает заметный «скачок», когда текст изменяется между значениями 40 и 41 — иначе произошел бы скачок, потому что эти значения имеют разную ширину. [/info]

Метод _toggleFavorite(), который вызывается при нажатии IconButton, вызывает setState(). Вызов setState() является критическим, потому что это сообщает платформе, что состояние виджета изменилось и что виджет должен быть перерисован. Аргумент функции для setState() переключает интерфейс между этими двумя состояниями:

  • Значок звездочки star и число 41
  • Значок звездочки star_border и число 40
void _toggleFavorite() {
  setState(() {
    if (_isFavorited) {
      _favoriteCount -= 1;
      _isFavorited = false;
    } else {
      _favoriteCount += 1;
      _isFavorited = true;
    }
  });
}

Шаг 4. Подключите виджет с сохранением состояния к дереву виджетов.

Добавьте свой пользовательский виджет с сохранением состояния в дерево виджетов в методе build() приложения. Сначала найдите код, который создает Icon и Text, и удалите его. В том же месте создайте виджет с сохранением состояния.

                                 class MyApp extends StatelessWidget {            
                                   @override            
                                      //@@ -38,11 +33,7 @@    
                                               ],            
                                             ),            
                                           ),            
                    //-                      Icon(            
                    //+                      FavoriteWidget(),            
                    //-                        Icons.star,            
                    //-                        color: Colors.red[500],            
                    //-                      ),            
                    //-                      Text('41'),            
                                         ],            
                                       ),            
                                     );            
                                     //@@ -117,3 +108,3 @@    
                                     );            
                                   }            
                                 }

Это оно! Когда вы перезагрузите приложение, значок звездочки теперь должен реагировать на нажатия.

Проблемы?

Если вы не можете запустить свой код, поищите в вашей IDE возможные ошибки. Отладка Flutter приложения может помочь. Если вы все еще не можете найти проблему, сравните ваш код с примером интерактивных озер на GitHub.

Если у вас все еще есть вопросы, обратитесь к любому из каналов сообщества разработчиков.

Остальная часть этой страницы описывает несколько способов управления состоянием виджета и перечисляет другие доступные интерактивные виджеты.

Управление состоянием

В чем смысл?

  • Существуют разные подходы к управлению государством.
  • Вы, как дизайнер виджетов, выбираете, какой подход использовать.
  • Если сомневаетесь, начните с управления состоянием в родительском виджете.

Кто управляет состоянием виджета с отслеживанием состояния? Сам виджет? Родительский виджет? И то и другое? Еще один объект? Ответ … это зависит. Есть несколько способов сделать ваш виджет интерактивным. Вы, как дизайнер виджетов, принимаете решение в зависимости от того, как вы ожидаете, что ваш виджет будет использоваться. Вот наиболее распространенные способы управления состоянием:

Как вы решаете, какой подход использовать? Следующие принципы должны помочь вам принять решение:

  • Если рассматриваемым состоянием являются пользовательские данные, например, проверенный или непроверенный режим флажка или положение ползунка, то состояние лучше всего управляется родительским виджетом.
  • Если рассматриваемое состояние эстетично, например, анимация, то состояние лучше всего управляется самим виджетом.

Если сомневаетесь, начните с управления состоянием в родительском виджете.

Мы приведем примеры различных способов управления состоянием, создав три простых примера: TapboxA, TapboxB и TapboxC. Все примеры работают одинаково — каждый создает контейнер, который при касании переключается между зеленой или серой рамкой. _Active — булевое значение определяет цвет: зеленый для активного или серый для неактивного.

В этих примерах GestureDetector используется для захвата действий в контейнере.

Виджет управляет своим собственным состоянием

Иногда для виджета наиболее целесообразно внутренне управлять своим состоянием. Например, ListView автоматически прокручивается, когда его содержимое превышает поле визуализации. Большинство разработчиков, использующих ListView, не хотят управлять прокруткой ListView, поэтому сам ListView управляет смещением прокрутки.

Класс _TapboxAState:

  • Управляет состоянием для TapboxA.
  • Определяет _active логическое значение, которое определяет текущий цвет блока.
  • Определяет функцию _handleTap(), которая обновляет _active при касании поля и вызывает функцию setState() для обновления пользовательского интерфейса.
  • Реализует все интерактивное поведение для виджета.
// TapboxA manages its own state.

//------------------------- TapboxA ----------------------------------

class TapboxA extends StatefulWidget {
  TapboxA({Key key}) : super(key: key);

  @override
  _TapboxAState createState() => _TapboxAState();
}

class _TapboxAState extends State {
  bool _active = false;

  void _handleTap() {
    setState(() {
      _active = !_active;
    });
  }

  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        child: Center(
          child: Text(
            _active ? 'Active' : 'Inactive',
            style: TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: _active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}

//------------------------- MyApp ----------------------------------

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Demo'),
        ),
        body: Center(
          child: TapboxA(),
        ),
      ),
    );
  }
}

Родительский виджет управляет состоянием виджета

Часто для родительского виджета наиболее целесообразно управлять состоянием и сообщать дочернему виджету, когда обновлять. Например, IconButton позволяет вам рассматривать значок как нажимаемую кнопку. IconButton — это виджет без состояния, потому что мы решили, что родительский виджет должен знать, была ли нажата кнопка, чтобы он мог предпринять соответствующие действия.

В следующем примере TapboxB экспортирует свое состояние в родительский объект посредством обратного вызова. Поскольку TapboxB не управляет каким-либо состоянием, он подклассов StatelessWidget.

Класс ParentWidgetState:

  • Управляет активным состоянием для TapboxB.
  • Реализует _handleTapboxChanged(), метод, вызываемый при касании поля.
  • Когда состояние изменяется, вызывается setState() для обновления пользовательского интерфейса.

Класс TapboxB:

  • Расширяет StatelessWidget, потому что все состояния обрабатываются его родителем.
  • При обнаружении касания он уведомляет родителя.
// ParentWidget manages the state for TapboxB.

//------------------------ ParentWidget --------------------------------

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: TapboxB(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

//------------------------- TapboxB ----------------------------------

class TapboxB extends StatelessWidget {
  TapboxB({Key key, this.active: false, @required this.onChanged})
      : super(key: key);

  final bool active;
  final ValueChanged onChanged;

  void _handleTap() {
    onChanged(!active);
  }

  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        child: Center(
          child: Text(
            active ? 'Active' : 'Inactive',
            style: TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}

[info] Совет: при создании API рассмотрите возможность использования аннотации @required для любых параметров, на которые опирается ваш код. Чтобы использовать @required, импортируйте основную библиотеку (которая повторно экспортирует библиотеку meta.dart языка Дарт): [/info]

  import 'package:flutter/foundation.dart';

Смешанный подход

Для некоторых виджетов наиболее подходящим является подход «смешай и сопоставляй». В этом сценарии виджет с отслеживанием состояния управляет некоторыми состояниями, а родительский виджет управляет другими аспектами состояния.

В примере TapboxC при касании вниз вокруг рамки появляется темно-зеленая рамка. При нажатии вверх рамка исчезает и цвет рамки меняется. TapboxC экспортирует свое _active состояние в родительский объект, но внутренне управляет своим состоянием _highlight. В этом примере есть два объекта State, _ParentWidgetState и _TapboxCState.

Объект _ParentWidgetState:

  • Управляет активным состоянием _active.
  • Реализует _handleTapboxChanged(), метод, вызываемый при касании поля.
  • Вызывает setState() для обновления пользовательского интерфейса, когда происходит касание и изменяется _active состояние.

Объект _TapboxCState:

  • Управляет состоянием _highlight.
  • GestureDetector прослушивает все события касания. Когда пользователь нажимает вниз, он добавляет выделение (реализовано как темно-зеленая рамка). Когда пользователь отпускает кран, он удаляет выделение.
  • Вызывает setState() для обновления пользовательского интерфейса касанием вниз, касанием вверх или касанием отмены, и состояние _highlight изменяется.
  • При событии касания передает это изменение состояния родительскому виджету для выполнения соответствующих действий с использованием свойства виджета.
//---------------------------- ParentWidget ----------------------------

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: TapboxC(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

//----------------------------- TapboxC ------------------------------

class TapboxC extends StatefulWidget {
  TapboxC({Key key, this.active: false, @required this.onChanged})
      : super(key: key);

  final bool active;
  final ValueChanged onChanged;

  _TapboxCState createState() => _TapboxCState();
}

class _TapboxCState extends State {
  bool _highlight = false;

  void _handleTapDown(TapDownDetails details) {
    setState(() {
      _highlight = true;
    });
  }

  void _handleTapUp(TapUpDetails details) {
    setState(() {
      _highlight = false;
    });
  }

  void _handleTapCancel() {
    setState(() {
      _highlight = false;
    });
  }

  void _handleTap() {
    widget.onChanged(!widget.active);
  }

  Widget build(BuildContext context) {
    // This example adds a green border on tap down.
    // On tap up, the square changes to the opposite state.
    return GestureDetector(
      onTapDown: _handleTapDown, // Handle the tap events in the order that
      onTapUp: _handleTapUp, // they occur: down, up, tap, cancel
      onTap: _handleTap,
      onTapCancel: _handleTapCancel,
      child: Container(
        child: Center(
          child: Text(widget.active ? 'Active' : 'Inactive',
              style: TextStyle(fontSize: 32.0, color: Colors.white)),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color:
              widget.active ? Colors.lightGreen[700] : Colors.grey[600],
          border: _highlight
              ? Border.all(
                  color: Colors.teal[700],
                  width: 10.0,
                )
              : null,
        ),
      ),
    );
  }
}

Альтернативная реализация могла бы экспортировать состояние выделения в родительский объект, сохраняя внутреннее активное состояние, но если бы вы попросили кого-то использовать этот блок переключателей, они, вероятно, пожаловались бы, что в этом нет особого смысла. Разработчик заботится, активен ли ящик. Разработчику, вероятно, все равно, как осуществляется управление выделением, и предпочитает, чтобы окно касаний обрабатывало эти детали.


Другие интерактивные виджеты

Flutter предлагает множество кнопок и аналогичных интерактивных виджетов( Material Design guidelines ). Большинство из этих виджетов реализуют рекомендации по дизайну материалов, которые определяют набор компонентов с продуманным пользовательским интерфейсом.

Если вы предпочитаете, вы можете использовать GestureDetector для создания интерактивности в любой пользовательский виджет. Вы можете найти примеры GestureDetector в разделе Управление состоянием и в галерее флаттера( Flutter Gallery ).

[info] Совет: Flutter также предоставляет набор виджетов в стиле iOS под названием Cupertino. [/info]

Когда вам нужна интерактивность, проще всего использовать один из готовых виджетов. Вот частичный список:

Стандартные виджеты

Материальные компоненты

Данная статья является переводом официальной статьи по ссылке Adding interactivity to your Flutter app

Пожалуйста, оцените материал

WebSofter

Web - технологии