欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Flutter开发之Widget自定义总结

程序员文章站 2022-06-09 14:35:03
前言 在flutter实际开发中,大家可能会遇到flutter框架中提供的widget达不到我们想要的效果,这时就需要我们去自定义widget,从flutter构建、布局...

前言

在flutter实际开发中,大家可能会遇到flutter框架中提供的widget达不到我们想要的效果,这时就需要我们去自定义widget,从flutter构建、布局、绘制三部曲中我们了解到,实际的测量、布局、绘制操作都在renderobject中,我们是可以进行继承相关的renderobject来实现自定义的。但是其实flutter框架在设计之初就给我们预留出了自定义的入口,方便我们进行自定义。

custompaint自定义绘制

例:圆形进度条

Flutter开发之Widget自定义总结

思路:使用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约束成正方形

Flutter开发之Widget自定义总结

思路:使用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进行布局

例:实现网格布局

Flutter开发之Widget自定义总结

思路:使用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组合实现

Flutter开发之Widget自定义总结

思路:通过对滚动进行监听来触发加载更多

_scrollcontroller.addlistener(() {
 var maxscroll = _scrollcontroller.position.maxscrollextent;
 if (_scrollcontroller.offset >= maxscroll) {
 if (widget.loadmorestatus != loadmorestatus.nodata) {
 widget.onloadmore();
 }
 }
});

实现二:通过notificationlistener监听scroll的整体状态,让后结合平移、动画来实现

Flutter开发之Widget自定义总结

思路:通过监听用户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监听手势滑动,然后通过平移来达到效果

Flutter开发之Widget自定义总结

思路:主要处理滑动边界,以及开关的零界点

@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的学习也有一段时间了,从最开始的widget的使用,到后面的框架的一些研究,所有的心得与总结都会记录下来,主要是对自己知识点的整理,同样也为了能够与广大flutter的学习者共同学习,相互探讨。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。