flutter学习-day13-功能型组件和状态共享

2023-12-17 20:36

本文主要是介绍flutter学习-day13-功能型组件和状态共享,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

📚 目录

  1. 导航返回拦截
  2. InheritedWidget数据共享
  3. 跨组件状态共享
    1. 事件总线EventBus
    2. 依赖注入Provider
  4. 颜色和主题
    1. 颜色字符串转成color对象
    2. 颜色亮度
    3. MaterialColor类
    4. 主体
  5. 异步UI更新
    1. FutureBuilder
    2. StreamBuilder
  6. 对话框

本文学习和引用自《Flutter实战·第二版》:作者:杜文

1. 导航返回拦截

为了避免用户误触返回按钮而导致 App 退出,在很多 App 中都拦截了用户点击返回键的按钮,然后进行一些防误触判断,比如当用户在某一个时间段内点击两次时,才会认为用户是要退出。Flutter中可以通过WillPopScope来实现返回按钮拦截。

  • 示例如下:
import 'package:flutter/material.dart';class HomePage extends StatefulWidget {const HomePage({super.key});State<HomePage> createState() => HomePageState();
}class HomePageState extends State<HomePage> {/// 时间DateTime? times;Widget build(BuildContext context) {return WillPopScope(onWillPop: () async {const oneSecond = Duration(seconds: 1);if (times == null || DateTime.now().difference(times!) > oneSecond) {// 两次点击间隔超过1秒则重新计时times = DateTime.now();return false;}return true;},child: Scaffold(appBar: AppBar(title: const Text('Flutter Home'),),body: Center(child: TextButton(child: const Text('点击我'),onPressed: () {},),),),);}
}

2. InheritedWidget数据共享

InheritedWidget提供了一种在 widget 树中从上到下共享数据的方式,比如我们在应用的根 widget 中通过InheritedWidget共享了一个数据,那么我们便可以在任意子widget 中来获取该共享的数据。如Flutter SDK中正是通过 InheritedWidget 来共享应用主题和Locale信息的。如下例子:

  • 计数器使用共享数据
import 'package:flutter/material.dart';class ShareDataWidget extends InheritedWidget {const ShareDataWidget({super.key, required this.data, required Widget child}): super(child: child);final int data;static ShareDataWidget? of(BuildContext context) {return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();}bool updateShouldNotify(ShareDataWidget old) {return old.data != data;}
}class MyTextWidget extends StatefulWidget {const MyTextWidget({super.key});MyTextWidgetState createState() => MyTextWidgetState();
}class MyTextWidgetState extends State<MyTextWidget> {Widget build(BuildContext context) {/// 使用InheritedWidget中的共享数据var txt = ShareDataWidget.of(context)!.data.toString();return Text(txt);}/// 父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。如果build中没有依赖InheritedWidget,则此回调不会被调用。void didChangeDependencies() {super.didChangeDependencies();debugPrint("count修改了");}
}class HomePage extends StatefulWidget {const HomePage({super.key});State<HomePage> createState() => HomePageState();
}class HomePageState extends State<HomePage> {int count = 0;Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Flutter Home'),),body: Center(child: ShareDataWidget(/// 使用ShareDataWidgetdata: count,child: Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[const Padding(padding: EdgeInsets.only(bottom: 20.0),/// 子widget中依赖ShareDataWidgetchild: MyTextWidget(),),ElevatedButton(child: const Text("点击增加"),/// 每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新onPressed: () => setState(() => ++count),)],),),),);}
}

2-1. 只引用数据不调用didChangeDependencies

在上面的例子中,如果我们只想在MyTextWidgetState中引用ShareDataWidget数据,但却不希望在ShareDataWidget发生变化时调用MyTextWidgetState的didChangeDependencies()方法,只需要将ShareDataWidget.of()的实现改一下即可。唯一的改动就是获取ShareDataWidget对象的方式,把dependOnInheritedWidgetOfExactType()方法换成了context.getElementForInheritedWidgetOfExactType<ShareDataWidget>().widget。他们的区别就是前者会注册依赖关系,而后者不会。

class ShareDataWidget extends InheritedWidget {const ShareDataWidget({super.key, required this.data, required Widget child}): super(child: child);final int data;static ShareDataWidget? of(BuildContext context) {// return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();return context.getElementForInheritedWidgetOfExactType<ShareDataWidget>()!.widget as ShareDataWidget;}bool updateShouldNotify(ShareDataWidget old) {return old.data != data;}
}

3. 跨组件状态共享

状态管理是一个永恒的话题。如果状态是组件私有的,则应该由组件自己管理;如果状态要跨组件共享,则该状态应该由各个组件共同的父元素来管理。常用的有事件总线EventBus(订阅者模式)和依赖注入Provider(观察者模式)。

3-1. 事件总线EventBus

//订阅者回调签名
typedef void EventCallback(arg);class EventBus {//私有构造函数EventBus._internal();//保存单例static EventBus _singleton = EventBus._internal();//工厂构造函数factory EventBus()=> _singleton;//保存事件订阅者队列,key:事件名(id),value: 对应事件的订阅者队列final _emap = Map<Object, List<EventCallback>?>();//添加订阅者void on(eventName, EventCallback f) {_emap[eventName] ??=  <EventCallback>[];_emap[eventName]!.add(f);}//移除订阅者void off(eventName, [EventCallback? f]) {var list = _emap[eventName];if (eventName == null || list == null) return;if (f == null) {_emap[eventName] = null;} else {list.remove(f);}}//触发事件,事件触发后该事件所有订阅者会被调用void emit(eventName, [arg]) {var list = _emap[eventName];if (list == null) return;int len = list.length - 1;//反向遍历,防止订阅者在回调中移除自身带来的下标错位for (var i = len; i > -1; --i) {list[i](arg);}}
}//定义一个top-level(全局)变量,页面引入该文件后可以直接使用bus
var bus = EventBus();

使用如下:

/// 页面A//监听登录事件
bus.on("login", (arg) {// ......
});/// 页面B// 登录成功后触发登录事件,页面A中订阅者会被调用
bus.emit("login", userInfo);

3-2. 依赖注入Provider

现在Flutter社区已经有很多专门用于状态管理的包了,在此列出几个相对评分比较高的:

名称描述
Provider & Scoped Model两个包都是基于InheritedWidget的,原理相似
ReduxReact生态链中Redux包的Flutter实现
MobXReact生态链中MobX包的Flutter实现
BLoCBLoC模式的Flutter实现

4. 颜色和主题

Flutter里有一个Colors类,里面定义了100多个颜色常量,颜色都是以一个int值保存。而Theme组件可以为Material APP定义主题数据。

4-1. 颜色字符串转成Color对象

  • 颜色固定
/// 直接使用整数值
Color(0xffdc380d)
  • 字符串变量
var c = "dc380d";/// 通过位运算符将Alpha设置为FF
Color(int.parse(c,radix:16)|0xFF000000)/// 通过方法将Alpha设置为FF
Color(int.parse(c,radix:16)).withAlpha(255)

4-2. 颜色亮度

假如要实现一个背景颜色和Title可以自定义的导航栏,并且背景色为深色时我们应该让Title显示为浅色;背景色为浅色时,Title 显示为深色。要实现这个功能,我们就需要来计算背景色的亮度,然后动态来确定Title的颜色。Color 类中提供了一个computeLuminance()方法,它可以返回一个[0-1]的一个值,数字越大颜色就越浅,我们可以根据它来动态确定Title的颜色。

import 'package:flutter/material.dart';/// 定义
class HomePage extends StatefulWidget {const HomePage({super.key});State<HomePage> createState() => HomePageState();
}/// 实现
class HomePageState extends State<HomePage> {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Flutter Home'),),body: const Center(child: Column(children: [NavBar(color: Colors.blue, title: '标题1'),NavBar(color: Colors.white, title: '标题2')],),),);}
}/// 定义组件
class NavBar extends StatelessWidget {final String title;final Color color;const NavBar({super.key,required this.color,required this.title,});Widget build(BuildContext context) {return Container(constraints: const BoxConstraints(minHeight: 52,minWidth: double.infinity,),decoration: BoxDecoration(color: color,boxShadow: const [// 阴影BoxShadow(color: Colors.black26,offset: Offset(0, 3),blurRadius: 3,),],),alignment: Alignment.center,child: Text(title,style: TextStyle(fontWeight: FontWeight.bold,// 根据背景色亮度来确定Title颜色color: color.computeLuminance() < 0.5 ? Colors.white : Colors.black,),));}
}

4-3. MaterialColor类

MaterialColor是实现Material Design中的颜色的类,它包含一种颜色的10个级别的渐变色。它通过"[]"运算符的索引值来代表颜色的深度,有效的索引有:50,100,200,…,900,数字越大,颜色越深。默认值是500。使用方式为Colors.blue.shade50。

  • 例子:
class HomePageState extends State<HomePage> {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Flutter Home'),),body: Center(child: Column(children: [NavBar(color: Colors.blue.shade50, title: '标题1'),NavBar(color: Colors.blue.shade100, title: '标题2'),NavBar(color: Colors.blue.shade200, title: '标题2'),NavBar(color: Colors.blue.shade300, title: '标题2'),NavBar(color: Colors.blue.shade400, title: '标题2'),NavBar(color: Colors.blue.shade500, title: '标题2'),NavBar(color: Colors.blue.shade600, title: '标题2'),NavBar(color: Colors.blue.shade700, title: '标题2'),NavBar(color: Colors.blue.shade800, title: '标题2'),NavBar(color: Colors.blue.shade900, title: '标题2')],),),);}
}

4-4. 主题

ThemeData用于保存是Material 组件库的主题数据,Material组件需要遵守相应的设计规范,而这些规范可自定义部分都定义在ThemeData中了,所以我们可以通过ThemeData来自定义应用主题。在子组件中,我们可以通过Theme.of方法来获取当前的ThemeData。(有些是不能自定义的,比如导航栏高度)。

  • 定义:
ThemeData({Brightness? brightness, // 深色还是浅色MaterialColor? primarySwatch, // 主题颜色样本,见下面介绍Color? primaryColor, // 主色,决定导航栏颜色Color? cardColor, // 卡片颜色Color? dividerColor, // 分割线颜色ButtonThemeData buttonTheme, // 按钮主题Color dialogBackgroundColor,// 对话框背景颜色String fontFamily, // 文字字体TextTheme textTheme,// 字体主题,包括标题、body等文字样式IconThemeData iconTheme, // Icon的默认样式TargetPlatform platform, // 指定平台,应用特定平台控件风格ColorScheme? colorScheme,/// ......
})

下面是一个单个页面换肤的例子,如果想要对整个应用换肤,则可以去修改MaterialApp的theme属性,例子如下:

import 'package:flutter/material.dart';/// 定义
class HomePage extends StatefulWidget {const HomePage({super.key});State<HomePage> createState() => HomePageState();
}/// 实现
class HomePageState extends State<HomePage> {var myThemeColor = Colors.teal;Widget build(BuildContext context) {ThemeData themeData = Theme.of(context);return Theme(data: ThemeData(// 用于导航栏、FloatingActionButton的背景色等primarySwatch: myThemeColor,// 用于Icon颜色iconTheme: IconThemeData(color: myThemeColor),),child: Scaffold(appBar: AppBar(title: const Text('Flutter Home'),),body: Center(child: Column(children: [const Row(mainAxisAlignment: MainAxisAlignment.center,children: [Icon(Icons.favorite),Text(" 颜色跟随主题"),],),Theme(data: themeData.copyWith(iconTheme:themeData.iconTheme.copyWith(color: Colors.black),),child: const Row(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[Icon(Icons.favorite),Text(" 颜色固定黑色")]),)],),),floatingActionButton: FloatingActionButton(// 切换主题onPressed: () {setState(() => myThemeColor = myThemeColor == Colors.teal? Colors.blue: Colors.teal);},child: const Icon(Icons.palette))));}
}

5. 异步UI更新

很多时候我们会依赖一些异步数据来动态更新UI,比如在打开一个页面时我们需要先从互联网上获取数据,在获取数据的过程中我们显示一个加载框,等获取到数据时我们再渲染页面;又比如我们想展示文件流、互联网数据接收流的进度。Flutter专门提供了FutureBuilder和StreamBuilder两个组件来快速实现这种功能。

5-1. FutureBuilder

FutureBuilder是Flutter中专门用于异步UI更新的组件,它会依赖一个Future,它会根据所依赖的Future的状态来动态构建自身。

属性描述
futureFutureBuilder依赖的Future,通常是一个异步耗时任务
initialData初始数据,用户设置默认数据
builderWidget构建器,该构建器会在Future执行的不同阶段被多次调用,第一个参数是context
builder.snapshotbuilder的参数,包含当前异步任务的状态信息及结果信息
import 'package:flutter/material.dart';/// 定义
class HomePage extends StatefulWidget {const HomePage({super.key});State<HomePage> createState() => HomePageState();
}/// 实现
class HomePageState extends State<HomePage> {Future<String> mockNetworkData() async {return Future.delayed(const Duration(seconds: 2), () => "我是从互联网上获取的数据");}Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Flutter Home'),),body: Center(child: FutureBuilder(future: mockNetworkData(),builder: (BuildContext context, AsyncSnapshot snapshot) {if (snapshot.connectionState == ConnectionState.done) {if (snapshot.hasError) {// 请求失败,显示错误return Text("Error: ${snapshot.error}");} else {// 请求成功,显示数据return Text("Contents: ${snapshot.data}");}} else {// 请求未结束,显示loadingreturn const CircularProgressIndicator();}},),));}
}

5-2. StreamBuilder

StreamBuilder是用于配合Stream来展示流上事件(数据)变化的UI组件。

例子如下:

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';/// 定义
class HomePage extends StatefulWidget {const HomePage({super.key});State<HomePage> createState() => HomePageState();
}/// 实现
class HomePageState extends State<HomePage> {/// 任务流Stream<int> myCounter() {return Stream.periodic(const Duration(seconds: 1), (i) {return i;});}/// 状态文案String str = '';Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Flutter Home'),),body: Center(child: StreamBuilder(stream: myCounter(),builder: (BuildContext context, AsyncSnapshot<int> snapshot) {if (snapshot.hasError) {str = snapshot.error.toString();}switch (snapshot.connectionState) {case ConnectionState.none:str = '没有Stream';case ConnectionState.waiting:str = '等待数据';case ConnectionState.active:str = snapshot.data.toString();case ConnectionState.done:str = '已关闭';}return Text('状态:$str');},),),floatingActionButton: FloatingActionButton(onPressed: () {}, child: const Icon(Icons.palette)));}
}

6. 对话框

对话框本质上也是UI布局,通常一个对话框会包含标题、内容,以及一些操作按钮,为此,Material库中提供了一些现成的对话框组件来用于快速的构建出一个完整的对话框。下面写一个AlertDialog做例子。

构造函数如下:

const AlertDialog({Key? key,this.title, // 对话框标题组件this.titlePadding, // 标题填充this.titleTextStyle, // 标题文本样式this.content, // 对话框内容组件this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0), // 内容的填充this.contentTextStyle,// 内容文本样式this.actions, // 对话框操作按钮组this.backgroundColor, // 对话框背景色this.elevation,// 对话框的阴影this.semanticLabel, // 对话框语义化标签(用于读屏软件)this.shape, // 对话框外形
})

使用例子如下:

import 'package:flutter/material.dart';/// 定义
class HomePage extends StatefulWidget {const HomePage({super.key});State<HomePage> createState() => HomePageState();
}/// 实现
class HomePageState extends State<HomePage> {/// 定义对话框Future<bool?> showDeleteConfirmDialog() {return showDialog<bool>(context: context,builder: (context) {return AlertDialog(title: const Text("提示"),content: const Text("您确定要删除当前文件吗?"),actions: <Widget>[TextButton(child: const Text("取消"),onPressed: () => Navigator.of(context).pop(), // 关闭对话框),TextButton(child: const Text("删除"),onPressed: () {//关闭对话框并返回trueNavigator.of(context).pop(true);},),],);},);}Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Flutter Home'),),body: const Center(child: Text('点击此处'),),floatingActionButton: FloatingActionButton(onPressed: () async {bool? deleteDialog = await showDeleteConfirmDialog();if (deleteDialog == null) {debugPrint("取消");} else {debugPrint("确认");}}, child: const Icon(Icons.palette)));}
}

本次分享就到这儿啦,我是鹏多多,如果您看了觉得有帮助,欢迎评论,关注,点赞,转发,我们下次见~

往期文章

  • 手把手教你搭建规范的团队vue项目,包含commitlint,eslint,prettier,husky,commitizen等等
  • Web Woeker和Shared Worker的使用以及案例
  • Vue2全家桶+Element搭建的PC端在线音乐网站
  • vue3+element-plus配置cdn
  • 助你上手Vue3全家桶之Vue3教程
  • 助你上手Vue3全家桶之VueX4教程
  • 助你上手Vue3全家桶之Vue-Router4教程
  • 超详细!Vue的九种通信方式
  • 超详细!Vuex手把手教程
  • 使用nvm管理node.js版本以及更换npm淘宝镜像源
  • vue中利用.env文件存储全局环境变量,以及配置vue启动和打包命令
  • 超详细!Vue-Router手把手教程

个人主页

  • CSDN
  • GitHub
  • 简书
  • 博客园
  • 掘金

这篇关于flutter学习-day13-功能型组件和状态共享的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/505759

相关文章

四种Flutter子页面向父组件传递数据的方法介绍

《四种Flutter子页面向父组件传递数据的方法介绍》在Flutter中,如果父组件需要调用子组件的方法,可以通过常用的四种方式实现,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录方法 1:使用 GlobalKey 和 State 调用子组件方法方法 2:通过回调函数(Callb

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

NFS实现多服务器文件的共享的方法步骤

《NFS实现多服务器文件的共享的方法步骤》NFS允许网络中的计算机之间共享资源,客户端可以透明地读写远端NFS服务器上的文件,本文就来介绍一下NFS实现多服务器文件的共享的方法步骤,感兴趣的可以了解一... 目录一、简介二、部署1、准备1、服务端和客户端:安装nfs-utils2、服务端:创建共享目录3、服

vue解决子组件样式覆盖问题scoped deep

《vue解决子组件样式覆盖问题scopeddeep》文章主要介绍了在Vue项目中处理全局样式和局部样式的方法,包括使用scoped属性和深度选择器(/deep/)来覆盖子组件的样式,作者建议所有组件... 目录前言scoped分析deep分析使用总结所有组件必须加scoped父组件覆盖子组件使用deep前言

基于Qt Qml实现时间轴组件

《基于QtQml实现时间轴组件》时间轴组件是现代用户界面中常见的元素,用于按时间顺序展示事件,本文主要为大家详细介绍了如何使用Qml实现一个简单的时间轴组件,需要的可以参考下... 目录写在前面效果图组件概述实现细节1. 组件结构2. 属性定义3. 数据模型4. 事件项的添加和排序5. 事件项的渲染如何使用

使用Nginx来共享文件的详细教程

《使用Nginx来共享文件的详细教程》有时我们想共享电脑上的某些文件,一个比较方便的做法是,开一个HTTP服务,指向文件所在的目录,这次我们用nginx来实现这个需求,本文将通过代码示例一步步教你使用... 在本教程中,我们将向您展示如何使用开源 Web 服务器 Nginx 设置文件共享服务器步骤 0 —

Python使用pysmb库访问Windows共享文件夹的详细教程

《Python使用pysmb库访问Windows共享文件夹的详细教程》本教程旨在帮助您使用pysmb库,通过SMB(ServerMessageBlock)协议,轻松连接到Windows共享文件夹,并列... 目录前置条件步骤一:导入必要的模块步骤二:配置连接参数步骤三:实例化SMB连接对象并尝试连接步骤四:

Linux使用粘滞位 (t-bit)共享文件的方法教程

《Linux使用粘滞位(t-bit)共享文件的方法教程》在Linux系统中,共享文件是日常管理和协作中的常见任务,而粘滞位(StickyBit或t-bit)是实现共享目录安全性的重要工具之一,本文将... 目录文件共享的常见场景基础概念linux 文件权限粘滞位 (Sticky Bit)设置共享目录并配置粘

鸿蒙开发搭建flutter适配的开发环境

《鸿蒙开发搭建flutter适配的开发环境》文章详细介绍了在Windows系统上如何创建和运行鸿蒙Flutter项目,包括使用flutterdoctor检测环境、创建项目、编译HAP包以及在真机上运... 目录环境搭建创建运行项目打包项目总结环境搭建1.安装 DevEco Studio NEXT IDE

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert