Я написал несколько статей оFlutter
Сообщение в блоге, и недавно, заняло некоторое время, чтобы изучить и исследоватьFlutter
, завершена высокая имитация фабрикиAppпроект(Все интерфейсы, используемые проектом, взяты из реального онлайн-приложения, что может дать тот же эффект, что и онлайн-проект), также обобщил и накопил некоторые советы и знания, так что записывайте и делитесь ими здесь, и надеюсьFlutter
Экология становится все лучше и лучше(Эффективность разработки приложений во флаттере действительно высока, да и опыт разработки тоже очень хорош 🙂 ).
Следующая запись в блоге разделена на 4 части для обзора:
- Предварительный просмотр завершенного проекта
- Анализ структуры проекта
- Подробный обзор функций проекта (используемые очки знаний)
- Резюме накопления малых навыков
Предварительный просмотр завершенного проекта
Во-первых, давайте быстро просмотрим завершенные функции и запущенные эффекты проекта с помощью видео, как показано ниже.
Так как вышеприведенное видео размещено на CDN, количество просмотров велико, и взимается большая плата, поэтому видео может не воспроизводиться!
Если воспроизведение видео не удается,Пожалуйста, нажмите здесь, чтобы посмотреть (Вы можете нажать на настройку передач, чтобы скрыть черную границу)
Посмотрев видео, вы, наверное, поняли, что степень завершенности в основном такая же, как и онлайн.AppРазница та же.Если вас заинтересовал проект и вы хотите узнать как его реализовать, вы можете зайти на мойGitHubклонировать исходный код для просмотра.
Это видео было записано с помощью реальной машины, потому что функция голосового поиска требует записи, симулятор, конечно, не может записывать,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Цвет фона меняется с прозрачного на белый или с белого на прозрачный, что в основном используется здесь.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(
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
Страница голосового поиска
Эффект страницы голосового поиска показан на рисунке: Поскольку симулятор не может записывать, он не может отображать нормальный процесс. Если запись распознана успешно, он вернется на страницу поиска, и вы можете увидеть нормальный процесс в видео-превью проекта.
Функция голосового поиска использует 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
Использование флаттера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(), прямоугольник
В активном состоянии нет длинного эллипса, как показано на картинке выше.На самом деле, это нарисовать совок по тыкве и реализовать длинный эллипс самостоятельно.Если вы знаете детали, вы можетеНажмите, чтобы просмотреть исходный код индикатора «длинный овал»
навигация по плавающим значкам
значокнавигацияЭффект показан на рисунке:
значокнавигацияПарящий над баннером, он на самом деле используетflutter
изStackкомпонента, компонент стека позволяет размещать и отображать его подкомпоненты, что обычно сочетается сPositionedКомпоненты используются вместе, а код структуры макета выглядит следующим образом:
ListView(
children: <Widget>[
Container(
child: Stack(
children: <Widget>[
Container( ... ), //这里放的是banner的代码
Positioned( ... ), //这个就是icon导航,通过 Positioned 固定显示位置
],
),
),
Container( ... ), // 这里放的网格导航及其他
],
),
Навигация по градиентной нерегулярной сетке с фоновым изображением
Эффект навигации по сетке показан на рисунке:
Как показано на рисунке, сетка навигации разделена на три строки и четыре столбца, а первая строка разделена на три столбца.Первый столбец каждой строки шире трех других столбцов, а остальные три столбца равны. Каждая строка имеет градиентные цвета, а первая, Оба столбца имеют фоновые изображения;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 слева и справа
Конкретный эффект показан на рисунке: щелкните левую вкладку, чтобы переключить страницы, проведите пальцем влево и вправо, чтобы переключить страницы, щелкните, чтобы развернуть, чтобы показать больше, и т. д.
На самом деле официальный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, потому что этот компонент используется во многих других компонентах и нуждается в динамическом рендеринге в соответствии с данными интерфейса, а сам компонент имеет изменения состояния. Самое лучшее инкапсулировать его в компонент (виджет) отдельно, иначе сложно контролировать изменение собственного состояния, клик не имеет эффекта, либо клик влияет на другие компоненты.
страница поиска назначения
Эффект показан на рисунке: щелкните результат поиска, например: нажмите «Однодневный тур», и будут найдены соответствующие данные «однодневного тура».
Большинство целевых страниц поиска представляют собой код, связанный с макетом и интерфейсами стыковки, поэтому я не буду повторять их здесь.
Тогда естьСтраница с фотографиями из путешествийИспользуемые функции и очки знаний, сосредоточьтесь на следующих функциях:
- Вкладка макетаBarListView слева и справа
- карта водопада
- Страница поиска фотографий из путешествий
Вкладка макетаBarListView слева и справа
Эффект показан на рисунке: проведите пальцем влево и вправо для переключения страниц, потяните вверх, чтобы загрузить больше, потяните вниз, чтобы обновить и т. д.
Это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 для просмотра.
Страница поиска фотографий из путешествий
Эффект показан на рисунке: сначала отображаются популярные ярлыки с фотографиями из путешествий, нажмите для поиска соответствующего контента и введите ключевые слова для поиска соответствующей информации о фотографиях из путешествий, мест, достопримечательностей, пользователей и т. д.
Страница поиска путешественника, а также код макета и интерфейса стыковки здесь повторяться не будут.
Резюме накопления малых навыков
Ниже приведены точки знаний, которые я использовал в проекте, которые я записал и поделился здесь, надеясь помочь всем.
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 ...