Краткое изложение 10-дневной имитации больших фабричных приложений Flutter и советы

Flutter
Краткое изложение 10-дневной имитации больших фабричных приложений Flutter и советы

Я написал несколько статей оFlutterСообщение в блоге, и недавно, заняло некоторое время, чтобы изучить и исследоватьFlutter, завершена высокая имитация фабрикиAppпроект(Все интерфейсы, используемые проектом, взяты из реального онлайн-приложения, что может дать тот же эффект, что и онлайн-проект), также обобщил и накопил некоторые советы и знания, так что записывайте и делитесь ими здесь, и надеюсьFlutterЭкология становится все лучше и лучше(Эффективность разработки приложений во флаттере действительно высока, да и опыт разработки тоже очень хорош 🙂 ).

Следующая запись в блоге разделена на 4 части для обзора:

  • Предварительный просмотр завершенного проекта
  • Анализ структуры проекта
  • Подробный обзор функций проекта (используемые очки знаний)
  • Резюме накопления малых навыков

Предварительный просмотр завершенного проекта

Во-первых, давайте быстро просмотрим завершенные функции и запущенные эффекты проекта с помощью видео, как показано ниже.

Так как вышеприведенное видео размещено на CDN, количество просмотров велико, и взимается большая плата, поэтому видео может не воспроизводиться!

Если воспроизведение видео не удается,Пожалуйста, нажмите здесь, чтобы посмотреть (Вы можете нажать на настройку передач, чтобы скрыть черную границу)

Посмотрев видео, вы, наверное, поняли, что степень завершенности в основном такая же, как и онлайн.AppРазница та же.Если вас заинтересовал проект и вы хотите узнать как его реализовать, вы можете зайти на мойGitHubклонировать исходный код для просмотра.

Это видео было записано с помощью реальной машины, потому что функция голосового поиска требует записи, симулятор, конечно, не может записывать,iOSа такжеAndoridможно запустить, эффект тот же, что и на рисунке:

iOS Andorid
iOS Andorid

Анализ структуры проекта

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

├── README.md  # 描述文件
├── android    # android 宿主环境
├── build      # 项目构建目录,由flutter自动完成
├── flutter_ctrip.iml
├── fonts      # 自己创建的目录,用于存放字体
├── images     # 自己创建的目录,用于存放图片
├── ios        # iOS 宿主环境
├── lib        # flutter 执行文件,自己写的代码都在这
├── pubspec.lock # 用来记录锁定插件版本
├── pubspec.yaml # 插件及资源配置文件
└── test       # 测试目录

Объяснять это не нужно, большинство из них генерируются и управляются флаттером, на что нужно обратить внимание, так этоlibсодержание.

Давайте посмотрим на вторичный каталог, как показано ниже.(сосредоточьтесь на каталоге lib)

├── README.md
├── android
│   ├── android.iml
  ...
│   └── settings.gradle
├── build
│   ├── app
  ...
│   └── snapshot_blob.bin.d.fingerprint
├── flutter_ctrip.iml
├── fonts
│   ├── PingFang-Italic.ttf
│   ├── PingFang-Regular.ttf
│   └── PingFang_Bold.ttf
├── images
│   ├── grid-nav-items-dingzhi.png
  ...
│   └── yuyin.png
├── ios
│   ├── Flutter
  ...
│   └── ServiceDefinitions.json
├── lib
│   ├── dao           # 请求接口的类
│   ├── main.dart     # flutter 入口文件
│   ├── model         # 实体类,把服务器返回的 json 数据,转换成 dart 类
│   ├── navigator     # bottom bar 首页底部导航路由
│   ├── pages         # 所以的页面
│   ├── plugin        # 封装的插件
│   ├── util          # 工具类,避免重复代码,封装成工具类以便各个 page 调用
│   └── widget        # 封装的组件
├── pubspec.lock
├── pubspec.yaml
└── test
    └── widget_test.dart

Посмотри снова,libСправочник, вторичный каталог, посмотрите на весь проект создал ряд документов, напишите, сколько кода следующим образом(на самом деле не так много)

├── dao/
│   ├── destination_dao.dart*
│   ├── destination_search_dao.dart*
│   ├── home_dao.dart
│   ├── search_dao.dart*
│   ├── trave_hot_keyword_dao.dart*
│   ├── trave_search_dao.dart*
│   ├── trave_search_hot_dao.dart*
│   ├── travel_dao.dart*
│   ├── travel_params_dao.dart*
│   └── travel_tab_dao.dart*
├── main.dart
├── model/
│   ├── common_model.dart
│   ├── config_model.dart
│   ├── destination_model.dart
│   ├── destination_search_model.dart
│   ├── grid_nav_model.dart
│   ├── home_model.dart
│   ├── sales_box_model.dart
│   ├── seach_model.dart*
│   ├── travel_hot_keyword_model.dart
│   ├── travel_model.dart*
│   ├── travel_params_model.dart*
│   ├── travel_search_hot_model.dart
│   ├── travel_search_model.dart
│   └── travel_tab_model.dart
├── navigator/
│   └── tab_navigater.dart
├── pages/
│   ├── destination_page.dart
│   ├── destination_search_page.dart
│   ├── home_page.dart
│   ├── my_page.dart
│   ├── search_page.dart
│   ├── speak_page.dart*
│   ├── test_page.dart
│   ├── travel_page.dart
│   ├── travel_search_page.dart
│   └── travel_tab_page.dart*
├── plugin/
│   ├── asr_manager.dart*
│   ├── side_page_view.dart
│   ├── square_swiper_pagination.dart
│   └── vertical_tab_view.dart
├── util/
│   └── navigator_util.dart*
└── widget/
    ├── grid_nav.dart
    ├── grid_nav_new.dart
    ├── loading_container.dart
    ├── local_nav.dart
    ├── sales_box.dart
    ├── scalable_box.dart
    ├── search_bar.dart*
    ├── sub_nav.dart
    └── webview.dart

Весь проект это вышеуказанные файлы(Специфика не будет разбираться поштучно. Например, если вам интересно, вы можете клонировать исходный код и запустить его, и он очистится естественным образом).

Подробный обзор функций проекта (используемые очки знаний)

Во-первых, давайте посмотрим на функции домашней страницы и используемые очки знаний.Главная страница ориентирована на реализацию следующих функций:

  • исчезновениеappBbar
  • Обертка поискового компонента
  • Страница голосового поиска
  • компонент баннера
  • навигация по плавающим значкам
  • Навигация по градиентной нерегулярной сетке с фоновым изображением

Затухание и исчезновение appBbar

Давайте сначала посмотрим на конкретный эффект и посмотрим на аромат, как показано на рисунке:

appBar
appBar

при прокруткеappBarЦвет фона меняется с прозрачного на белый или с белого на прозрачный, что в основном используется здесь.flutterизNotificationListenerкомпонент, он будет прослушивать событие всплытия дерева компонентов, когда компонент, обернутый им(Подсборка)когда происходят изменения,NotificationБудет запущена функция обратного вызова, поэтому она может отслеживать прокрутку страницы для ее динамического изменения.appBarпрозрачность(альфа), код показан ниже:

NotificationListener(
  onNotification: (scrollNotification) {
    if (scrollNotification is ScrollUpdateNotification &&
        scrollNotification.depth == 0) {
      _onScroll(scrollNotification.metrics.pixels);
    }
    return true;
  },
  child: ...

Tips:

scrollNotification.depthЗначение 0 указывает на его дочерние компоненты.(Контролировать только дочерние компоненты, а не внучатые компоненты);

scrollNotification is ScrollUpdateNotificationчтобы определить, был ли компонент обновлен,ScrollUpdateNotificationЭто ситуация жизненного цикла уведомлений, которая выглядит следующим образом:

  • Компонент ScrollStartNotification начинает прокрутку
  • Положение компонента ScrollUpdateNotification изменилось
  • Компонент ScrollEndNotification останавливает прокрутку
  • UserScrollNotification неясно

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

_onScrollКод метода следующий:

  void _onScroll(offset) {
    double alpha = offset / APPBAR_SCROLL_OFFSET;  // APPBAR_SCROLL_OFFSET 常量,值:100;offset 滚动的距离

    //把 alpha 值控制值 0-1 之间
    if (alpha < 0) {
      alpha = 0;
    } else if (alpha > 1) {
      alpha = 1;
    }
    setState(() {
      appBarAlpha = alpha;
    });
    print(alpha);
  }

Обертка поискового компонента

Действие компонента поиска показано на рисунке:

searchBar
searchBar

Ниже приведен вызов домашней страницыsearchBarкод:

SearchBar(
  searchBarType: appBarAlpha > 0.2  //searchBar 的类:暗色、亮色
      ? SearchBarType.homeLight
      : SearchBarType.home,
  inputBoxClick: _jumpToSearch,     //点击回调函数
  defaultText: SEARCH_BAR_DEFAULT_TEXT,   // 提示文字
  leftButtonClick: () {},           //左边边按钮点击回调函数
  speakClick: _jumpToSpeak,         //点击话筒回调函数
  rightButtonClick: _jumpToUser,    //右边边按钮点击回调函数
),

На самом деле, используйтеTextFieldКомпоненты, а также некоторые стили, на которые следует обратить внимание:onChanged,онTextFieldИспользуется для отслеживания изменения текстового поля, с помощью которого мы отслеживаем ввод пользователя для запроса данных интерфейса; Для конкретных деталей реализации, пожалуйста, обратитесь к исходному коду:Нажмите, чтобы просмотреть исходный код searchBar

Страница голосового поиска

Эффект страницы голосового поиска показан на рисунке: Поскольку симулятор не может записывать, он не может отображать нормальный процесс. Если запись распознана успешно, он вернется на страницу поиска, и вы можете увидеть нормальный процесс в видео-превью проекта.

speak
speak

Функция голосового поиска использует SDK для распознавания языка Baidu. После родного доступа черезMethodChannelОбщение с родной нативной стороной, которая здесь не будет подчеркиваться (здесь будут задействованы знания родной нативной).

Сосредоточьтесь на реализации анимации при нажатии на кнопку записи. Эта анимация используетAnimatedWidgetРеализовано, код такой:

class AnimatedWear extends AnimatedWidget {
  final bool isStart;
  static final _opacityTween = Tween<double>(begin: 0.5, end: 0); // 设置透明度变化值
  static final _sizeTween = Tween<double>(begin: 90, end: 260);   // 设置圆形线的扩散值

  AnimatedWear({Key key, this.isStart, Animation<double> animation})
      : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;  // listenable 继承 AnimatedWidget,其实就是控制器,会自动监听组件的变化
    return Container(
      height: 90,
      width: 90,
      child: Stack(
        overflow: Overflow.visible,
        alignment: Alignment.center,
        children: <Widget>[
          ...
          // 扩散的圆线,其实就是用一个圆实现的,设置圆为透明,设置border
          Positioned(
            left: -((_sizeTween.evaluate(animation) - 90) / 2), // 根据 _sizeTween 动态设置left偏移值
            top: -((_sizeTween.evaluate(animation) - 90) / 2), //  根据 _sizeTween 动态设置top偏移值
            child: Opacity(
              opacity: _opacityTween.evaluate(animation),      // 根据 _opacityTween 动态设置透明值
              child: Container(
                width: isStart ? _sizeTween.evaluate(animation) : 0, // 设置 宽
                height: _sizeTween.evaluate(animation),              // 设置 高
                decoration: BoxDecoration(
                    color: Colors.transparent,
                    borderRadius: BorderRadius.circular(
                        _sizeTween.evaluate(animation) / 2),
                    border: Border.all(
                      color: Color(0xa8000000),
                    )),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

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

компонент баннера

Эффект показан на рисунке:

banner
banner

bannerИспользование флаттераflutter_swiperПлагин реализует код следующим образом:

Swiper(
  itemCount: bannerList.length,              // 滚动图片的数量
  autoplay: true,                            // 自动播放
  pagination: SwiperPagination(              // 指示器
      builder: SquareSwiperPagination(
        size: 6,                             // 指示器的大小
        activeSize: 6,                       // 激活状态指示器的大小
        color: Colors.white.withAlpha(80),   // 颜色
        activeColor: Colors.white,           // 激活状态的颜色
      ),
    alignment: Alignment.bottomRight,        // 对齐方式
    margin: EdgeInsets.fromLTRB(0, 0, 14, 28), // 边距
  ),
  itemBuilder: (BuildContext context, int index) { // 构造器
    return GestureDetector(
      onTap: () {
        CommonModel model = bannerList[index];
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => WebView(
              url: model.url,
            ),
          ),
        );
      },
      child: Image.network(
        bannerList[index].icon,
        fit: BoxFit.fill,
      ),
    );
  },
),

Для конкретного использования вы можете перейти к официальной библиотеке плагинов флаттера.pub.devПроверить:Нажмите flutter_swiper для просмотра.

Советы:

Следует отметить, что я немного изменил стиль индикатора,flutter_swiperПредусмотрены только 3 стиля индикатора следующим образом:

  • точки = const DotSwiperPaginationBuilder(), круг
  • фракция = const FractionPaginationBuilder(), процентный тип, например: 1/6, что означает первую страницу из 6 страниц
  • rect = const RectSwiperPaginationBuilder(), прямоугольник

В активном состоянии нет длинного эллипса, как показано на картинке выше.На самом деле, это нарисовать совок по тыкве и реализовать длинный эллипс самостоятельно.Если вы знаете детали, вы можетеНажмите, чтобы просмотреть исходный код индикатора «длинный овал»

навигация по плавающим значкам

значокнавигацияЭффект показан на рисунке:

iconBar

значокнавигацияПарящий над баннером, он на самом деле используетflutterизStackкомпонента, компонент стека позволяет размещать и отображать его подкомпоненты, что обычно сочетается сPositionedКомпоненты используются вместе, а код структуры макета выглядит следующим образом:

ListView(
  children: <Widget>[
    Container(
      child: Stack(
        children: <Widget>[
          Container( ... ), //这里放的是banner的代码
          Positioned( ... ), //这个就是icon导航,通过 Positioned 固定显示位置
        ],
      ),
    ),
    Container( ... ), // 这里放的网格导航及其他
  ],
),

Навигация по градиентной нерегулярной сетке с фоновым изображением

Эффект навигации по сетке показан на рисунке:

gridNav

Как показано на рисунке, сетка навигации разделена на три строки и четыре столбца, а первая строка разделена на три столбца.Первый столбец каждой строки шире трех других столбцов, а остальные три столбца равны. Каждая строка имеет градиентные цвета, а первая, Оба столбца имеют фоновые изображения;flutterвнутриColumnКомпоненты позволяют располагать подкомпоненты вертикально,RowКомпоненты позволяют размещать подкомпоненты по горизонтальной оси Код макета выглядит следующим образом:

Column(                      // 最外面放在 Column 组件
  children: <Widget>[
    Container(               // 第一行包裹 Container 设置其渐变色
      height: 72,
      decoration: BoxDecoration(
        gradient: LinearGradient(colors: [  //设置渐变色
          Color(0xfffa5956),
          Color(0xffef9c76).withAlpha(45)
        ]),
      ),
      child: Row( ... ),    // 第一行
    ),
    Padding(
      padding: EdgeInsets.only(top: 1),  // 设置行直接的间隔
    ),
    Container(
      height: 72,
      decoration: BoxDecoration(
        gradient: LinearGradient(colors: [  //设置渐变色
          Color(0xff4b8fed),
          Color(0xff53bced),
        ]),
      ),
      child: Row( ... ),  // 第二行
    ),
    Padding(
      padding: EdgeInsets.only(top: 1),   // 设置行直接的间隔
    ),
    Container(
      height: 72,
      decoration: BoxDecoration(
        gradient: LinearGradient(colors: [  //设置渐变色
          Color(0xff34c2aa),
          Color(0xff6cd557),
        ]),
      ),
      child: Row( ... ),  // 第三行
    ),
  ],
),

На самом деле, есть еще много деталей конкретной реализации, таких как:

  • Как сделать ширину первого столбца больше, а остальные равными;
  • Ширина последнего столбца первой строки в 2 раза больше ширины другого;
  • Фоновое изображение первого и второго столбцов и плавающий красный кончик пузыря и т. д.;

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

Во-вторых, давайте посмотрим наназначенияИспользуемые функции страницы и точки знаний фокусируются на реализации следующих функций:

  • Вкладка макетаBarListView слева и справа
  • страница поиска назначения

Вкладка макетаBarListView слева и справа

Конкретный эффект показан на рисунке: щелкните левую вкладку, чтобы переключить страницы, проведите пальцем влево и вправо, чтобы переключить страницы, щелкните, чтобы развернуть, чтобы показать больше, и т. д.

destination
destination

На самом деле официальныйtabBarа такжеTabBarViewКомпоненты могут достигать эффекта компоновки вверх и вниз(Страница фотографии путешествий реализована с этим), но он не может обеспечить левое и правое расположение и не очень гибкий, поэтому я используюvertical_tabsПлагин, код такой:

VerticalTabView(
    tabsWidth: 88,
    tabsElevation: 0,
    indicatorWidth: 0,
    selectedTabBackgroundColor: Colors.white,
    backgroundColor: Colors.white,
    tabTextStyle: TextStyle(
      height: 60,
      color: Color(0xff333333),
    ),
    tabs: tabs,
    contents: tabPages,
  ),
),

Конкретный метод использования здесь повторяться не будет.Нажмите на vertical_tabs для просмотра

Здесь следует отметить: Разверните, чтобы отобразить больше реализаций компонента тега span, потому что этот компонент используется во многих других компонентах и ​​нуждается в динамическом рендеринге в соответствии с данными интерфейса, а сам компонент имеет изменения состояния. Самое лучшее инкапсулировать его в компонент (виджет) отдельно, иначе сложно контролировать изменение собственного состояния, клик не имеет эффекта, либо клик влияет на другие компоненты.

страница поиска назначения

Эффект показан на рисунке: щелкните результат поиска, например: нажмите «Однодневный тур», и будут найдены соответствующие данные «однодневного тура».

destination
destination

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

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

  • Вкладка макетаBarListView слева и справа
  • карта водопада
  • Страница поиска фотографий из путешествий

Вкладка макетаBarListView слева и справа

Эффект показан на рисунке: проведите пальцем влево и вправо для переключения страниц, потяните вверх, чтобы загрузить больше, потяните вниз, чтобы обновить и т. д.

travel
travel

Этоflutterпредоставленные компоненты,tabBarа такжеTabBarView, код показан ниже:

Container(
  color: Colors.white,
  padding: EdgeInsets.only(left: 2),
  child: TabBar(
    controller: _controller,
    isScrollable: true,
    labelColor: Colors.black,
    labelPadding: EdgeInsets.fromLTRB(8, 6, 8, 0),
    indicatorColor: Color(0xff2FCFBB),
    indicatorPadding: EdgeInsets.all(6),
    indicatorSize: TabBarIndicatorSize.label,
    indicatorWeight: 2.2,
    labelStyle: TextStyle(fontSize: 18),
    unselectedLabelStyle: TextStyle(fontSize: 15),
    tabs: tabs.map<Tab>((Groups tab) {
      return Tab(
        text: tab.name,
      );
    }).toList(),
  ),
),
Flexible(
    child: Container(
  padding: EdgeInsets.fromLTRB(6, 3, 6, 0),
  child: TabBarView(
      controller: _controller,
      children: tabs.map((Groups tab) {
        return TravelTabPage(
          travelUrl: travelParamsModel?.url,
          params: travelParamsModel?.params,
          groupChannelCode: tab?.code,
        );
      }).toList()),
)),

карта водопада

карта водопадаиспользуетсяflutter_staggered_grid_viewПлагин, код такой:

StaggeredGridView.countBuilder(
  controller: _scrollController,
  crossAxisCount: 4,
  itemCount: travelItems?.length ?? 0,
  itemBuilder: (BuildContext context, int index) => _TravelItem(
        index: index,
        item: travelItems[index],
      ),
  staggeredTileBuilder: (int index) => new StaggeredTile.fit(2),
  mainAxisSpacing: 2.0,
  crossAxisSpacing: 2.0,
),

Подробнее об этом ниже,Нажмите flutter_staggered_grid_view для просмотра.

Страница поиска фотографий из путешествий

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

no-shadow
travel-search

Страница поиска путешественника, а также код макета и интерфейса стыковки здесь повторяться не будут.

Резюме накопления малых навыков

Ниже приведены точки знаний, которые я использовал в проекте, которые я записал и поделился здесь, надеясь помочь всем.

PhysicalModel

PhysicalModelВы можете обрезать контейнер с фоновым изображением.Например, если вы помещаете изображение в контейнер и хотите установить закругленные углы изображения, установка borderRadius украшения контейнера недействительна.PhysicalModel, код показан ниже:

PhysicalModel(
  borderRadius: BorderRadius.circular(6),  // 设置圆角
  clipBehavior: Clip.antiAlias,            // 裁剪行为
  color: Colors.transparent,               // 颜色
  elevation: 5,                            // 设置阴影
  child: Container(
        child: Image.network(
          picUrl,
          fit: BoxFit.cover,
        ),
      ),
),

LinearGradient

Добавьте в контейнер градиентный цвет, который используется в навигации по сетке, панели приложений и т. д. Код выглядит следующим образом:

Container(
  height: 72,
  decoration: BoxDecoration(
    gradient: LinearGradient(colors: [
      Color(0xff4b8fed),
      Color(0xff53bced),
    ]),
  ),
  child: ...
),

Color(int.parse('0xff' + gridNavItem.startColor))

Значение цвета преобразуется в цвет.Если нет переменной, ее также можно использовать непосредственно таким образом.Color(0xff53bced),

  • ox: флаттер требования, можно исправить
  • ff: представляет прозрачность. Если вы не знаете, как ее установить, вы можете использовать палитру цветов или withOpacity(opacity) , withAlpha(a)
  • 53bced: обычное 6-битное значение RGB.

Расширенный, FractionallySizedBox

ExpandedПозволяет дочерним компонентам заполнять родительский контейнер, обычно сRowа такжеColumnКомпонентная смесь;
FractionallySizedBoxМожет заставить дочерние компоненты заполнять или превышать родительский контейнер, может использоваться отдельно, на размер влияют факторы ширины и высоты widthFactor и heightFactor.

MediaQuery.removePadding

MediaQuery.removePaddingВы можете удалить поля компонентов. Некоторые компоненты имеют свои собственные поля. Иногда при компоновке вам не нужны поля. Сейчас вы можете их использовать.MediaQuery.removePadding, код показан ниже:

MediaQuery.removePadding(
  removeTop: true,
  context: context,
  child: ...
)

MediaQuery.of(context).size.width

MediaQuery.of(context).size.widthПолучите ширину экрана таким же образом,MediaQuery.of(context).size.heightПолучить высоту экрана; Например, если вы хотите разделить строку на 3 равные части: 0,3 * MediaQuery.of(context).size.width, встраница назначенияКомпонент label использует его, код выглядит следующим образом:

Container(
  alignment: Alignment.center,
  ...
  width: 0.3*MediaQuery.of(context).size.width - 12, // 屏幕平分三等分, - 12 是给每份中间留出空间 
  height: 40,
  ...
  child: ...
),

Theme.of(context).platform == TargetPlatform.iOS

Для определения типа операционной системы иногда может потребоваться ее использование для создания разных раскладок для Android и iOS.

with AutomaticKeepAliveClientMixin

flutterПри переключении страниц данные будут перезагружаться каждый раз, если вы хотите, чтобы страница сохраняла состояние и не перезагружалась, вам нужно использоватьAutomaticKeepAliveClientMixin, код показан ниже:(Используется на странице с фотографиями из путешествий, чтобы предотвратить перезагрузку tabBar и tabBarView при переключении)

class TravelTabPage extends StatefulWidget {
  ...
  //需要重写 wantKeepAlive 且 设置成 true
  @override
  bool get wantKeepAlive => true;
}

with SingleTickerProviderStateMixin

смешатьSingleTickerProviderStateMixinВы можете использовать эффекты анимации при переключении страниц, такие как:

bottomNavigationBar: BottomNavigationBar(
          currentIndex: _currentIndex,
          onTap: (index) {
            _controller.animateToPage( // _controller 是 PageView 的 PageController
                index,
                curve: Curves.easeIn, duration: Duration(milliseconds: 260)
            );
            setState(() {
              _currentIndex = index;
            });
          },
          ...
)          

SystemUiOverlayStyle

SystemUiOverlayStyleСтрока состояния может быть погружена, а глобальная конфигурация может быть установлена ​​следующим образом:

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    TextStyle textStyle = TextStyle(fontSize: 20);
    SystemUiOverlayStyle style = SystemUiOverlayStyle(
        statusBarColor: Colors.transparent,
        statusBarIconBrightness: Brightness.light
    );
    SystemChrome.setSystemUIOverlayStyle(style);

    return MaterialApp(
      ...
  }
}

Настройки одной страницы следующие:

class _HomePageState extends State<HomePage> {

  ...

  @override
  Widget build(BuildContext context) {
    SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light); //设置状态栏沉浸式
    return Scaffold(
      backgroundColor: Color(0xfffafafc),
      body: LoadingContainer(
        ...
      )
      ...

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

адрес блога: lishaoy.net

Адрес блога: h.lishaoy.net

Адрес проекта на GitHub: Github.com/pierce Oh / Law ...