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

相关文章

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

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

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

hdu1565(状态压缩)

本人第一道ac的状态压缩dp,这题的数据非常水,很容易过 题意:在n*n的矩阵中选数字使得不存在任意两个数字相邻,求最大值 解题思路: 一、因为在1<<20中有很多状态是无效的,所以第一步是选择有效状态,存到cnt[]数组中 二、dp[i][j]表示到第i行的状态cnt[j]所能得到的最大值,状态转移方程dp[i][j] = max(dp[i][j],dp[i-1][k]) ,其中k满足c

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss