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

相关文章

Vue项目的甘特图组件之dhtmlx-gantt使用教程和实现效果展示(推荐)

《Vue项目的甘特图组件之dhtmlx-gantt使用教程和实现效果展示(推荐)》文章介绍了如何使用dhtmlx-gantt组件来实现公司的甘特图需求,并提供了一个简单的Vue组件示例,文章还分享了一... 目录一、首先 npm 安装插件二、创建一个vue组件三、业务页面内 引用自定义组件:四、dhtmlx

Vue ElementUI中Upload组件批量上传的实现代码

《VueElementUI中Upload组件批量上传的实现代码》ElementUI中Upload组件批量上传通过获取upload组件的DOM、文件、上传地址和数据,封装uploadFiles方法,使... ElementUI中Upload组件如何批量上传首先就是upload组件 <el-upl

MySQL 中的服务器配置和状态详解(MySQL Server Configuration and Status)

《MySQL中的服务器配置和状态详解(MySQLServerConfigurationandStatus)》MySQL服务器配置和状态设置包括服务器选项、系统变量和状态变量三个方面,可以通过... 目录mysql 之服务器配置和状态1 MySQL 架构和性能优化1.1 服务器配置和状态1.1.1 服务器选项

Vue3中的动态组件详解

《Vue3中的动态组件详解》本文介绍了Vue3中的动态组件,通过`component:is=动态组件名或组件对象/component`来实现根据条件动态渲染不同的组件,此外,还提到了使用`markRa... 目录vue3动态组件动态组件的基本使用第一种写法第二种写法性能优化解决方法总结Vue3动态组件动态

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

java父子线程之间实现共享传递数据

《java父子线程之间实现共享传递数据》本文介绍了Java中父子线程间共享传递数据的几种方法,包括ThreadLocal变量、并发集合和内存队列或消息队列,并提醒注意并发安全问题... 目录通过 ThreadLocal 变量共享数据通过并发集合共享数据通过内存队列或消息队列共享数据注意并发安全问题总结在 J

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

Java实现状态模式的示例代码

《Java实现状态模式的示例代码》状态模式是一种行为型设计模式,允许对象根据其内部状态改变行为,本文主要介绍了Java实现状态模式的示例代码,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来... 目录一、简介1、定义2、状态模式的结构二、Java实现案例1、电灯开关状态案例2、番茄工作法状态案例

通过prometheus监控Tomcat运行状态的操作流程

《通过prometheus监控Tomcat运行状态的操作流程》文章介绍了如何安装和配置Tomcat,并使用Prometheus和TomcatExporter来监控Tomcat的运行状态,文章详细讲解了... 目录Tomcat安装配置以及prometheus监控Tomcat一. 安装并配置tomcat1、安装

Linux之进程状态&&进程优先级详解

《Linux之进程状态&&进程优先级详解》文章介绍了操作系统中进程的状态,包括运行状态、阻塞状态和挂起状态,并详细解释了Linux下进程的具体状态及其管理,此外,文章还讨论了进程的优先级、查看和修改进... 目录一、操作系统的进程状态1.1运行状态1.2阻塞状态1.3挂起二、linux下具体的状态三、进程的