Flutter开发之Widget自定义总结
前言
在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的学习也有一段时间了,从最开始的widget的使用,到后面的框架的一些研究,所有的心得与总结都会记录下来,主要是对自己知识点的整理,同样也为了能够与广大flutter的学习者共同学习,相互探讨。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。
上一篇: 花茶第一泡要倒掉吗