探索flutter框架开发的app在移动应用市场的潜力与挑战
1717
2022-12-23
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
_waterfallFlowItemState = new WaterfallFlowItemState(frame: _frame!);
return _waterfallFlowItemState!;
}
}
class WaterfallFlowItemState extends State
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
//当前显示的瀑布流item
List
//当前组渲染frame对象保存
List _fList = [];
//总frame集合
List _frameList = [];
//数据源这里只保存高度
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
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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~