IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 【Flutter 组件】001-关于 Widget 的一切 -> 正文阅读

[移动开发]【Flutter 组件】001-关于 Widget 的一切

【Flutter 组件】001-关于 Widget 的一切

一、概述

1、Widget 基本概述

Flutter 中的 Widget 相当于 Android 里的 View,iOS 里的 UIView。

  • Flutter 中几乎所有的对象都是一个 **widget **;* Widget 不仅可以表示 UI 元素,还可以表示一些功能性的组件,如用于手势检测的 GestureDetector 、用于APP主题数据传递的 Theme 等等;* 我们将 Widget 统称为组件。> 界面刷新机制,类似 React

当 Widget 状态发生变化,需要更新界面时,框架会先计算从上一个状态转换到下一个状态所需的最小更改,然后再去刷新界面。

2、Flutter Framework 里的 Widget

架构图

说明

Framework 里面有一层是 Widgets,在 Widgets 层下面,有:

  • Rendering(渲染层)
  • Animation、Painting、Gestures(动画、绘制、手势)
  • Foundation(基础库层)

Widgets 层下面平常使用较少,也比较复杂,常用 Widgets 层上面的 Material 和 Cupertino。

摘录:Material & Cupertino 指的 Widget 的风格是 Material 或 Cupertino 。Flutter 为了减轻开发人员的工作量,实现了两种不同风格的组件:Material 和 Cupertino 。**Material 用于 Android,Cupertino 用于 iOS。**有了这些组件,开发人员不需要再做额外的工作,就可以让 Flutter 的 UI 风格适应不同的平台,让 Flutter UI 获得和 Native UI 一样的使用体验。

3、根 Widget

我们常用的 MaterialApp 就是根(Root)Widget ,Flutter会默认把 根Widget 充满屏幕。

根 Widget 只能是下面三个:

  • WidgetsAppWidgetsApp 是可以自定义风格的 根Widget。* MaterialAppMaterialApp 是在 WidgetsApp 上添加了很多 material-design 的功能,是 Material Design 风格的 根Widget。* CupertinoAppCupertinoApp 也是基于 WidgetsApp 实现的 iOS 风格的 根Widget。MaterialApp 是最常用的,也是功能最完善的,且经常与 Scaffold 一起使用。

二、Widget 类

1、Widget 的功能

描述一个UI元素的配置信息

就是说, Widget 其实并不是表示最终绘制在设备屏幕上的显示元素,所谓的配置信息就是 Widget 接收的参数,比如对于 Text 来讲,文本的内容、对齐方式、文本样式都是它的配置信息。

Widget 描述了他们的视图在给定其当前配置和状态时应该看起来像什么。

2、Widget 类

源码

@immutable
abstract class Widget extends DiagnosticableTree {const Widget({ this.key });final Key? key;@protected@factoryElement createElement();@overrideString toStringShort() {final String type = objectRuntimeType(this, 'Widget');return key == null ? type : '$type-$key';}@overridevoid debugFillProperties(DiagnosticPropertiesBuilder properties) {super.debugFillProperties(properties);properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;}@override@nonVirtualbool operator ==(Object other) => super == other;@override@nonVirtualint get hashCode => super.hashCode;static bool canUpdate(Widget oldWidget, Widget newWidget) {return oldWidget.runtimeType == newWidget.runtimeType&& oldWidget.key == newWidget.key;}static int _debugConcreteSubtype(Widget widget) {return widget is StatefulWidget ? 1 : widget is StatelessWidget ? 2 : 0;}
} 

说明

Widget 类本身是一个抽象类,其中最核心的就是定义了 createElement() 接口。我们在开发中一般不直接继承 Widget 而是 Widget 的子类:StatelessWidgetStatefulWidget来间接继承widget类。

  • @immutable :代表 Widget 是不可变的,这会限制 Widget 中定义的属性(即配置信息)必须是不可变的(final)为什么?因为 Flutter 中的属性发生变化会导致重新构建 Widget 树,即重新创建新的 Widget 实例来替换旧的 Widget 实例,所以允许 Widget 的属性变化是没有意义的,因为一旦 Widget 自己的属性变了自己就会被替换。* widget 类继承自 DiagnosticableTreeDiagnosticableTree即“诊断树”,主要作用是提供调试信息。* Key : 这个key属性类似于 React/Vue 中的key,主要的作用是决定是否在下一次 build复用旧的 widget ,决定的条件在canUpdate()方法中。* createElement() :正如前文所述“一个 widget 可以对应多个Element”;Flutter 框架在构建UI树时,会先调用此方法生成对应节点的Element对象。 此方法是 Flutter 框架隐式调用的,在我们开发过程中基本不会调用到。* debugFillProperties(...) 复写父类的方法,主要是设置诊断树的一些特性。* canUpdate(...) 是一个静态方法,它主要用于在 widget 树重新 build复用旧的 widget ,其实具体来说,应该是:是否用新的 widget 对象去更新旧 UI 树上所对应的 Element 对象的配置;通过其源码我们可以看到,只要 newWidgetoldWidgetruntimeTypekey 同时相等时就会用 new widget 去更新 Element 对象的配置,否则就会创建新的 Element 。### Widget 的标识符:Key

diff 简介: 因为 Flutter 采用的是 react-style 的框架,每次刷新 UI 的时候,都会重新构建新的 Widget树,然后和之前的 Widget树 进行对比计算出变化的部分,这个计算过程叫做 diff 。

标识符: 在 diff 过程中,如果能提前知道哪些 Widget 没有变化,无疑会提高 diff 的性能,这时候就需要使用到标识符

为了在 diff 过程中,知道 Widget 有没有变化,就需要给 Widget 添加一个唯一的标识符,然后在 Widget树 的 diff 过程中,查看刷新前后的 Widget树 有没有相同标识符的 Widget,如果标识符相同,则说明 Widget 没有变化否则说明 Widget 有变化

假设 UI 刷新前,Widget树 是 A,在 A 里有一个标识符为 a 的 Widget,在 UI 刷新后,重建的 Widget树 是 B,如果 B 里还有标识符为 a 的 Widget,则说明这个 Widget 没变,但是如果 B 里没有标识符为 a 的 Widget,那么说明这个 Widget 发生了变化。

这个标识符在 Flutter 中就是 Key所有 Widget 都有 Key 这一个属性。

Flutter 中如何在 diff 过程中判断哪些 Widget 没有变化

稍微有些复杂,有两种情况:

  • 默认情况下( Widget 没有设置 Key)当没有给 Widget 设置 Key 时,Flutter 会根据 Widget 的 runtimeType 和显示顺序是否相同来判断 Widget 是否有变化。runtimeType 是 Widget 的类型,例如 Text 和 RaisedButton 就是不同的类型。* Widget 有 Key当给 Widget 设置了 Key 时,Flutter 是根据 Key 和 runtimeType 是否相同来判断 Widget 是否有变化。### Key 的分类

Key 总共分为两类:

1.Local Key(局部Key)
2.Global Key(全局Key)

1. Local Key(局部Key)

在有相同父级的 Widget 中,Key 必须是唯一的,这样的 Key 叫做 局部 Key。

局部Key 在 Flutter 中对应的抽象类是 LocalKey。LocalKey 有不同的实现,主要的区别就是使用什么值来作为 Key 的值:

  • ObjectKey将对象作为 Key 的值。* ValueKey使用特定类型的值来作为 Key 的值。* UniqueKey使用 UniqueKey 自己的对象作为 Key 的值,所以只与自身相等,称为 唯一 Key。2.Global Key(全局Key)

全局 Key 是在整个 APP 中唯一的 Key

全局 Key 在 Flutter 中对应的抽象类GlobalKey。GlobalKey 有不同的实现,主要区别是使用的场景不同:

  • LabeledGlobalKeyLabeledGlobalKey 用于调试,不会用来比较 Widget 是否有变化。* GlobalObjectKey将对象作为 Global Key 的值。### key 的使用

一般不用,页面复杂时用。

一般情况下我们需要使 Key,但是当页面比较复杂时,就需要使 Key 去提升渲染性能。

三、Flutter中的四棵树

1、概述

Flutter 中的 Widget 是用来秒数 UI 元素的配置信息的,那么真正的布局绘制由谁来完成呢?

Flutter 框架的的处理流程:

根据 Widget 树生成 Element 树,根据 Element 树生成 Render 树,根据 Render 树生成 Layer 树。

1.根据 Widget 树生成一个 Element 树,Element 树中的节点都继承自 Element 类。
2.根据 Element 树生成 Render 树(渲染树),渲染树中的节点都继承自RenderObject 类。
3.根据渲染树生成 Layer 树,然后上屏显示,Layer 树中的节点都继承自 Layer 类。

真正的布局和渲染逻辑在 Render 树中,Element 是 Widget 和 RenderObject 的粘合剂,可以理解为一个中间代理。

2、举个例子

一个 Widget 树示例:

Container( // 一个容器 widgetcolor: Colors.blue, // 设置容器背景色child: Row( // 可以将子widget沿水平方向排列children: [Image.network('https://www.example.com/1.png'), // 显示图片的 widgetconst Text('A'),],),
); 

说明

如上 Container 设置了背景色,Container 内部会创建一个新的 ColoredBox 来填充背景,相关逻辑如下:

// 源代码位置:D:/MySoft/Flutter/SDK/flutter/packages/flutter/lib/src/widgets/container.dart:403
if (color != null) {
	current = ColoredBox(color: color!, child: current);
} 

代码演示

代码

import 'package:flutter/material.dart';

void main() {runApp(const MyApp());
}

class MyApp extends StatelessWidget {const MyApp({super.key});// 此处返回的是根组件@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(title: 'Flutter Demo Home Page'),);}
}

class MyHomePage extends StatefulWidget {const MyHomePage({super.key, required this.title});final String title;@overrideState<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(widget.title),),body: Container( // 一个容器 widgetcolor: Colors.blue, // 设置容器背景色child: Row( // 可以将子widget沿水平方向排列children: [Image.network('https://p3-passport.byteimg.com/img/user-avatar/f755e80a8a455a4d9de36f5f096784e6~100x100.awebp'), // 显示图片的 widgetconst Text('訾博', style: TextStyle(color: Colors.white)), // 显示文本的 widget],),), // This trailing comma makes auto-formatting nicer for build methods.);}
} 

图示

三棵树图示

注意:

1.三棵树中,Widget 和 Element 是一一对应的,但并不和 RenderObject 一一对应。比如 StatelessWidgetStatefulWidget 都没有对应的 RenderObject。
2.渲染树在上屏前会生成一棵 Layer 树,前期只需要记住以上三棵树就行

四、StatelessWidget

1、概述

无状态的组件!

StatelessWidget 相对比较简单,它继承自widget类,重写了createElement()方法:

@override
StatelessElement createElement() => StatelessElement(this); 

StatelessElement 间接继承自 Element 类,与 StatelessWidget 相对应(作为其配置数据)。

StatelessElement 用于不需要维护状态的场景,它通常在 build 方法中通过嵌套其他 widget 来构建 UI,在构建过程中会递归的构建其嵌套的 widget 。

2、一个简单的示例

继承 StatelessElement 类实现自定义组件

import 'package:flutter/material.dart';

class Echo extends StatelessWidget {const Echo({Key? key,required this.text,this.backgroundColor = Colors.grey,}):super(key:key);final String text;final Color backgroundColor;@overrideWidget build(BuildContext context) {return Center(child: Container(color: backgroundColor,child: Text(text),),);}
} 

使用自定义的类

import 'package:flutter/material.dart';
import 'package:study/echo.dart';

// 省略其他内容......

class _MyHomePageState extends State<MyHomePage> {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(widget.title),),body: const Echo(text: '訾博', backgroundColor: Colors.red,),);}
} 

运行结果

3、Context

概述

build方法有一个context参数,它是 BuildContext 类的一个实例,表示当前 widget 在 widget 树中的上下文,每一个 widget 都会对应一个 context 对象(因为每一个 widget 都是 widget 树上的一个节点)。

实际上,context 是当前 widget 在 widget 树中位置中执行”相关操作“的一个句柄(handle),比如它提供了从当前 widget 开始向上遍历 widget 树以及按照 widget 类型查找父级 widget 的方法。

获取父级 widget 代码示例

重视这段代码的写法!

class ContextRoute extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("Context测试"),),body: Container(// 主义这个 Builder ,一般我们返回的是一个 widget 但有时需要逻辑计算,就可以使用这个 Builderchild: Builder(builder: (context) {// 在 widget 树中向上查找最近的父级 `Scaffold` widget Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();// 直接返回 AppBar 的 title, 此处实际上是 Text("Context测试")return (scaffold.appBar as AppBar).title;}),),);}
} 

五、StatefulWidget

1、概述

概述

有状态的组件!

StatelessWidget 一样,StatefulWidget 也是继承自 widget 类,并重写了 createElement() 方法,不同的是返回的 Element 对象并不相同;另外 StatefulWidget 类中添加了一个新的接口createState()

StatefulWidget 类

abstract class StatefulWidget extends Widget {const StatefulWidget({ super.key });@overrideStatefulElement createElement() => StatefulElement(this);@protected@factoryState createState();
} 

说明

  • StatefulElement 间接继承自 Element 类,与 StatefulWidget 相对应(作为其配置数据)。StatefulElement 中可能会多次调用 createState() 来创建状态(State)对象。* createState() 用于创建和 StatefulWidget 相关的状态,它在 StatefulWidget 的生命周期中可能会被多次调用。> 例如,当一个 StatefulWidget 同时插入到 widget 树的多个位置时,Flutter 框架就会调用该方法为每一个位置生成一个独立的 State 实例,其实,**本质上就是一个 StatefulElement 对应一个 State 实例。**2、State 状态

简介

State 是其对用的 StatefulWidget 要维护的状态:

一个 StatefulWidget 类会对应一个 State 类,State 表示与其对应的 StatefulWidget 要维护的状态,State 中的保存的状态信息可以:

1.在 widget 构建时可以被同步读取。
2.在 widget 生命周期中可以被改变,当State被改变时,可以手动调用setState()方法通知 Flutter 框架状态发生改变,Flutter 框架在收到消息后,会重新调用其 build 方法重新构建 widget 树,从而达到更新 UI 的目的。

State 中有两个常用属性:

1.widget,它表示与该 State 实例关联的 widget 实例,由Flutter 框架动态设置。注意,这种关联并非永久的,因为在应用生命周期中,UI 树上的某一个节点的 widget 实例在重新构建时可能会变化,但 State 实例只会在第一次插入到树中时被创建,当在重新构建时,如果 widget 被修改了,Flutter 框架会动态设置 State. widget 为新的 widget 实例。
2.context,StatefulWidget 对应的 BuildContext,作用同 StatelessWidget 的 BuildContext。

State生命周期

图示

说明

  • initState当 widget 第一次插入到 widget 树时会被调用,对于每一个 State 对象,Flutter 框架只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。不能在该回调中调用BuildContext.dependOnInheritedWidgetOfExactType(该方法用于在 widget 树上获取离当前 widget 最近的一个父级InheritedWidget),原因是在初始化完成后, widget 树中的InheritFrom widget也可能会发生变化,所以正确的做法应该在在build()方法或didChangeDependencies()中调用它。> 当 widget 第一次插入到 widget 树时会被调用,只会调用一次,常用于初始化状态。* didChangeDependencies():当 State 对象的依赖发生变化时会被调用;例如:在之前 build() 中包含了一个InheritedWidget ,然后在之后的 build()Inherited widget 发生了变化,那么此时 InheritedWidget 的子 widget 的didChangeDependencies()回调都会被调用。典型的场景是当系统语言 Locale 或应用主题改变时,Flutter 框架会通知 widget 调用此回调。需要注意,组件第一次被创建后挂载的时候(包括重创建)对应的 didChangeDependencies 也会被调用。* build():此回调读者现在应该已经相当熟悉了,它主要是用于构建 widget 子树的,会在如下场景被调用:1.在调用 initState() 之后;2.在调用 didUpdateWidget() 之后;3.在调用 setState() 之后;4.在调用 didChangeDependencies() 之后;5.在 State 对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其他位置之后。
  • reassemble():此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在 Release 模式下永远不会被调用。* didUpdateWidget ():在 widget 重新构建时,Flutter 框架会调用 widget.canUpdate 来检测 widget 树中同一位置的新旧节点,然后决定是否需要更新,如果 widget.canUpdate 返回 true 则会调用此回调。正如之前所述,widget.canUpdate会在新旧 widget 的 keyruntimeType 同时相等时会返回true,也就是说在在新旧 widget 的key和runtimeType同时相等时didUpdateWidget()就会被调用。* deactivate():当 State 对象从树中被移除时,会调用此回调。在一些场景下,Flutter 框架会将 State 对象重新插到树中,如包含此 State 对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey 来实现)。如果移除后没有重新插入到树中则紧接着会调用 dispose() 方法。* dispose()当 State 对象从树中被永久移除时调用;通常在此回调中释放资源。3、在 widget 树中获取 State 对象

由于 StatefulWidget 的的具体逻辑都在其 State 中,所以很多时候,我们需要获取 StatefulWidget 对应的State 对象来调用一些方法,比如 Scaffold 组件对应的状态类 ScaffoldState 中就定义了打开 SnackBar(路由页底部提示条)的方法。我们有两种方法在子 widget 树中获取父级 StatefulWidget 的State 对象。

通过 Context 获取

context 对象有一个 findAncestorStateOfType() 方法,该方法可以从当前节点沿着 widget 树向上查找指定类型的 StatefulWidget 对应的 State 对象。

代码示例

class GetStateObjectRoute extends StatefulWidget {const GetStateObjectRoute({Key? key}) : super(key: key);@overrideState<GetStateObjectRoute> createState() => _GetStateObjectRouteState();
}

class _GetStateObjectRouteState extends State<GetStateObjectRoute> {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("子树中获取 State 对象"),),body: Center(child: Column(children: [Builder(builder: (context) {return ElevatedButton(onPressed: () {// 查找父级【最近的】 Scaffold 对应的 ScaffoldState 对象ScaffoldState _state = context.findAncestorStateOfType<ScaffoldState>()!;// 打开抽屉菜单_state.openDrawer();},child: Text('打开抽屉菜单'),);}),],),),drawer: Drawer(),);}
} 

约定

如果希望暴露状态,则提供一个静态的 of 方法来获取其 State 状态,反之则不提供!

一般来说,如果 StatefulWidget 的状态是私有的(不应该向外部暴露),那么我们代码中就不应该去直接获取其 State 对象;如果StatefulWidget的状态是希望暴露出的(通常还有一些组件的操作方法),我们则可以去直接获取其 State 对象。但是通过 context.findAncestorStateOfType 获取 StatefulWidget 的状态的方法是通用的,我们并不能在语法层面指定 StatefulWidget 的状态是否私有,所以在 Flutter 开发中便有了一个默认的约定:**如果 StatefulWidget 的状态是希望暴露出的,应当在 StatefulWidget 中提供一个of 静态方法来获取其 State 对象,开发者便可直接通过该方法来获取;如果 State 不希望暴露,则不提供of 方法。**这个约定在 Flutter SDK 里随处可见。所以,上面示例中的Scaffold也提供了一个of方法,我们其实是可以直接调用它的:

代码示例 1

Builder(builder: (context) {return ElevatedButton(onPressed: () {// 直接通过 of 静态方法来获取ScaffoldStateScaffoldState _state=Scaffold.of(context);// 打开抽屉菜单_state.openDrawer();},child: Text('打开抽屉菜单2'),);
}), 

代码示例 2

Builder(builder: (context) {return ElevatedButton(onPressed: () {ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("我是SnackBar")),);},child: Text('显示SnackBar'),);
}), 

通过 GlobalKey 获取

开销较大,如果有其他可选方案,应尽量避免使用它!

GlobalKey 不能重复!

Flutter还有一种通用的获取 State 对象的方法——通过 GlobalKey 来获取! 步骤分两步:

1.给目标StatefulWidget添加GlobalKey````//定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储static GlobalKey<ScaffoldState> _globalKey= GlobalKey();...Scaffold(key: _globalKey , //设置key...) ```2.通过GlobalKey来获取State对象```_globalKey.currentState.openDrawer() ```GlobalKey 是 Flutter 提供的一种在整个 App 中引用 element 的机制。如果一个 widget 设置了 GlobalKey,那么我们便可以通过globalKey.currentWidget 获得该 widget 对象、globalKey.currentElement来获得 widget 对应的 element 对象,如果当前 widget 是StatefulWidget,则可以通过 globalKey.currentState` 来获得该 widget 对应的 state 对象。

注意:使用 GlobalKey 开销较大,如果有其他可选方案,应尽量避免使用它。另外,同一个 GlobalKey 在整个 widget 树中必须是唯一的,不能重复。

六、自定义 Widget 的三种方式

1、Flutter 自定义 Widget 的三种方式

  • 通过继承 Widget 来修改和扩展它的功能;* 通过组合 Widget 来扩展功能;* 使用 CustomPaint 绘制自定义 Widget。> CustomPaint 继承自 SingleChildRenderObjectWidget这几种方式都有各自的优势和特点,相对来说 CustomPaint 绘制实现自定义是这里面比较复杂的一种自定义 Widget 方式。Flutter 中的很多基础 Widget 也是通过继承 Widget 进行扩展形成新的 Widget 或者是自己绘制 Widget。

2、通过继承实现自定义

概述

首先我们看下通过 Widget 的继承来实现自定义 Widget 组件。这种例子在 Flutter 中不在少数,例如:NetworkImage 和 AssetImage 都是继承 ImageProvider 来实现不同场景功能的、Center 是继承自 Align 来实现的等等。所以我们可以根据具体的使用场景、特点来选择基础 Widget 进行继承,从而实现我们想要的功能。

一个官方的 Dialog 例子

// 继承自StatelessWidget
class Dialog extends StatelessWidget {// 构造方法,设置传参const Dialog({Key key,this.backgroundColor,this.elevation,this.insetAnimationDuration = const Duration(milliseconds: 100),this.insetAnimationCurve = Curves.decelerate,this.shape,this.child,}) : super(key: key);// 设置属性final Color backgroundColor;final double elevation;final Duration insetAnimationDuration;final Curve insetAnimationCurve;final ShapeBorder shape;final Widget child;static const RoundedRectangleBorder _defaultDialogShape =RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0)));static const double _defaultElevation = 24.0;// 构建布局样式@overrideWidget build(BuildContext context) {final DialogTheme dialogTheme = DialogTheme.of(context);// 具体构建布局样式return AnimatedPadding(padding: MediaQuery.of(context).viewInsets + const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0),duration: insetAnimationDuration,curve: insetAnimationCurve,child: MediaQuery.removeViewInsets(removeLeft: true,removeTop: true,removeRight: true,removeBottom: true,context: context,child: Center(child: ConstrainedBox(constraints: const BoxConstraints(minWidth: 280.0),child: Material(color: backgroundColor ?? dialogTheme.backgroundColor ?? Theme.of(context).dialogBackgroundColor,elevation: elevation ?? dialogTheme.elevation ?? _defaultElevation,shape: shape ?? dialogTheme.shape ?? _defaultDialogShape,type: MaterialType.card,child: child,),),),),);}
} 

我们继承 Dialog 来实现一个加载中的对话框

import 'package:flutter/material.dart';

// 继承我们的Dialog组件,这样它就具有Dialog的一些特性和方法属性
class LoadingDialog extends Dialog {String text;// 建立构造方法,传递参数LoadingDialog({Key key, @required this.text}) : super(key: key);@overrideWidget build(BuildContext context) {// 具体构建逻辑return Material(type: MaterialType.transparency,child: Center(child: SizedBox(width: 120.0,height: 120.0,child: Container(decoration: ShapeDecoration(color: Color(0xffffffff),shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0),),),),child: Column(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[CircularProgressIndicator(),Padding(padding: const EdgeInsets.only(top: 20.0,),child: Text(text,style: TextStyle(fontSize: 12.0),),),],),),),),);}
}

// 调用使用的地方
class CustomWidgetSamples extends StatefulWidget {@overrideState<StatefulWidget> createState() {return CustomWidgetSamplesState();}
}

class CustomWidgetSamplesState extends State<CustomWidgetSamples> {@overridevoid initState() {super.initState();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('CustomWidget'), primary: true),body: Container(child: Align(alignment: Alignment.center,// 构造并传递参数child: LoadingDialog(text: '加载中...'),),));}
}

// 我们只需传递我们的text参数即可
LoadingDialog(text: '加载中...'), 

3、通过组合实现自定义

概述

Widget 组合,顾名思义,就是将各种 Flutter 的基础 Widget,进行不同的选择、组合拼装,来实现一个可以满足我们需求的、新的 Widget。Flutter 的基础 Widget 中,也有很多是通过组合来实现的。

自定义 ToolBar 示例

// 自定义一个ToolBar
import 'package:flutter/material.dart';

class ToolBar extends StatefulWidget implements PreferredSizeWidget {// 构造方法,设置传递参数ToolBar({@required this.onTap}) : assert(onTap != null);// 属性参数,点击回调final GestureTapCallback onTap;@overrideState createState() {return ToolBarState();}// AppBar需要实现 PreferredSizeWidget@overrideSize get preferredSize {return Size.fromHeight(56.0);}
}

class ToolBarState extends State<ToolBar> {@overrideWidget build(BuildContext context) {// 设置布局return SafeArea(top: true,child: Container(color: Colors.blue,child: Row(children: <Widget>[Icon(Icons.menu,color: Colors.white,size: 39,),Expanded(child: Container(color: Colors.white,padding: EdgeInsets.all(5),margin: EdgeInsets.all(5),child: Text('搜索...',style: TextStyle(fontSize: 18),),),),GestureDetector(onTap: this.widget.onTap,child: Icon(Icons.photo_camera,color: Colors.white,size: 39,),)],),),);}
}

// 调用自定义ToolBar
class CustomWidgetSamples extends StatefulWidget {@overrideState<StatefulWidget> createState() {return CustomWidgetSamplesState();}
}

class CustomWidgetSamplesState extends State<CustomWidgetSamples> {@overridevoid initState() {super.initState();}@overrideWidget build(BuildContext context) {return Scaffold(// 设置自定义ToolBarappBar:ToolBar(onTap: () {print('click');},),primary: true,body: Column(children: <Widget>[Container(child: Align(alignment: Alignment.center,child: LoadingDialog(text: '加载中...'),),)],));}
} 

4、通过 RenderObject 自定义

通过 CustomPaint 绘制 Widget

概述

StatelessWidgetStatefulWidget 都是用于组合其他组件的,它们本身没有对应的 RenderObject。Flutter 组件库中的很多基础组件都不是通过 StatelessWidgetStatefulWidget 来实现的,比如 Text 、Column、Align等,就好比搭积木StatelessWidgetStatefulWidget 可以将积木搭成不同的样子,但前提是得有积木,而这些积木都是通过自定义 RenderObject 来实现的。实际上Flutter 最原始的定义组件的方式就是通过定义 RenderObject 来实现,而StatelessWidgetStatefulWidget 只是提供的两个帮助类。

一个示例

class CustomWidget extends LeafRenderObjectWidget{@overrideRenderObject createRenderObject(BuildContext context) {// 创建 RenderObjectreturn RenderCustomObject();}@overridevoid updateRenderObject(BuildContext context, RenderCustomObjectrenderObject) {// 更新 RenderObjectsuper.updateRenderObject(context, renderObject);}
}

class RenderCustomObject extends RenderBox{@overridevoid performLayout() {// 实现布局逻辑}@overridevoid paint(PaintingContext context, Offset offset) {// 实现绘制}
} 

如果组件不会包含子组件,则我们可以直接继承自 LeafRenderObjectWidget ,它是 RenderObjectWidget 的子类,而 RenderObjectWidget 继承自 Widget ,我们可以看一下它的实现:

abstract class LeafRenderObjectWidget extends RenderObjectWidget {const LeafRenderObjectWidget({ Key? key }) : super(key: key);@overrideLeafRenderObjectElement createElement() => LeafRenderObjectElement(this);
} 

很简单,就是帮 widget 实现了createElement 方法,它会为组件创建一个 类型为 LeafRenderObjectElement 的 Element 对象。如果自定义的 widget 可以包含子组件,则可以根据子组件的数量来选择继承 SingleChildRenderObjectWidget 或 MultiChildRenderObjectWidget,它们也实现了createElement() 方法,返回不同类型的 Element 对象。

然后我们重写了 createRenderObject 方法,它是 RenderObjectWidget 中定义方法,该方法被组件对应的 Element 调用(构建渲染树时)用于生成渲染对象。我们的主要任务就是来实现 createRenderObject 返回的渲染对象类,本例中是 RenderCustomObject 。updateRenderObject 方法是用于在组件树状态发生变化但不需要重新创建 RenderObject 时用于更新组件渲染对象的回调

RenderCustomObject 类是继承自 RenderBox,而 RenderBox 继承自 RenderObject,我们需要在 RenderCustomObject 中实现布局、绘制、事件响应等逻辑。

CustomPaint 绘制

CustomPaint 的构造方法

const CustomPaint({Key key,// CustomPainter背景this.painter,// CustomPainter前景画笔this.foregroundPainter,// 画布尺寸,this.size = Size.zero,// 是否是复杂的绘制,Flutter会设置一些缓存优化策略this.isComplex = false,// 下一帧是否会改变this.willChange = false,// 子元素,可以为空Widget child,}) 

绘制的核心就是自定义 CustomPainter,我们简单看下 CustomPainter 里面的方法结构:

class Sky extends CustomPainter {// 绘制方法@overridevoid paint(Canvas canvas, Size size) {// canvas为画布// size为画布大小}// 刷新布局时是否重绘,可以根据实际情况进行返回值@overridebool shouldRepaint(Sky oldDelegate) => false;

}

 // Canvas和其他平台的Canvas功能和作用基本一样,包含很多绘制方法void save() native 'Canvas_save';void saveLayer(Rect bounds, Paint paint);void _saveLayerWithoutBounds(List<dynamic> paintObjects, ByteData paintData)native 'Canvas_saveLayerWithoutBounds';void restore() native 'Canvas_restore';int getSaveCount() native 'Canvas_getSaveCount';void translate(double dx, double dy) native 'Canvas_translate';void scale(double sx, [double sy]) => _scale(sx, sy ?? sx);void rotate(double radians) native 'Canvas_rotate';void skew(double sx, double sy) native 'Canvas_skew';void transform(Float64List matrix4);void clipRect(Rect rect, { ClipOp clipOp: ClipOp.intersect, bool doAntiAlias = true });void clipRRect(RRect rrect, {bool doAntiAlias = true});void clipPath(Path path, {bool doAntiAlias = true});void drawColor(Color color, BlendMode blendMode);void drawLine(Offset p1, Offset p2, Paint paint);void drawPaint(Paint paint);void drawRect(Rect rect, Paint paint);void drawRRect(RRect rrect, Paint paint);void drawDRRect(RRect outer, RRect inner, Paint paint);void drawOval(Rect rect, Paint paint);void drawCircle(Offset c, double radius, Paint paint);void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint);void drawPath(Path path, Paint paint);void drawImage(Image image, Offset p, Paint paint);void drawImageRect(Image image, Rect src, Rect dst, Paint paint);void drawImageNine(Image image, Rect center, Rect dst, Paint paint);void drawPicture(Picture picture);void drawParagraph(Paragraph paragraph, Offset offset);void drawPoints(PointMode pointMode, List<Offset> points, Paint paint);void drawRawPoints(PointMode pointMode, Float32List points, Paint paint);void drawVertices(Vertices vertices, BlendMode blendMode, Paint paint);void drawAtlas(Image atlas, List<RSTransform> transforms, List<Rect> rects, List<Color> colors, BlendMode blendMode, Rect cullRect, Paint paint);void drawRawAtlas(Image atlas,Float32List rstTransforms,Float32List rects,Int32List colors,BlendMode blendMode,Rect cullRect,Paint paint);void drawShadow(Path path, Color color, double elevation, bool transparentOccluder); 

在进行 Canvas 画布绘制时,我们就需要画笔 Paint,我们需要创建相应的画笔来绘制到 Canvas 上。Paint 画笔也有很多可以设置的属性,常用的有:

color:画笔颜色
style:绘制模式,画线 or 充满
maskFilter:绘制完成,还没有被混合到布局上时,添加的遮罩效果,比如blur效果
strokeWidth:线条宽度
strokeCap:线条结束时的绘制样式
shader:着色器,一般用来绘制渐变效果或ImageShader
... ...

// 可以这样使用
Paint myPaint = Paint()..color = Colors.blueAccent // 画笔颜色..strokeCap = StrokeCap.round // 画笔笔触类型..isAntiAlias = true // 是否启动抗锯齿..blendMode = BlendMode.exclusion // 颜色混合模式..style = PaintingStyle.fill // 绘画风格,默认为填充..colorFilter = ColorFilter.mode(Colors.blueAccent,BlendMode.exclusion) // 颜色渲染混合模式..maskFilter = MaskFilter.blur(BlurStyle.inner, 2.0) // 模糊遮罩效果..filterQuality = FilterQuality.high // 颜色渲染模式的质量..strokeWidth = 10.0; // 画笔的宽度 

通过 CustomPaint 自定义的 Widget

class CustomWidgetSamplesState extends State<CustomWidgetSamples> {@overridevoid initState() {super.initState();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('CustomWidget'),),body: Column(children: <Widget>[CustomPaint(painter: Sky(),child: Center(child: Text('文字',),),)],));}
}

class Sky extends CustomPainter {@overridevoid paint(Canvas canvas, Size size) {// 绘制圆角矩形// 用Rect构建一个边长50,中心点坐标为150,150的矩形Rect rectCircle =Rect.fromCircle(center: Offset(150.0, 150.0), radius: 60.0);// 根据上面的矩形,构建一个圆角矩形RRect rrect = RRect.fromRectAndRadius(rectCircle, Radius.circular(30.0));canvas.drawRRect(rrect, Paint()..color = Colors.yellow);}@overridebool shouldRepaint(Sky oldDelegate) => false;@overridebool shouldRebuildSemantics(Sky oldDelegate) => false;
} 

七、附加内容

1、Flutter 方法的封装示例

import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';

class Utils {BuildContext context;// 可以设置构造方法,传递参数,参数传递时有区别,通过key:value形式Utils({@required this.context}) : assert(context != null);// 首先指定返回类型,然后定义方法名/// 获取时间戳毫秒数,13位int getMilliseconds() {return DateTime.now().millisecondsSinceEpoch;}// 方法名后可以设置传递的参数/// 复制到剪贴板void setClipData(String text) {Clipboard.setData(ClipboardData(text: text));}// 以下划线开始的方法名这个类的外部不可以调用,只能内部进行调用使用/// 获取屏幕宽度double _getScreenWidth(BuildContext context) {return MediaQuery.of(context).size.width;}/// 获取屏幕高度double getScreenHeight(BuildContext context) {return MediaQuery.of(context).size.height;}/// 获取屏幕状态栏高度double getStatusBarTop(BuildContext context) {return MediaQuery.of(context).padding.top;}/// 获取屏幕方向Orientation getScreenOrientation(BuildContext context) {return MediaQuery.of(context).orientation;}Future<String> getBatteryLevel() async {var batteryLevel = 'unknown';MethodChannel methodChannel = MethodChannel('samples.flutter.io/battery');try {int result = await methodChannel.invokeMethod('getBatteryLevel');batteryLevel = 'Battery level: $result%';} on PlatformException {batteryLevel = 'Failed to get battery level.';}return batteryLevel;}
}

// 使用时,构造方法传参通过key:value形式传递设置
Utils utils = Utils(context: context);

// 调用方法
utils.getScreenHeight(context); 

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-12-25 11:21:31  更:2022-12-25 11:24:52 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年3日历 -2024/3/28 20:38:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码