Flutter开发之Widget自定义总结
作者:kevinxie 发布时间:2021-11-07 12:41:03
前言
在Flutter实际开发中,大家可能会遇到flutter框架中提供的widget达不到我们想要的效果,这时就需要我们去自定义widget,从Flutter构建、布局、绘制三部曲中我们了解到,实际的测量、布局、绘制操作都在RenderObject中,我们是可以进行继承相关的RenderObject来实现自定义的。但是其实flutter框架在设计之初就给我们预留出了自定义的入口,方便我们进行自定义。
CustomPaint自定义绘制
例:圆形进度条
思路:使用CustomPaint绘制需要的效果
class CircleProgress extends StatelessWidget {
final Size size;
final double progress;
CircleProgress({@required this.size, @required this.progress});
@override
Widget build(BuildContext context) {
return CustomPaint(
size: size,
painter: CircleProgressPainter(endDegree: progress * 360),//在Painter中写真正的绘画逻辑
);
}
}
class CircleProgressPainter extends CustomPainter {
...省略
@override
void paint(Canvas canvas, Size size) {
...绘制的具体逻辑,size是画布的大小
}
}
CustomSingleChildLayout对单一child进行布局
例:实现对child约束成正方形
思路:使用CustomSingleChildLayout对child进行布局,并约束为正方形
class RectLayout extends StatelessWidget {
final Widget child;
RectLayout({@required this.child});
@override
Widget build(BuildContext context) {
return CustomSingleChildLayout(
delegate: RectLayoutDelegate(),//进行布局的代理
child: child,
);
}
}
class RectLayoutDelegate extends SingleChildLayoutDelegate {
//确定layout的size,constraints是parent传过来的约束
@override
Size getSize(BoxConstraints constraints) => super.getSize(constraints);
///是否需要relayout
@override
bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) => false;
///确定child的位置,返回一个相对于parent的偏移值,size是layout的大小,由getsize确定,childSize由getConstraintsForChild得出的Constraints对child进行约束,得到child自身的size
@override
Offset getPositionForChild(Size size, Size childSize) {
double dx = (size.width - childSize.width) / 2;
double dy = (size.height - childSize.height) / 2;
return Offset(dx, dy);
}
///确定child的约束,用于确定child的大小
@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {//
double maxEdge = min(constraints.maxWidth, constraints.maxHeight);
return BoxConstraints(maxWidth: maxEdge, maxHeight: maxEdge);
}
}
CustomSingleChildLayout对多个child进行布局
例:实现网格布局
思路:使用CustomSingleChildLayout对child进行布局、定位,使其成为网格的布局
class GridLayout extends StatelessWidget {
final List<Widget> children;
final double horizontalSpace;
final double verticalSpace;
GridLayout(
{@required this.children,
@required this.horizontalSpace,
@required this.verticalSpace});
@override
Widget build(BuildContext context) {
List<Widget> layoutChildren = new List();
for (int index = 0; index < children.length; index++) {
layoutChildren.add(LayoutId(id: index, child: children[index]));
}
return CustomMultiChildLayout(
delegate: GridLayoutDelegate(//真正的布局实现
horizontalSpace: horizontalSpace,
verticalSpace: verticalSpace,
),
children: layoutChildren,
);
}
}
class GridLayoutDelegate extends MultiChildLayoutDelegate {
final double horizontalSpace;
final double verticalSpace;
List<Size> _itemSizes = List();
GridLayoutDelegate(
{@required this.horizontalSpace, @required this.verticalSpace});
@override
void performLayout(Size size) {
//对每个child进行逐一布局
int index = 0;
double width = (size.width - horizontalSpace) / 2;
var itemConstraints = BoxConstraints(
minWidth: width, maxWidth: width, maxHeight: size.height);
while (hasChild(index)) {
_itemSizes.add(layoutChild(index, itemConstraints));
index++;
}
//对每一个child逐一进行定位
index = 0;
double dx = 0;
double dy = 0;
while (hasChild(index)) {
positionChild(index, Offset(dx, dy));
dx = index % 2 == 0 ? width + horizontalSpace : 0;
if (index % 2 == 1) {
double maxHeight =
max(_itemSizes[index].height, _itemSizes[index - 1].height);
dy += maxHeight + verticalSpace;
}
index++;
}
}
@override
bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) {
return oldDelegate != this;
}
//确定layout的size,constraints是parent传过来的约束
@override
Size getSize(BoxConstraints constraints) => super.getSize(constraints);
}
组合自定义
一般情况,组合自定义应该是我们最经常用的方式,通过继承自StatelessWidget或StatefulWidget,把多个Widget组合起来,从而达到我们需要的效果。
例:下拉刷新,上拉加载
实现一:通过自带的RefreshIndictor和ScrollController组合实现
思路:通过对滚动进行监听来触发加载更多
_scrollController.addListener(() {
var maxScroll = _scrollController.position.maxScrollExtent;
if (_scrollController.offset >= maxScroll) {
if (widget.loadMoreStatus != LoadMoreStatus.noData) {
widget.onLoadMore();
}
}
});
实现二:通过NotificationListener监听scroll的整体状态,让后结合平移、动画来实现
思路:通过监听用户overscroll的距离来平移内容区域,从而达到下拉刷新,上拉加载的效果
@override
Widget build(BuildContext context) {
double topHeight =
_pullDirection == PullDirection.DOWN ? _overScrollOffset.dy.abs() : 0;
double bottomHeight =
_pullDirection == PullDirection.UP ? _overScrollOffset.dy.abs() : 0;
return Stack(
children: <Widget>[
widget.headerBuilder.buildTip(_state, topHeight),
Align(
alignment: Alignment.bottomCenter,
child: widget.footerBuilder.buildTip(_state, bottomHeight),
),
Transform.translate(
offset: _overScrollOffset,
child: NotificationListener<ScrollNotification>(
onNotification: handleScrollNotification,
child: DecoratedBox(
decoration: BoxDecoration(color: Colors.grey[100]),
child: ListView.builder(
itemBuilder: buildItem,
itemCount: 30,
),
),
),
)
],
);
}
例:上下左右滑动的layout
实现:通过GestureDetector监听手势滑动,然后通过平移来达到效果
思路:主要处理滑动边界,以及开关的零界点
@override
Widget build(BuildContext context) {
//debugPrint('_slideOffset:${_slideOffset.toString()}');
return GestureDetector(
onPanUpdate: handlePanUpdate,
onPanEnd: handlePanEnd,
child: Stack(
children: <Widget>[
widget.background,
Transform.translate(
child: widget.foreground,
offset: _slideOffset,
),
],
),
);
}
以上的完整代码在这flutter知识点整理
Flutter学习总结
对Flutter的学习也有一段时间了,从最开始的Widget的使用,到后面的框架的一些研究,所有的心得与总结都会记录下来,主要是对自己知识点的整理,同样也为了能够与广大Flutter的学习者共同学习,相互探讨。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对脚本之家的支持。
来源:https://juejin.im/post/5ca5d8bef265da30851fa657


猜你喜欢
- 1.现象描述原来项目在Android studio 2.3一切正常,升级3.0之后报如下错误:Error:Cannot choose bet
- 前言写Android:如何编写“万能”的Activity的这篇文章到现在已经好久了,但是由于最近事情较多,写重构篇的计划就一直被无情的耽搁下
- 本文实例为大家分享了java实现客房管理系统的具体代码,供大家参考,具体内容如下AddClient.javaimport java.awt.
- clone()和Cloneable接口clone顾名思义就是克隆,即,复制一个相等的对象,但是不同的引用地址。我们知道拿到一个对象的地址,只
- JavaFX主要致力于富客户端开发,以弥补swing的缺陷,主要提供图形库与media库,支持audio,video,graphic,ani
- 目录数据类型布尔类型字符串类型String拼接字符'+'转义字符运算符加减乘除模运算增量赋值运算符自增运算符和自建运算符赋值
- 1.什么是线程安全性当多个线程访问某个类时,不管运行时环境采用何种调用方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或
- 本文是利用SharpPcap实现网络包的捕获的小例子,实现了端口监控,数据包捕获等功能,主要用于学习分享。什么是SharpPcap?Shar
- java 打造阻塞式线程池的实例详解原来以为tiger已经自带了这种线程池,就是在任务数量超出时能够阻塞住投放任务的线程,主要想用在JMS消
- 1.概念1.AOP技术简介AOP 为Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运
- 本文实例讲述了Spring使用ClassPathResource加载xml资源。分享给大家供大家参考,具体如下:一 代码package le
- 前言什么是语法糖?(语法糖就是像糖一样的语法…)语法糖(Syntactic sugar),又名糖衣语法,最早是由英国计算机科学家彼得·约翰·
- 企业级的系统和我们平常桌面、手机上运行的软件有着很重要的区别,其中比较重要的一点就是环境(包括第三方的系统的不同接口以及各系统的不同版本、安
- 本文实例讲述了java之swing下拉菜单实现方法。分享给大家供大家参考。具体如下:import java.awt.*;import jav
- 前言公司的邮件系统用的是 * 的 Lotus notes, 你敢信?最近要实现一个功能,邮件提醒功能,就是通过自动发送提醒邮件 前
- Springboot导出文件,前端下载文件后端代码可以把请求设置为post,我这里是Get @RequestMapping(value =
- 1、Java数组介绍在Java中,数组是用来存放同一种数据类型的集合,注意只能存放同一种数据类型(Object类型数组除外)。①、数组的声明
- 本文实例讲述了Android编程解析XML方法。分享给大家供大家参考,具体如下:XML在各种开发中都广泛应用,Android也不例外。作为承
- 本文介绍C# lock关键字,C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时
- 1.简介本教程将介绍如何在Spring Security中设置身份验证提供程序,与使用简单UserDetailsService的标准方案相比