【Flutter】实战问题集锦(二)

1,609 阅读7分钟

ModalBottomSheet底部弹出框设置高度

查看源码会发现ModalBottomSheet被设置了固定高度。唯一解决的办法是copy一份ModalBottomSheet的源码修改高度限制以及文件名。

首先找到入口文件,会发现BottomSheet其实也是路由push的一个页面。

Future<T> hShowModalBottomSheet<T>({
  @required BuildContext context,
  @required WidgetBuilder builder,
  Color backgroundColor,
  double elevation,
  ShapeBorder shape,
  bool isScrollControlled = false,
  bool useRootNavigator = false,
}) {
  ......
  return Navigator.of(context, rootNavigator: useRootNavigator).push(_ModalBottomSheetRoute<T>(
    builder: builder,
    theme: Theme.of(context, shadowThemeOnly: true),
    isScrollControlled: isScrollControlled,
    barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
    backgroundColor: backgroundColor,
    elevation: elevation,
    shape: shape,
  ));
}

然后继续往下找真正要显示的布局代码:_ModalBottomSheetRoute -> _ModalBottomSheetRoute -> _ModalBottomSheet

class _ModalBottomSheetLayout extends SingleChildLayoutDelegate {
  _ModalBottomSheetLayout(this.progress, this.isScrollControlled);

  final double progress;
  final bool isScrollControlled;
    
  @override
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
    return BoxConstraints(
      minWidth: constraints.maxWidth,
      maxWidth: constraints.maxWidth,
      minHeight: 0.0, 
      ///这里就是设置bottomSheet的最大高度了
       maxHeight: isScrollControlled
        ? constraints.maxHeight
        : constraints.maxHeight * 9.0 / 16.0,
    );
  }
  .....
}

代码中maxHeight就是可显示底部弹窗最大高度值,我们可以通过修改期望的高度值达到最终效果。

ListView中item的Container设置高度无效

在使用ListView过程中发现item视图高度无法通过Container设置,只能通过在ListView外层嵌套Container设置高度。如下代码中ListView中Container高度为100,ListView外部Container高度为200,最终效果展示时ListView的列表展示为200。

Container(
  height: 200,
  color: Colors.yellow,
  padding: EdgeInsets.only(top: 10,bottom: 10),
  child: ListView.builder(
    scrollDirection: Axis.horizontal,
    itemBuilder: (context, position) {
      return Container(
        width: 100,
        height: 100,
        color: Colors.blue,
        child: Text("sssss"),
      );
    },
    itemCount: 10,
  ),
),

TabBarView切换Tab

在使用TabBarView做分页UI时遇到不显示的分页视图做了initState操作。

  • 当在Tab1切换至Tab2时:Tab1执行dispose,Tab2执行initState;
  • 当在Tab1切换至Tab3时:Tab1执行dispose,Tab2执行initState同时Tab3执行initState然后Tab2又执行一次initState。

目前在Issue提问来看算是TabBarView的一个bug,虽然类似Android中ViewPager也有预加载分页视图操作至少可以设置缓存数量。尤其是在业务需求中经常需要曝光埋点等工作不应该做初始化操作。此外Flutter中TabBarView调用了两次initState不算是正常操作。由于业务逻辑需求不同这里采用AutomaticKeepAliveClientMixin去KeepAlive对于initState执行不太适用。最终使用mounted做判断阻止initState初始化操作。

@override
  void initState() {
    super.initState();
    SchedulerBinding.instance.addPostFrameCallback((_){
      if (mounted) {
        print("TabBarViewWidget ${widget.name}");
        /// 执行需要的初始化工作
      }
    });
}

Isolate执行spaw方法报错

在执行Isolate执行失败

simple1() {
  Isolate.spawn(test1, "hello");
}
test1(string) {
  print("test1 $string");
}

抛出异常如下

Unhandled Exception: Invalid argument(s): Isolate.spawn expects to be passed a static or top-level function

原因执行的方法是静态才可以通过,修改方法后执行通过。

static test1(string) {
  print("test1 $string");
}

查看源码会发现Isolate的spawn方法为静态,所以传入的方法肯定也需要是静态才合法。

external static Future<Isolate> spawn<T>(
      void entryPoint(T message), T message,
      {bool paused: false,
      bool errorsAreFatal,
      SendPort onExit,
      SendPort onError,
      @Since("2.3") String debugName});

插件库image_picker不回调结果

由于项目使用了基于v1.9.0hotfix版本FlutterBoost导致Android原生环境下onActivityResult回调被拦截最终不能获取到结果。

Android端当前版本image_picker 0.6.4似乎已经解决了这个问题。

另外在iOS使用中需要前置self.window.rootViewController设置否则无法调起相册功能。

self.window.rootViewController = rvc;
[FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:router
                                                        onStart:^(FlutterViewController *fvc) {}];

没有依赖sqflite插件库但报错'fmdb/FMDB.h' file not found

  • 环境:IOS simulator 13.2
  • 版本:sqflite 1.3.0

当时一脸懵逼我都没有使用sqflite插件怎么还报错了,逐个排查后发现是cached_network_image使用了sqflite插件。一个图片缓存库还用到sqlite数据库操作了。

最终解决办法是将import fmdb/FMDB.h改为import FMDB/FMDB.h可通过编译运行。开源作者的注释很清楚是说明ios引用fmdb而在MacOS才引用FMDB太囧了。然后查看了iOS项目下Pods依赖FMDB头文件确实是FMDB.h,但别人运行项目时还是fmdb/FMDB.h才是正常。具体原因不知道是pod版本问题还是其他什么依赖导致,所以目前简单粗暴的做法还是直接改源码import FMDB/FMDB.h。如果有知道正确解决办法的小伙伴请给我留言谢谢啦。

ListView加载图片列表性能问题

问题一:图片列表如果是几千张图片那么第一次进入应用时会非常慢,但第二次进入则不会出现这么的问题。主要原因在第二次进入时图片已经在缓存中可以直接加载无需做网络请求下载操作,这个缓存是通过PaintingBinding.instance.imageCache实现。

问题二:listview若itemBuilder中只有Image时会导致进入列表时会加载所有Item,而若非只有Image时则不会加载所有Item。其实原因在于Image初始化没有Size导致列表开始加载就是加载所有图片,所有图片同时进行请求下载任务必定会导致卡顿。

解决办法当然是为Image组件外部设置Size,避免首帧加载列表所有图片。

Flutter产物编译失败问题

在已有项目中增加Flutter功能模块,相应的通过脚本创建FlutterModule。

flutter create -t module xxx(moduleName)

然后编译Flutter项目生成aar包依赖。

./gradlew assembleRelease

当出现Android could not get unknown property for 'applicationVariants'报错。 这是因为FlutterSDK中gradle脚本只支持application打包,找到flutter/packages/flutter_tools/gradle/flutter.gradle文件需要修改gradle脚本内容

如下的代码做修改:

buildTypes {
    release {
        applicationVariants.all { variant ->
            appendVersionName(variant, defaultConfig)
        }
    }
}

修改为libraryVariants

buildTypes {
    release {
       libraryVariants.all { variant ->
            appendVersionName(variant, defaultConfig)
        }
    }
}

若出现资源文件加载失败则需要通过修改Maven地址或是高级上网解决。

 Could not get resource 'http://download.flutter.io/io/flutter/flutter_embedding_release/1.0.0-e1e6ced81d029258d449bdec2ba3cddca9c2ca0c/flutter_embedding_release-1.0.0-e1e6ced81d029258d449bdec2ba3cddca9c2ca0c.jar'.

分别修改aar_init_script.gradle、reslove_dependecies.gradle、flutter.gradle中的maven地址,可以修改为https://storage.googleapis.com/download.flutter.io。

Flutter编译产物aar文件中找不到libflutter.so以及asset相关的文件内容。

外接纹理创建崩溃问题

在做外接纹理时遇到Glide加载图片资源后通过onResourceReady回调准备绘制bitmap抛出异常。

PlatformException(error, Can't create handler inside thread Thread[glide-disk-cache-thread-0,5,main]

  • 原因:onResourceReady回调为异步,PluginRegistry.Registrar的textures执行createSurfaceTexture不能异步执行,因此导致抛出异常。

我们可以预先创建TextureRegistry.SurfaceTextureEntry若图片加载失败可对其进行release操作。另外提一点Plugin的onMethodCall回调是主线程。

/// FlutterJNI注册textureId是在主线程。
 @UiThread
  public void markTextureFrameAvailable(long textureId) {
    ensureRunningOnMainThread();
    ensureAttachedToNative();
    nativeMarkTextureFrameAvailable(nativePlatformViewId, textureId);
  }

HTTP request failed, statusCode: 403

Image.network加载http://前缀图片时会报错无法获取图片资源。 开始以为Flutter的Image默认不支持http图片下载看了这个issue才知道可能这张图片地址并非原图片地址中间可能存在二次请求所以会出现403错误。

issue

Flutter插件项目Android模块只有Example项目

在原有的纯Flutter项目添加原生插件功能时,打开Android工程只找到Example项目插件项目却一直找不到。

最后发现找不到的插件项目的原因是.matadata文件原设置为module所以无法应用到插件项目。通过修改project_type:plugin让原先的纯Flutter项目转变为插件项目,实际上也是简单的配置问题😂。

单击卡顿问题

业务开发中有提到说列表Cell点击后打开第二个页面慢,当时以为是启动第二个页面过程中做了一些耗时操作请求或是第二个页面启动时做耗时操作等引起的问题。最终团队小伙伴发现是因为GestureDetector同时实现了onTap和onDoubleTap导致单击反馈慢。原先是为了规避onTap和onLongPress两者操作冲突所以加了onDoubleTap空方法,却导致了点击反馈延后。 解决办法只能是舍弃onDoubleTap,重写onTapDown和onTapUp规避长按和单击存在的手势冲突。

原因是双击手势中加了一个延迟300ms计时器,在这过程中会hold手势事件若延迟时间到为处理双击则会释放relese释放手势操作。而释放之后才由onTap去消费手势事件,就导致之前说点击延迟卡顿的假象。

const Duration kDoubleTapTimeout = Duration(milliseconds: 300);
void _startDoubleTapTimer() {
_doubleTapTimer ??= Timer(kDoubleTapTimeout, _reset);
 }