1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Flutter-TE.md 21 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 10.03.2025 00:06 5767d61

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

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

FocusTrapArea

Компонент FocusTrapArea может показаться вам незнакомым, поскольку он был добавлен недавно. Сам по себе FocusTrapArea ничего особенного не представляет; он просто помещает FocusNode в дерево RenderObject.

Основная цель его внедрения заключается в поддержке платформ Web/Desktop. Внедрение FocusTrapArea позволяет полю ввода TextField сохранять фокус после выполнения метода TextEditingController.clear на этих платформах.

Подробнее см. issue Flutter: #86154, #86041.Обычное поведение | Ненормальное поведение | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | стабильная версия | мастерская версия |

MouseRegionИмя говорит само за себя — используется для обработки событий мыши, в основном для реакции на указательные события, связанные с мышью, такими как вход/выход курсора мыши в область компонента, эффекты отображения курсора и т.д.

IgnorePointer

Он主要用于处理 TextField 中当前输入框是否可用的状态,例如当 widget.enabled 或者 widget.decoration?.enabledfalse 时,IgnorePointer 将屏蔽区域内所有手势事件,从而使 TextField 不可点击输入。

TextSelectionGestureDetectorBuilder

关于 TextSelectionGestureDetectorBuilder,你可能最不常遇到它,而在 TextField 中使用的是它的子类 _TextFieldSelectionGestureDetectorBuilder

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

Внутри TextSelectionGestureDetectorBuilder основной механизм состоит в использовании GlobalKey editableTextKey для получения состояния EditableTextState, что позволяет связывать различные события жестов с поведением EditableText.

Внутри данного компонента используется TextSelectionGestureDetector.

Например, в _TextFieldSelectionGestureDetectorBuilder можно найти процесс обработки события onSingleTapUp:

Как показывают вышеупомянутые строки кода:- 1) Скрытие уже открытого ToolBar (первоначально это Overlay, то есть диалоговое окно для копирования/вставки);

    1. Выбор действия в зависимости от платформы;
    1. Вызов появления клавиатуры;
    1. Вызов обратного вызова события клика;Поэтому видно, что здесь сначала выполняется появление клавиатуры, а затем вызывается обратный вызов события клика. Поэтому если вам требуется выполнение некоторых действий перед появлением клавиатуры при клике, использование onTap в TextField может оказаться неудобным, поскольку он уже вызвал появление клавиатуры.

В конце концов, _TextFieldSelectionGestureDetectorBuilder вызывает метод buildGestureDetector для создания контроллера, который слушает и обрабатывает события касания, чтобы использовать его для управления потомством.

InputDecorator

Что касается внутренних параметров InputDecorator, подробное объяснение здесь приведено не будет, так как ранее данная тема была рассмотрена более детально в книге. Для тех, кто работал с TextField, InputDecorator должен быть знакомым компонентом; в TextField реализация InputDecorator осуществляется вместе с AnimatedBuilder. Поскольку в TextField как FocusNode, так и TextEditingController являются ChangeNotifier (Listenable), они могут использоваться в качестве animation в AnimatedBuilder.

Изображение

Когда FocusNode и TextEditingController изменяются, это приводит к тому, что InputDecorator заново строится (rebuild), меняя тем самым эффект отрисовки, например, при вводе данных в поле ввода или смене фокуса можно менять цвет фона поля ввода.

Обратите внимание, чтобы не спутать InputDecorator и InputDecoration. InputDecoration используется для конфигурации InputDecorator.Изображение

Как видно, InputDecorator имеет множество параметров и возможностей для настройки, позволяющих разработчику создавать сложные пользовательские интерфейсы для полей ввода. Однако если вы столкнетесь со случаями, когда некоторые позиции или зазоры не удовлетворяют странным требованиям продукта, то поздравляем вас — вы начинаете путь продвинутого разработчика Flutter.

Почему?

Проще говоря, реализация InputDecorator основана на внутреннем пользовательском RenderBox, где части кода, связанные с макетом, занимают более 600 строк. Это значит, что согласно параметрам InputDecoration, таким как icon, prefixIcon, suffix и т.д., происходит расположение компонентов, расчет положений и направлений, а также корректировка положений относительно базовой линии.

Кроме того, анимационные эффекты внутри InputDecorator создаются главным образом через внутренние компоненты типа AnimatedOpacity.

Поэтому, если вам не нравятся какие-то позиции или границы в InputDecorator, либо придется переопределить его реализацию, либо придется принять ситуацию «все равно лучше, чем ничего».

RepaintBoundary

Зачем внутри TextField есть RepaintBoundary? В первую очередь, что делает RepaintBoundary?

Ранее было подробно объяснено в статье "Полное понимание рендеринга в Flutter". Проще говоря, RepaintBoundary主要用于创建一个Layer,获得一个独立的绘图区域.Часто это встречается при переходах между страницами в Navigator, где каждый регион является самостоятельной областью рисования благодаря наличию RepaintBoundary.

Также стоит отметить, что каждая страница в Navigator имеет свой FocusScope, который мы используем для управления клавиатурой и фокусом, например, через метод FocusScope.of(context). Внутри TextField существует RepaintBoundary, поскольку TextField представляет собой компонент, который требует частого обновления. В то же время изменения содержимого внутри TextField обычно не требуют перерисовки родительского контейнера, поэтому RepaintBoundary позволяет реализовать более эффективное локальное отрисовывание.

UnmanagedRestorationScope

UnmanagedRestorationScope используется реже всего; это InheritedWidget, который предназначен для передачи вниз одного RestorationBucket. RestorationBucket主要用于与状态的保存和恢复相关的工作。

例如,当应用程序由于内存不足而在后台被系统回收时,可以使用它来恢复特定数据。举个例子:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // Присвойте вашему RootRestorationScope уникальный идентификатор, значение по умолчанию — null.
      restorationScopeId: 'root',
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

// Наше состояние должно быть смешано с RestorationMixin
class _HomePageState extends State<HomePage> with RestorationMixin {
```  // Для каждого состояния нам нужно использовать восстановляемое свойство
  final RestorableInt _index = RestorableInt(0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('Индекс равен ${_index.value}')),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _index.value,
        onTap: (i) => setState(() => _index.value = i),
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(
              icon: Icon(Icons.home),
              label: 'Главная'
          ),
          BottomNavigationBarItem(
              icon: Icon(Icons.notifications),
              label: 'Уведомления'
          ),
          BottomNavigationBarItem(
              icon: Icon(Icons.settings),
              label: 'Настройки'
          ),
        ],
      ),
    );
  }

  @override
  // Уникальный идентификатор для этой страницы,
  // давайте назовём его по имени нашей страницы!
  String get restorationId => 'home_page';

  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    // Регистрация нашего свойства для сохранения каждый раз, когда оно меняется,
    // и для восстановления каждый раз, когда наша приложение уничтожается ОС!
    registerForRestoration(_index, 'nav_bar_index');
  }
}

```Как показано выше кодом:

- Вначале конфигурируется `MaterialApp` с помощью `restorationScopeId` (необходимо обязательно указать, чтобы включить данную функцию).
- Используется `RestorableInt`, чтобы настроить и сохранить значение `index` компонента `BottomNavigationBar`.
- В `State` внедряется `RestorationMixin`, а состояние `index` восстанавливается внутри метода `restoreState`.```По умолчанию внутренняя часть `MaterialApp` использует `RootRestorationScope`, который сам представляет собой `UnmanagedRestorationScope`. После запуска данного примера можно наблюдать эффект, отключив опцию "Не сохранять активность" в настройках разработчика эмулятора.> Пример взят из статьи "[Введение в восстановление состояния в Flutter](https://dev.to/pedromassango/что-такое-восстановление-состояния-и-как-его-использовать-в-flutter-5blm)".

Перейдем к `TextField`: в `_TextFieldState` внедряется `RestorationMixin`, а затем используется `RestorableTextEditingController` для восстановления `TextEditingController`.

> Поскольку содержимое поля ввода по умолчанию сохраняется в поле `TextEditingValue` объекта `TextEditingController`, здесь используется `RestorableTextEditingController`.

![Иллюстрация](http://img.cdn.guoshuyu.cn/2bkj.png)

Обычно `MaterialApp` уже содержит встроенную реализацию `RootRestorationScope`, поэтому нам достаточно установить `restorationScopeId` для `MaterialApp`. А **`TextField` благодаря встроенной логике `UnmanagedRestorationScope` обеспечивает сохранение и восстановление введенного текста**.


## EditableText

`EditableText`  это основной компонент `TextField`, который позволяет прокручивать контент через `Scrollable`. Также он использует соответствующий `restorationId` для восстановления и кэширования данных.

**Сначала обратите внимание на возможность прокрутки: для `EditableText` это как "viewport", который работает на основе `ViewportOffset` для достижения эффекта прокрутки**.

А внутри `EditableText` используется `CompositedTransformTarget` для связи между `Toolbar` и полем ввода, то есть для связи между элементами управления вводом и плавающими окнами "вставить/вырезать".

**Поэтому давайте кратко рассмотрим `CompositedTransformTarget`: этот компонент обычно используется вместе с `CompositedTransformFollower` для создания связей между различными элементами управления**.![Иллюстрация](http://img.cdn.guoshuyu.cn/20211223_Flutter-TE/image8) Как показано выше, часто используемый встроенный `Slider`, реализуется с использованием `CompositedTransformTarget` и `CompositedTransformFollower`. **Это позволяет одному компоненту следовать за другим без необходимости вычисления позиций; они соединены между собой через `LayerLink`**. Возврат к `TextField`, на самом деле, помимо панели инструментов "Копировать/вставить", выборка выделенного текста внутри `EditableText` также реализуется аналогичным образом, просто здесь используется слой `LeaderLayer` напрямую, а не через его упаковку `CompositedTransformTarget`.

> Для заинтересованных в использовании `CompositedTransformTarget` можно обратиться к следующей статье: https://juejin.cn/post/6946416845537116190

Конечно, использование `CompositedTransformTarget` всё ещё приводит к значительному снижению производительности, поэтому не рекомендуется использовать его часто и массово, так как это относится к операциям типа `pushLayer`.

Кроме того, внутренняя часть `EditableText`, отвечающая за отрисовку содержимого, основана на известной нам `TextPainter`. В этом нет ничего особенного, подробнее мы пока не будем раскрывать.

**Поэтому цель данной статьи  это представление составных частей `TextField` и объяснение их функциональности, чтобы разработчики могли лучше понять реализацию часто используемых полей ввода текста в Flutter, что поможет быстро находить и решать проблемы**, такие как:- Откуда появляется панель инструментов "Копировать/вставить";
- Как панель инструментов располагается и размещается;
- Как при нажатии на `TextField` вызывается клавиатура и обрабатываются события жестами;
- Как `TextField` обеспечивает локальное отображение;
- ...


Наконец, рассмотрим простой пример. Недавно кто-то спросил меня: ***как реализовать эффект перехода с однострочного поля ввода на многолинейное в Flutter, подобно тому, как это сделано в WeChat***, вот код:

```dart
TextField(
  focusNode: _focusNode,
  maxLines: 7,
  minLines: 1,
  decoration:
      const InputDecoration(border: OutlineInputBorder()),
)

Изображение

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/CarGuo-GSYFlutterBook.git
git@api.gitlife.ru:oschina-mirror/CarGuo-GSYFlutterBook.git
oschina-mirror
CarGuo-GSYFlutterBook
CarGuo-GSYFlutterBook
master