Flutter瀑布流仿写原生的复用机制有什么用
这篇文章主要介绍了Flutter瀑布流仿写原生的复用机制有什么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。
废话开篇:
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 实现
@overrideWidget 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 = [];//当前显示的瀑布流itemList<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单元组件
//渲染瀑布流itemcreateWaterfallFlow(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瀑布流仿写原生的复用机制有什么用”这篇文章对大家有帮助,同时也希望大家多多支持编程网,关注编程网行业资讯频道,更多相关知识等着你来学习!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341