目录
- 问题二、ui布局代码分析。
废话开篇:
ios与android在实现列表界面的时候是有重用机制的,目的就是减少内存开销,用时间换空间。个人感觉flutter并没有特别强调复用,关于listview.builder 的“复用”个人感觉应该是销毁跟重建的过程,所以这里用flutter实现了简单的复用机制。代码拙劣,大神勿喷,共同进步
先看复用效果
复用状态打印
右侧是简单实现瀑布流界面,里面显示的是一共有39个widget。左侧是控制台打印一共创建的12个widget,所以这里就简单的实现了widget复用。
问题一、实现思路是什么?
这里先简单的说一下实现思路。
- 在渲染界面前,通过计算得出全部的widget的位置坐标。
- 首次渲染创建一屏可视瀑布流widget.
- 监听滑动,判断当前页面滚动方向展示的瀑布流widget,先去缓存池里拿,如果没有就直接创建,添加到组件中进行渲染。如果缓存池里有,修改widget的相对布局位置。
问题二、ui布局代码分析。
tip: waterfallflow.dart 瀑布流主页面;waterfallflowitem.dart 瀑布流单元item
效果展示:
waterfallflowitem.dart 瀑布流item文件
class waterfallflowitem extends statefulwidget{ frame? _frame; waterfallflowitemstate? _waterfallflowitemstate; waterfallflowitem({required frame frame}){ _frame = frame; } frame getframe(){ return _frame!; } void setframe({required frame frame}) { _frame = frame; _waterfallflowitemstate!.setframe(frame: frame); } @override state<statefulwidget> createstate() { _waterfallflowitemstate = new waterfallflowitemstate(frame: _frame!); return _waterfallflowitemstate!; } } class waterfallflowitemstate extends state<waterfallflowitem> with automatickeepaliveclientmixin { frame? _frame; waterfallflowitemstate({required frame frame}){ _frame = frame; } void setframe({required frame frame}) { setstate(() { _frame = frame; }); } @override widget build(buildcontext context) { return new positioned( top: _frame!.top, left: _frame!.left, child: gesturedetector( child: new container( color: _frame!.index == 12 ? colors.red : color.fromargb(255, 220, 220, 220), width: _frame!.width, height: _frame!.heigth, child: new text(_frame!.index.tostring()), ), ontap: (){ }, ) ); } @override // todo: implement wantkeepalive bool get wantkeepalive => true; }
waterfallflow.dart 主界面文件
builder 实现
@override widget build(buildcontext context) { return new container( //去掉scrollview顶部空白间隙 child: mediaquery.removepadding( context: context, removetop: true, child: scrollbar( //isalwaysshown: true, //showtrackonhover: true, //scrollview child: new singlechildscrollview( controller: _scrollcontroller, child: new container( width: mediaquery.of(context).size.width, //最大高度 height: _maxheight, color: colors.white, child: new stack( //帧布局下的瀑布流单元格item集合 children: _listwidget, ), ), ), ) ), ); }
声明的属性
//瀑布流间隔 double sep = 5; //瀑布流宽度 double? _width; //最大高度 double _maxheight = 0; //左侧最大高度 double leftheight = 0; //右侧最大高度 double rightheight = 0; //主界面高度 double _minecontentheight = 0; //瀑布流item缓存池 list<waterfallflowitem> _bufferpoolwidget = []; //当前显示的瀑布流item list<waterfallflowitem> _listwidget = []; //当前组渲染frame对象保存 list<frame> _flist = []; //总frame集合 list<frame> _framelist = []; //数据源这里只保存高度 list<double> _list = [ 100,150,45,11,140,89,212,21,434,545,100,150,45,11,140,89,212,21,434,545, 100,150,45,11,140,89,212,21,434,545,100,150,45,11,140,89,212,21,434,545]; //滑动监听 scrollcontroller _scrollcontroller = new scrollcontroller(); //滑动偏移量 double _scrolloff = 0;
计算主窗口scrollview 高度
//获取最大高度,并计算出全部的瀑布流位置 void getmaxheight(){ list<frame> flist = []; double width = (_width! - sep * 3) / 2.0; double maxheight = _maxheight; for(int i = _framelist.length;i < _list.length;i++){ double height = _list[i]; bool isleft = (leftheight <= rightheight); double left = isleft ? sep : (width + sep * 2); maxheight = isleft ? leftheight : rightheight; frame frame = frame(leftp: left, topp: maxheight, widthp: width, heigthp: height,indexp: i); if(isleft == true) { leftheight += (height + sep); } else { rightheight += (height + sep); } flist.add(frame); } _maxheight = max(leftheight, rightheight); _framelist.addall(flist); //刷新 setstate(() {}); }
frame 位置信息类
class frame{ double left = 0;//左 double top = 0;//右 double width = 0;//宽度 double heigth = 0;//高度 int index = 0;//索引 frame({required leftp ,required topp, required widthp, required heigthp, required indexp}){ left = leftp * 1.0; top = topp * 1.0; width = widthp * 1.0; heigth = heigthp * 1.0; index = indexp; } }
生成瀑布流widget单元item
//重用池里生成item _takereuseflowitem(frame f,dynamic block){ waterfallflowitem? waterfallflowitem; //是否重用,是,直接修改frame;否,重新渲染。 bool isreuse = false; //有,从缓存池里取(缓存中的已在结构树里,可以修改帧布局位置) if(_bufferpoolwidget.length > 0){ waterfallflowitem = _bufferpoolwidget.last; waterfallflowitem.setframe(frame: f); _bufferpoolwidget.removelast(); isreuse = true; } //没有,直接创建(不缓存中的,需要调用setstate方法渲染) if(waterfallflowitem == null) { waterfallflowitem = new waterfallflowitem(frame: f,); isreuse = false; } block(waterfallflowitem,isreuse); }
创建首屏全部可视瀑布流widget单元组件
//渲染瀑布流item createwaterfallflow(int index){ getmaxheight(); //这里加点延迟,保证获取最大高度完成(不太严谨,大神有好方法请赐教[抱拳]) future.delayed(duration(milliseconds: 100),(){ _minecontentheight = context.size!.height; for(var i = 0;i < _framelist.length;i++){ frame f = _framelist[i]; //判断可视化逻辑 if(f.top <= _minecontentheight + _scrolloff) { _takereuseflowitem(f,(waterfallflowitem waterfallflowitem,bool isreuse){ _listwidget.add(waterfallflowitem); }); } } setstate(() { }); }); }
滑动过程中进行重用渲染
//获取上滑状态当前显示的下一个item位置 frame? _getupneedshowframe(){ frame? f; waterfallflowitem? lastwaterfallflowitem = _listwidget.last; if(lastwaterfallflowitem.getframe().index + 1 < _framelist.length) { f = _framelist[lastwaterfallflowitem.getframe().index + 1]; } return f; } //获取下滑状态当前显示的上一个item位置 frame? _getdownneedshowframe(){ frame? f; waterfallflowitem? lastwaterfallflowitem = _listwidget[0]; if(lastwaterfallflowitem.getframe().index - 1 >= 0) { f = _framelist[lastwaterfallflowitem.getframe().index - 1]; } return f; } //超出界面可视范围的瀑布流加入缓存池 void addflowitemaddtobufferpool(){ list<waterfallflowitem> list = []; for(int i = 0; i < _listwidget.length;i++){ waterfallflowitem? waterfallflowitem = _listwidget[i]; frame? frame = waterfallflowitem.getframe(); if((frame.top + frame.heigth) < _scrolloff || frame.top > _minecontentheight + _scrolloff) { _bufferpoolwidget.add(waterfallflowitem); list.add(waterfallflowitem); } } if(list.length != 0) { for(int i= 0;i < list.length;i++){ waterfallflowitem? waterfallflowitem = list[i]; if(_listwidget.contains(waterfallflowitem)){ _listwidget.remove(waterfallflowitem); } } } //从缓存池里获取item //上滑状态 frame? upnextframe = _getupneedshowframe(); if(upnextframe != null) { //debugprint('我是在复用 ${upnextframe.index} ,${upnextframe.top},${_minecontentheight + _scrolloff}'); if(upnextframe.top <= _minecontentheight + _scrolloff) { debugprint('我在上滑重置第${upnextframe.index}个frame'); _takereuseflowitem(upnextframe,(waterfallflowitem waterfallflowitem,bool isreuse){ _listwidget.add(waterfallflowitem); if(!isreuse){ debugprint('我不是复用'); setstate(() {}); } else { debugprint('我是复用'); waterfallflowitem.setframe(frame: upnextframe); } }); } } //下滑状态 frame? downnextframe = _getdownneedshowframe(); if(downnextframe != null) { //debugprint('我是在复用 ${downnextframe.index} ,${downnextframe.top},${_minecontentheight + _scrolloff}'); if(downnextframe.top + downnextframe.heigth > _scrolloff && downnextframe.top + downnextframe.heigth < _minecontentheight + _scrolloff) { debugprint('我在下滑重置第${downnextframe.index}个frame'); _takereuseflowitem(downnextframe,(waterfallflowitem waterfallflowitem,bool isreuse){ _listwidget.insert(0, waterfallflowitem); if(!isreuse){ debugprint('我不是复用'); setstate(() {}); } else { debugprint('我是复用'); waterfallflowitem.setframe(frame: downnextframe); } }); } } }
滚动监听
_scrollcontroller.addlistener(() { _scrolloff = _scrollcontroller.offset; //加入缓存池,并进行复用 addflowitemaddtobufferpool(); debugprint('总共:${_listwidget.length + _bufferpoolwidget.length} 个'); });
基本上flutter的瀑布流复用逻辑就完成了,代码拙劣,里面有些地方需要优化,比如:快速滑动防护,item的内容渲染。flutter对于界面渲染已经很极致了,重写复用有点倒退的赶脚。大神勿喷,互相学习。
总结
到此这篇关于flutter瀑布流仿写原生的复用机制的文章就介绍到这了,更多相关flutter仿写复用机制内容请搜索<计算机技术网(www.ctvol.com)!!>以前的文章或继续浏览下面的相关文章希望大家以后多多支持<计算机技术网(www.ctvol.com)!!>!
需要了解更多c/c++开发分享Flutter瀑布流仿写原生的复用机制详解,都可以关注C/C++技术分享栏目—计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/678288.html