软件编程
位置:首页>> 软件编程>> flutter编程>> Flutter瀑布流仿写原生的复用机制详解

Flutter瀑布流仿写原生的复用机制详解

作者:头疼脑胀的代码搬运工  发布时间:2023-06-20 17:02:08 

标签:flutter,复用,机制

废话开篇:

iOS与android在实现列表界面的时候是有重用机制的,目的就是减少内存开销,用时间换空间。个人感觉flutter并没有特别强调复用,关于listView.builder 的“复用”个人感觉应该是销毁跟重建的过程,所以这里用flutter实现了简单的复用机制。代码拙劣,大神勿喷,共同进步

先看复用效果

Flutter瀑布流仿写原生的复用机制详解

复用状态打印

Flutter瀑布流仿写原生的复用机制详解

右侧是简单实现瀑布流界面,里面显示的是一共有39个Widget。左侧是控制台打印一共创建的12个Widget,所以这里就简单的实现了Widget复用。

问题一、实现思路是什么?

这里先简单的说一下实现思路。

  • 在渲染界面前,通过计算得出全部的Widget的位置坐标。

  • 首次渲染创建一屏可视瀑布流Widget.

  • 监听滑动,判断当前页面滚动方向展示的瀑布流Widget,先去缓存池里拿,如果没有就直接创建,添加到组件中进行渲染。如果缓存池里有,修改Widget的相对布局位置。

问题二、UI布局代码分析。

Flutter瀑布流仿写原生的复用机制详解

tip: WaterfallFlow.dart 瀑布流主页面;WaterfallFlowItem.dart 瀑布流单元item

效果展示:

Flutter瀑布流仿写原生的复用机制详解

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对于界面渲染已经很极致了,重写复用有点倒退的赶脚。大神勿喷,互相学习。

来源:https://juejin.cn/post/6989900968842231838

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com