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

网友投稿 1717 2022-12-23

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

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 createState() {

_waterfallFlowItemState = new WaterfallFlowItemState(frame: _frame!);

return _waterfallFlowItemState!;

}

}

class WaterfallFlowItemState extends State 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!-,

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 _bufferPoolWidget = [];

//当前显示的瀑布流item

List _listWidget = [];

//当前组渲染frame对象保存

List _fList = [];

//总frame集合

List _frameList = [];

//数据源这里只保存高度

List _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 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- <= _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 list = [];

for(int i = 0; i < _listWidget.length;i++){

WaterfallFlowItem? waterfallFlowItem = _listWidget[i];

Frame? frame = waterfallFlowItem.getFrame();

if((frame- + frame.heigth) < _scrollOff || frame- > _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-},${_mineContentHeight + _scrollOff}');

if(upNextFrame- <= _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-},${_mineContentHeight + _scrollOff}');

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

总结

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:vue框架开发小程序(小程序 vue框架)
下一篇:vue小程序开发(vue小程序开发项目)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~