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

目录
  • 废话开篇:
  • 先看复用效果
  • 复用状态打印
  • 问题一、实现思路是什么?
  • 问题二、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仿写复用机制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

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

    目录 废话开篇: 先看复用效果 复用状态打印 问题一.实现思路是什么? 问题二.UI布局代码分析. 总结 废话开篇: iOS与android在实现列表界面的时候是有重用机制的,目的就是减少内存开销,用时间换空间.个人感觉flutter并没有特别强调复用,关于listView.builder 的"复用"个人感觉应该是销毁跟重建的过程,所以这里用flutter实现了简单的复用机制.代码拙劣,大神勿喷,共同进步 先看复用效果 复用状态打印 右侧是简单实现瀑布流界面,里面显示的是一共有39个W

  • Android学习笔记之ListView复用机制详解

    PS:满打满算,差不多三个月没写博客了...前一阵忙的不可开交...总算是可以抽出时间研究研究其他事情了... 1.ListView的复用机制 ListView是我们经常使用的一个控件,虽然说都会用,但是却并不一定完全清楚ListView的复用机制,虽然在Android 5.0版本之后提供了RecycleView去替代ListView和GridView,提供了一种插拔式的体验,也就是所谓的模块化.本篇主要针对ListView的复用机制进行探讨,因此就 提RecycleView.昨天看了一下郭霖大

  • Android原生音量控制实例详解

    本文主要涉及AudioService.还是基于5.1.1版本的代码. AudioService.java文件位于/framework/base/media/java/android/media/下. 音量控制是AudioService最重要的功能之一.先总结一下: AudioService音量管理的核心是VolumeStreamState.它保存了一个流类型所有的音量信息. VolumeStreamState保存了运行时的音量信息,而音量的生效则是在底层AudioFlinger完成的.所以进行音

  • Flutter实现自定义下拉选择框的示例详解

    在一些列表页面中,我们经常会有上方筛选项的的需求,点击出现一个下拉菜单,多选.单选.列表选等,而在Flutter中,并没有现成的这样的组件,找第三方的扩展有时候又会受到一定限制,所以最好我们可以自己做一个,这样即使扩展我们也会得心应手. 先看效果图: 关键点:弹出.收回动画.状态改变.选项联动 思路: 我们可以看到一个完整的下拉框有头部和具体的下拉选项两部分组成,头部又和下拉组进行了联动, 把头部当做1个数组,下方选项作为1个数组,两个数组数量一致之间形成一个完整的下拉选择框可以更好的控制联动效

  • Java中Stream流中map和forEach的区别详解

    目录 什么是 stream 流 Map forEach 使用场景 不是很难的知识,但是今天犯错了,记录一下 什么是 stream 流 我们在使用集合或数组对元素进行操作时往往会遇到这种情况:通过对不同类型的存储元素,按照特定条件进行查找.排序.等操作时往往会写一大段代码,而且更要命的是,不同类型的数据,操作的方法也不一样,比如一个存储 Student 实体类和一个只存储 String 类型的集合俩者的操作步骤肯定大不一样且无法通用,而 stream API 就解决了这些问题,对数据操作时进行了统

  • Flutter实现一个支持渐变背景的Button示例详解

    目录 Flutter中的按钮 不完美的地方 在child中处理 外面套一个wrapper MaterialStateProperty MaterialStatesController 边距问题 EnhancedButton Flutter中的按钮 自Flutter 1.20 新增了ButtonStyleButton 系列按钮,可以说非常好用了,默认样式比之前漂亮了许多,扩展性也增加了很多.按钮样式统一由ButtonStyle这个类提供,支持根据各种状态(MaterialState)变化的属性,也

  • Flutter开发setState能否在build中直接调用详解

    目录 两种情况 原理分析 总结 两种情况 setState() 能在 build() 中直接调用吗?答案是能也不能. 来看一段简单的代码: import 'package:flutter/material.dart'; class TestPage extends StatefulWidget { const TestPage({super.key}); @override State<TestPage> createState() => _State(); } class _State

  • Flutter学习之创建一个内嵌的navigation详解

    目录 简介 搭建主Navigator 构建子路由 总结 简介 我们在flutter中可以使用Navigator.push或者Navigator.pushNamed方法来向Navigator中添加不同的页面,从而达到页面调整的目的. 一般情况下这样已经足够了,但是有时候我们有多个Navigator的情况下,上面的使用方式就不够用了.比如我们有一个主页面app的Navigator,然后里面有一个匹配好友的功能,这个功能有多个页面,因为匹配好友功能的多个页面实际上是一个完整的流程,所以这些页面需要被放

  • IOS图片的原生(Graphics)详解及实例

    IOS图片的原生(Graphics)详解及实例 一,效果图. 二,工程图. 三,代码. RootViewController.h #import <UIKit/UIKit.h> @interface RootViewController : UIViewController @end RootViewController.m #import "RootViewController.h" @interface RootViewController () @end @imple

  • 利用FlubuCore用C#来写DevOps脚本的方法详解

    前言 随着近些年微服务的流行,有越来越多的开发者和团队所采纳和使用,它的确提供了很多的优势也解决了很多的问题,但是我们也知道也并不是银弹,提供优势的同时它也给我们的开发人员和团队也带来了很多的挑战. 为了迎接或者采用这些新技术,开发团队需要更加注重一些流程或工具的使用,这样才能更好的适应这些新技术所带来的一些问题. 对于流程行问题,敏捷的Scrum能够很好的提升产品开发团队之间的协作问题,那么对于应用变的越来越复杂这种情况,它最直接的问题就是带来了开发运维的复杂性,这个时候我们就需要使用工具来解

随机推荐