赞
踩
最近用flutter做了一个评论弹窗的功能,本来以为很简单的烂大街的一个功能,结果却遇到了不少的问题,而且这些问题我觉得很有意义,以至于我觉得我如果分享出来可能会对其他人很有帮助。
要做一件事情可能会很容易,但做好一件事情却很难~
粗略的截了一些图:抖音、豆瓣、知乎、番茄小说
这些产品的评论功能基本都是这种弹窗或者说是滑动面板的模式:
抖音/豆瓣
知乎/番茄小说
当认真的体验了这几款产品后,发现有这些特点:
列表与下拉手势联动 | 面板顶部下拉手势 | 查看更多回复 | 弹起键盘 | |
---|---|---|---|---|
抖音 | 支持 | 支持 | 点击展开 | 滚动定位 |
豆瓣 | 支持 | 支持 | 点击展开 | 评论框顶部显示内容 |
知乎 | 支持 | 不支持 | 跳转新页面 | 无处理 |
番茄小说 | 支持 | 支持 | 跳转新页面 | 滚动定位 |
大概梳理总结这么几点:
我相信这几款产品基本都是用android原生去写的,那么flutter是否实现和它们一样的用户体验呢?
其实我最初想到的就是用showModalBottomSheet去从底部弹一个窗出来,但是BottomSheet本身并没有处理与列表的滑动交互问题,如果凑合着其实也还行,就是缺少了下拉手势的交互。
所以我在flutter社区找到了sliding_up_panel,这是一个很受欢迎的库,我打算基于这个库来实现评论功能
至于实现效果,最终是选择了番茄小说的交互效果:
列表与下拉手势联动 | 面板顶部下拉手势 | 查看更多回复 | 弹起键盘 | |
---|---|---|---|---|
番茄小说 | 支持 | 支持 | 跳转新页面 | 滚动定位 |
那些基本的代码就不细讲了,一顿操作下来,基本功能成型:
接下来说的难点解决暂时也不贴具体的源码了,以讲思路为主,部分是真实的,部分是伪代码
最终的源码我会贴到文章末尾~
看一下官方文档的说明:
Properties | Description |
---|---|
panelBuilder [beta] | NOTE: This feature is still in beta and may have some problems. Please open an issue on GitHub if you encounter something unexpected.Provides a ScrollController to attach to a scrollable object in the panel that links the panel position with the scroll position. Useful for implementing an infinite scroll behavior. If panel and panelBuilder are both non-null, panel will be used. |
panelBuilder会回调一个ScrollController,ListView能跟面板手势连接的原因就是根据ScrollController的offset来判断哪个时机能滚动面板
目前我们有一个列表页,点击列表项还能跳转到一个详情页,两个页面都存在ListView,那么目前在panelBuilder只提供一个ScrollController的情况下,只能做到在列表页与面板有手势的联动,跳转到详情页后将无法联动面板,因为一个ScrollController只能用在一个ListView上
为此我提了一个issue: github.com/akshathjain…
但是这个库的作者好久都没有更新了,只能自己想办法了
我们可以发现这个ScrollController是在sliding_up_panel内部创建的:
// prevent the panel content from being scrolled only if the widget is
// draggable and panel scrolling is enabled
_sc = new ScrollController();
_sc.addListener(() {
if (widget.isDraggable && !_scrollingEnabled) _sc.jumpTo(0);
});
那么我们实际上可以扩展sliding_up_panel的PanelController,使他拥有可以set ScrollController的能力
实现方法:
在panelState中添加ScrollController的set方法
然后在PanelController添加一个setScrollController的方法:
void setScrollController(ScrollController sc) {
_panelState!.sc = sc;
}
在列表页要向详情页跳转的时候,将面板的ScrollController变为详情页的ScrollController
当从详情页退出到列表页的时候,再将面板的ScrollController变回列表页的ScrollController
大概是这样的伪代码:
double offset = listController?.offset ?? 0;
panelController.setScrollController(listScrollController);
Navigator.push(...).then((value) {
panelController.setScrollController(detailScrollController);
listController?.jumpTo(offset);
});
无论是在评论列表页还是在评论详情页,列表都能很好的和面板手势交互连接起来,符合预期~
具体的表现为:列表不在初始位置时,红框部分下拉无响应
其实这是预期的行为,因为sliding_up_panel并没有提供这方面的能力
同样我又提了一个issue: github.com/akshathjain…
同样的结果,这个库的作者好久都没有更新了,只能自己想办法了
要想让外部的widget也能像面板内部一样去响应手势,我们得知道源码是怎么实现的
通过阅读源码发现,手势滑动面板的代码主要在这里:
那么我们可以把这一部分代码单独封装成一个方法,通过PanelController对外提供出去
然后再对TopBar做一个手势监听,监听的同时去调用这个方法,那么就可以让TopBar去响应手势了
PanelController新增的代码:
void onChildWidgetPointerMove(PointerMoveEvent p) {
_panelState!.onChildWidgetPointerMove(p);
}
在TopBar的外部包裹Listener组件,对手势进行响应:
Listener(
behavior: HitTestBehavior.opaque,
onPointerMove: (p) => panelController.onChildWidgetPointerMove(p),
child:TopBar()
)
在TopBar上加上手势后,即使列表不在初始位置,TopBar依旧可以响应下拉手势,符合预期~
关于这个功能,我选择找一个开源库来实现:
在监听键盘弹起的时候,调用滚动方法:
@override
void didChangeMetrics() {
// 回复他人的时候滚动调整,延迟300毫秒滑动动画更自然
if (index != -1) {
Future.delayed(Duration(milliseconds: 300), () {
scrollController?.scrollToIndex(index);
});
}
super.didChangeMetrics();
}
具体的实现代码就不在这里贴了,本来以为很顺利就可以写完的一个功能
结果又出现了问题:
在评论列表没有滚动过的情况下,点击列表某一条弹起键盘,列表不会定位到那一项
哪怕稍微滑动一点点,再去点击弹起键盘,都可以直接定位
也就是说sliding_up_panel这个库可能有点问题
在面板初始位置调用ScrollController的任何jumpTo或者animateTo方法都是无效的!
纳闷了半天,怎么回事?
于是又提了一个issue: github.com/akshathjain…
同样的结果,这个库的作者好久都没有更新了,只能自己想办法了
看看源码:
在执行初始化方法的时候 ,给ScrollController注册了一个监听
当面板处于可滚动的状态并且_scrollingEnabled为false的时候,会直接滚动到0的位置
而这个_scrollingEnabled是一个私有变量,并且初始值就是false,这个值会在面板滚动的时候调用onGestureSlide方法从而被重新赋值。
这就是为什么在刚刚打开面板时调用ScrollController的滚动方法不起作用的原因了,它会一直jumpTo(0)
于是我做了一个对其他代码影响范围最小的改动点:改变私有变量_scrollingEnabled的初始值
因为是私有变量,所以还是通过PanelController来注入的方式改变:
void setScrollEnable(bool enable) {
_panelState!.scrollingEnabled = enable;
}
在调用滚动方法前将_scrollEnable的值改为true
@override
void didChangeMetrics() {
// 回复他人的时候滚动调整,延迟300毫秒滑动动画更自然
if (index != -1) {
Future.delayed(Duration(milliseconds: 300), () {
panelController.setScrollEnable(true);
scrollController?.scrollToIndex(index);
});
}
super.didChangeMetrics();
}
无论在面板的初始位置,还是列表滚动一些距离,在键盘弹起后,列表都能滚动定位到预期位置,符合预期~
这个问题具体的表现是这样的,有时候用户会下拉文本框,会连同面板一起下拉,体验不太友好:
这个问题不是很难,但是是一个细节问题
在键盘弹起的时候禁用面板的拖拽功能,键盘消失的时候再恢复面板的拖拽功能
伪代码:
// 面板 SlidingUpPanel( xxx: 0, xxx: 0, xxx: false, xxx: xxx, isDraggable: isDraggable, ); /// 是否显示编辑框 void setShowInput(bool isShow) { isDraggable = !isShow; isShowTextField = isShow; if (mounted) setState(() {}); }
键盘弹起以后,评论面板不可再拖动,符合预期~
这个也是一个预期的问题,因为我们并没有做特殊处理
因为有局部路由的缘故,所以我们应该:
直接上一些伪代码:
在评论详情页的build方法中给context变量赋值
currentNavigatorContext = context;
给内容页面添加WillPopScope
Future<bool> _willPanelPop() async {
// 存在详情页局部路由弹出
if (currentNavigatorContext != null) {
Navigator.popUntil(currentNavigatorContext!, (route) => route.isFirst);
return false;
}
// 评论面板是打开的状态,那么关闭它
if (panelController.isPanelOpen) {
panelController.close();
return false;
}
return true;
}
目前效果符合用户的使用习惯, 符合预期~
因为ios手机自带侧滑手势返回,包括手机物理返回键不是侧滑的android手机,都有这个问题:
这个是不太能接受的,用户本想侧滑返回,但是稍微往下一拉就把面板拉下来了,这体验很差劲
看了一下番茄小说,是侧滑的时候不允许下拉,看到别人能做,我觉得我们也可以~
注意看我们的flutter的NavigatorObserver类的这几个方法
箭头标注的方法它们的调用时机是在:侧滑开始、侧滑取消、页面弹出
思路就是在开始侧滑的时候禁止面板的滑动,取消和弹出的时候再恢复面板的滑动
直接上伪代码:
class CommentDetailNavigator extends NavigatorObserver { @override void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { isDraggable = true; } @override void didStartUserGesture(Route<dynamic> route, Route<dynamic>? previousRoute) { isDraggable = false; panelController.panelPosition = 1.0; } @override void didStopUserGesture() { isDraggable = true; } }
这个代码试了一下好像并不管用,改变了isDraggable需要重新setState页面,其中可能存在什么问题
研究一下源码:
我认为问题是在手势没抬起之前,就算重新build了以后,之前的手势其实仍然生效
也就是说依旧会调用滑动中_onGestureSlide和滑动结束_onGestureEnd的方法
那么解决方法就来了:
在滑动中_onGestureSlide和滑动结束_onGestureEnd方法中添加这一行:
if (!widget.isDraggable) return;
这个可是实时生效的,手势滑动方法中直接return这指定滑不了
该库的作者不重复加这个判断估计也是因为认定了isDraggable为false的时候直接返回的是child对象,不会和手势有任何关系了
可谁曾想还有我现在遇到的这种场景。。。
最后注意:还需要在详情页的TopBar手势监听那里添加判断拦截,不然侧滑TopBar的位置依旧会将面板拉下来
if (!isDraggable) return;
横向侧滑的时候,并不会把面板下拉下来,符合预期~
实现一个发评论看评论的功能其实很简单, 但是能把这一个个细节问题都解决还是挺困难的
我大概完善了如下细节:
简单使用了provider实现了难点问题(不包含业务及逻辑代码)
在该项目sliding_up_panel目录下, 可搜索pengboboer add查看改动点
这篇文章写了好久,如果能帮到你们,希望给个点赞和star~
你们的鼓励是对我最大的肯定~
转自:https://juejin.cn/post/7252171043384246331
作者:彭博博儿
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。