【个人笔记】Flutter很好用,but……
前言
为什么有这篇文章?
这篇文章可以说是个人Flutter问题随笔吧,虽然Flutter可以方便的做到跨平台,但是毕竟是新项目,功能没那么全面也是意料之中,所以在此记录跟踪一下目前在使用Flutter过程中遇到的问题,目前的解决方案,并跟踪记录一下。
问题列表(持续更新):
- Flutter 中不支持异步测绘大量文字。
Flutter IOS 后台音频播放(已经有了第三方)。- Flutter 中 NestedScrollView 中存在折叠头、子列表等情况下的滑动等异常。
Hero 跳转之后的图片如果在折叠布局中被折叠隐藏了,那么Hero的退出动画无法正常播放,并且原图片无法显示(flutter的master分支已修复,stable版本hotfix已发布)- listView 不支持按index跳转
- Simulation的value会返回Infinite
- hintText不能垂直居中?
正文
1. Flutter 中不支持异步测绘大量文字
这个源自于个人练手APP,在开发阅读模块过程中遇到了这么一个问题:
如果想要自定义文字间距,段落间距等,那么自然就要使用textPainter自己绘制文字。但是由此引发出一个问题:如果有上万个文字,那么测绘时间会很长。自然会阻塞主进程。
解决这个问题,第一个想法自然就是使用异步处理,那么flutter 中的异步方案有哪些呢?
- Future(包括await async那些东西)
- isoloate
按照官方文档,大量计算东西应该放到Isolate中,所以,自然而然地,我们将textPinter测绘方法放到Isolate中执行,但是呢,执行报错:native function not found
原因就是UI包的类只允许在Flutter的UI Isolate中执行。
此题无解,完结撒花,各回各家,各找各妈(才怪)
解决方案一:Plugin
这个方法的思路来自于官方开发人员的回答:
right now you would have to write your own line-breaking logic - perhaps as a plugin. We don't currently have any API for doing text layout in a separate isolate. @GaryQian might have some other idea about this too.
所以说,我们看下官方的Plugin是怎么实现的。
在 Plugin工程中,有这么一个: android_alarm_manager ,这个Plugin中的AlarmService.java文件中,有这么一个方法:startBackgroundIsolate(要素察觉+1)
通过阅读源码,我们得知,其实说白了就是新建一个FlutterNativeView ,这个FlutterNativeView 不负责渲染界面,只负责当后台线程来用。同样的道理,我们也可以用同样的方法来处理我们的需要运行在UI Isolate的异步任务。(至于为什么新建一个FlutterNativeView ,就能实现这个需求,粗略的说下是因为,每次新建FlutterNativeView,其实都会新建一些TaskRunner,其中就包含UI 的TaskRunner,总之有兴趣的可以搜下Flutter中的TaskRunner机制了解下)
不过,其实不用自己实现注册,有这么一个第三方:flutter_isolate可以做到这点,用法跟普通的isolate差不多,所以解决方式也很简单:找到原来使用Isolate的那段代码,把Isolate.spawn替换为FlutterIsolate.spawn即可。不过经过测试,在1.10版本上会报错,所以想使用这个方式的话,还是切回stable 分支使用吧。
解决方案二:分割为多个小任务(个人感觉不太推荐这种方式,感觉不可控,毕竟假如小任务的执行时间也很长咋办)
这种方式说白了就是每测绘完一行,就提交一次,而不是整体测绘完之后再提交结果。
举个小例子:
Future(() async {
var result;
for (var i = 0; i < 1000000; ++i) {
result = 'result is $i';
await Future.delayed(Duration.zero);
}
print(result);
});
用这种方式计算就不会卡UI
解决方案三:目前这个问题已经加入到里程碑了,在未来的某天应该就有解决方案
2. Flutter IOS 后台音频播放
也就是像音乐播放器那样,会有个通知显示进度,即使在桌面也可以播放音乐这种
目前只有Android的实现,IOS这块目前还没有方案……%
3. Flutter 中 NestedScrollView 中存在折叠头、子列表等情况下的滑动等异常
找了半天才发现有个大佬已经搞定了这个问题:
使用extended_nested_scroll_view即可解决这个问题,在此记录一下
4. Hero 跳转之后的图片如果在折叠布局中被折叠隐藏了,那么Hero的退出动画无法正常播放,并且原图片无法显示
这个描述可能有些抽象,具体情况以一个例子为例描述:
一、首页放一个图片,给他设置点击跳转详情页,并设置hero动画。
二、将图片设置为SliverAppBar的FlexibleSpaceBar的widget中的内容,然后上拉页面,折叠隐藏掉SliverAppBar。
三、按后退键退出详情页,触发hero的关闭动画。
按这三步执行之后,首页原来那张图片就变成空白,而且无法点击。
issue:
所以切换到master分支或者dev分支(问题修复版本v.1.10.14),或者降低稳定版的版本。
5. listView 不支持按index跳转
下面是我搜索出的内容:
stackoverflow.com/questions/5…
上面的实现方法各有千秋吧,整理一下主要的缺陷点,以后可以根据实际情况使用:
- stackOverflow 中提到了两种方法:一是使用index_list_view,这个稍后统一分析。二是 gist.github.com/debuggerx01… ,不过有老哥将它放在带评论的列表上,结果性能比较差,简介文章中也讲明了,如果处理不好确实可能存在性能问题。
- indexed_list_view stackOverflow中也提了,这个适用于无限列表的情况。
- scroll_to_index 代码稍微有点侵入性(
其实这个不是重点),其次没有jump方法,scroll时长好像也没什么效果,所以如果列表比较大而跳转距离也很大(比如说凡人修仙传的章节那种……),那么动画效果比较感人。(PS:好像这个滑动时间长的问题(github.com/flutter/flu…)已经修复了,虽然在这个issue中没有说,但是我自己在v1.10.14上发现好像现在scroll动画能在很短时间内滑动到目标位置了) - flutter_widgets 谷歌大大的产品(不过不是官方正式控件),基本满足需求,方法也很简单,jump方法的什么也有。但是,如果把它放到NestScrollView中搞视差滑动的页面的时候,会导致整体无法滑动,只能滑动ScrollablePositionedList中的内容……(其实原因就是因为自定义了controller),而且如果存在ExpansionTile这种可伸缩的布局,通过伸缩方式改变了item的高度,ScrollablePositionedList并不会更新高度,而是还是以改变前高度为准进行index跳转。
现在正在尝试解决ScrollablePositionedList中的视差滑动问题,(得想个办法让NestScrollView届的到ScrollablePositionedList的controller) 在NestScrollView中是通过设置PrimaryScrollController,让innerController管理body滑动的,所以使用这个innerController来替代ScrollablePositionedList的controller即可解决无法滑动的问题,但是随之而来的是一个新的问题,现在按index跳转,会导致body触发滑动,进而直接折叠掉SliverAppBar……………如何让index跳转的时候,body在NestScrollView中的位置不动,只在原地实现自身的index跳转,只有手动滑动的时候才允许改变body在NestScrollView中的位置,目前想法是在SliverAppBar的floating、pinned等控制滑动方式的方法上看看有没有突破口。 不搞了,现在挺好的。直接满屏展示,实在不行加个跳转到顶部按钮,我感觉蛮合理的。(大雾)
改变高度这个,目前采用设置addPersistentFrameCallback 方式持续监听。本以为应该性能比较差,但是测试了一下,好像frame没啥变化,所以应该没啥问题。
6. ClampingScrollSimulation 的value中会返回Infinite
例如下面这段代码
ClampingScrollSimulation simulation;
AnimationController animationController;
simulation = ClampingScrollSimulation(
position: mTouch.dy,
velocity: details.velocity.pixelsPerSecond.dy,
tolerance: Tolerance.defaultTolerance,
);
animationController
..addListener(() {
currentState = STATE.STATE_ANIMATING;
canvasKey.currentContext.findRenderObject().markNeedsPaint();
/// 需要在这里判断一下数据是否有效的……
if (!animationController.value.isInfinite &&
!animationController.value.isNaN) {
currentAnimationPage.onTouchEvent(TouchEvent(
TouchEvent.ACTION_MOVE, Offset(0, animationController.value)));
}
})
..addStatusListener((status) {
switch (status) {
case AnimationStatus.dismissed:
break;
case AnimationStatus.completed:
currentState = STATE.STATE_IDE;
currentAnimationPage
.onTouchEvent(TouchEvent(TouchEvent.ACTION_UP, Offset(0, 0)));
currentTouchData = TouchEvent(TouchEvent.ACTION_UP, Offset(0, 0));
break;
case AnimationStatus.forward:
case AnimationStatus.reverse:
currentState = STATE.STATE_ANIMATING;
break;
}
});
animationController.animateWith(simulation);
讲道理,不该有非法数据返回啊……总之在这里记录下
7. 如果textField设置了hintText,它就不能垂直居中
这个是真的完全没点头绪了,虽然flutter中的issue中有不少跟我遇到一样情况的,但是它好像将baseline设置一下就行了,但在我这不生效…… 而且发现,好像再加上suffixIcon就恢复正常…… 强行解决也不是没办法,加padding嘛,但是感觉好low
先记录一下
题外话
有些同学可能对我为什么需要测量上万字感到困惑,所以在这简单的提下:
测绘上万字这个情况来自于我想把自己原来的一个Android原生小说阅读器,转换为flutter实现(主要是想实现那帮仿真、滚动等翻页动画啥的,练习一下flutter的canvas操作),小说阅读器的基本思路参考自这个项目:任阅。
思路基本是这样的:
基本功能是:上一页,下一页,上一章,下一章,章内跳页。
因为要考虑上一章下一章切换以及章内跳转到多少页的这些功能,所以自然以章为基础单位,章内部包含页,取的时候把章的数据拿出来,然后从章中页数据中取出要展示的页的数据,并将其绘制到canvas上。
所以,缓存计算也是计算的章的内容,所以当从服务器下载下来新的章的时候,就有可能遇到上万字的章的情况。
可能也有同学说:直接按页缓存不就行了,每次只缓存几页的内容,那肯定不会遇到需要一次性测量上万字的情况。但是如果纯粹按页缓存的话,如果遇到这两种情况就傻眼了:1. 如果有个用户打开一本新的小说,直接跳到第十章,然后按了上一页。 2. 章节内跳转到第n页(n>缓存页数)。
因为标点符号、全角、特殊字符等非普通汉字的存在,所以我们是无法直接得知每页展示的内容,自然无法在测量完整章之前得知某页的展示内容,所以要支持上面提到2个情况,必然要测量分页整章内容。
当然,可能我这个思路也不是最优解,如果有更好的方案,希望各位不吝指教
转载自:https://juejin.cn/post/6844903958641639437