likes
comments
collection
share

【个人笔记】Flutter很好用,but……

作者站长头像
站长
· 阅读数 30

前言

为什么有这篇文章?

这篇文章可以说是个人Flutter问题随笔吧,虽然Flutter可以方便的做到跨平台,但是毕竟是新项目,功能没那么全面也是意料之中,所以在此记录跟踪一下目前在使用Flutter过程中遇到的问题,目前的解决方案,并跟踪记录一下。

问题列表(持续更新):

  1. Flutter 中不支持异步测绘大量文字
  2. Flutter IOS 后台音频播放(已经有了第三方)。
  3. Flutter 中 NestedScrollView 中存在折叠头、子列表等情况下的滑动等异常
  4. Hero 跳转之后的图片如果在折叠布局中被折叠隐藏了,那么Hero的退出动画无法正常播放,并且原图片无法显示(flutter的master分支已修复,stable版本hotfix已发布)
  5. listView 不支持按index跳转
  6. Simulation的value会返回Infinite
  7. hintText不能垂直居中?

正文

1. Flutter 中不支持异步测绘大量文字

这个源自于个人练手APP,在开发阅读模块过程中遇到了这么一个问题:

如果想要自定义文字间距,段落间距等,那么自然就要使用textPainter自己绘制文字。但是由此引发出一个问题:如果有上万个文字,那么测绘时间会很长。自然会阻塞主进程。

解决这个问题,第一个想法自然就是使用异步处理,那么flutter 中的异步方案有哪些呢?

  1. Future(包括await async那些东西)
  2. 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

解决方案三:目前这个问题已经加入到里程碑了,在未来的某天应该就有解决方案

github.com/flutter/flu…

2. Flutter IOS 后台音频播放

也就是像音乐播放器那样,会有个通知显示进度,即使在桌面也可以播放音乐这种

目前只有Android的实现,IOS这块目前还没有方案……%

3. Flutter 中 NestedScrollView 中存在折叠头、子列表等情况下的滑动等异常

找了半天才发现有个大佬已经搞定了这个问题:

使用extended_nested_scroll_view即可解决这个问题,在此记录一下

4. Hero 跳转之后的图片如果在折叠布局中被折叠隐藏了,那么Hero的退出动画无法正常播放,并且原图片无法显示

这个描述可能有些抽象,具体情况以一个例子为例描述:

一、首页放一个图片,给他设置点击跳转详情页,并设置hero动画。

二、将图片设置为SliverAppBar的FlexibleSpaceBar的widget中的内容,然后上拉页面,折叠隐藏掉SliverAppBar。

三、按后退键退出详情页,触发hero的关闭动画。

按这三步执行之后,首页原来那张图片就变成空白,而且无法点击。

issue:

github.com/flutter/flu…

github.com/flutter/flu…

所以切换到master分支或者dev分支(问题修复版本v.1.10.14),或者降低稳定版的版本。

5. listView 不支持按index跳转

下面是我搜索出的内容:

stackoverflow.com/questions/5…

pub.dev/packages/in…

pub.dev/packages/sc…

pub.dev/packages/fl…

上面的实现方法各有千秋吧,整理一下主要的缺陷点,以后可以根据实际情况使用:

  1. stackOverflow 中提到了两种方法:一是使用index_list_view,这个稍后统一分析。二是 gist.github.com/debuggerx01… ,不过有老哥将它放在带评论的列表上,结果性能比较差,简介文章中也讲明了,如果处理不好确实可能存在性能问题。
  2. indexed_list_view stackOverflow中也提了,这个适用于无限列表的情况。
  3. scroll_to_index 代码稍微有点侵入性(其实这个不是重点),其次没有jump方法,scroll时长好像也没什么效果,所以如果列表比较大而跳转距离也很大(比如说凡人修仙传的章节那种……),那么动画效果比较感人。(PS:好像这个滑动时间长的问题(github.com/flutter/flu…)已经修复了,虽然在这个issue中没有说,但是我自己在v1.10.14上发现好像现在scroll动画能在很短时间内滑动到目标位置了)
  4. 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个情况,必然要测量分页整章内容。

当然,可能我这个思路也不是最优解,如果有更好的方案,希望各位不吝指教