服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

3月Flutter小报|读小报,涨知识

日期: 来源:闲鱼技术收集编辑:风逆

本期内容

  1. 1. Flutter如何Mock MethodChannel进行单元测试

  2. 2. Flutter如何获取键盘的完整高度

  3. 3. Flutter快速实现新手引导气泡

Flutter如何Mock MethodChannel进行单元测试

在做Flutter单元测试的时候,有时候我们会遇到Flutter Widget的某个方法调用了Platform的方法,这时候就需要Mock这个MethodChannel来消除依赖,否则测试用例执行到Channel的方法就会抛出异常。

1. 获取TestDefaultBinaryMessenger

在测试环境下,Flutter给我们提供了TestDefaultBinaryMessenger来拦截MethodChannel,所以我们需要先获取到它。

/// 依赖WidgetTester,需要在测试用例中获取
testWidgets('one test case', (widgetTester) async {
  final TestDefaultBinaryMessenger messenger = 
    widgetTester.binding.defaultBinaryMessenger;
});

/// 通过单例获取,写在setUp中可以在所有测试用例执行前运行
setUp(() {
    final TestDefaultBinaryMessenger messenger = 
      TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger;
}

2. Mock Flutter与Platform间的相互调用

  • • Flutter调用Platform方法:

    • • TestDefaultBinaryMessenger#setMockMethodCallHandler,第一个参数是需要拦截的MethodChannel,第二个参数是Function表示Mock调用。

    • • TestDefaultBinaryMessenger#allMessagesHandler,和上面类似,但这里是拦截所有的MethodChannel,并且,此项设置后,setMockMethodCallHandler将不生效

  • • Platform调用Flutter方法:

    • • TestDefaultBinaryMessenger#handlePlatformMessage,第一个参数是MethodChannel名字,第二个参数是传给Flutter编码后的MethodCall,第三个参数是Flutter处理后结果的回调。

3. 示例

allMessagesHandler使用举例,假如我们有这样一个MethodChannel

class PipeLinePlugin {
  PipeLinePlugin({this.pipeLineId, this.onTextureRegistered}) {
    _channel = MethodChannel('method_channel/pipeline_$pipeLineId');
    
    /// 调用start后,Platform会回调registerTextureId
    _channel.setMethodCallHandler((call) {
      if (call.method == 'registerTextureId' && call.arguments is Map) {
        int textureId = (call.arguments as Map)?['textureId'];
        onTextureRegistered?.call(textureId);
      }
    });
  }

  final String pipeLineId;
  final MethodChannel _channel;
  final Function(int textureId)? onTextureRegistered;

  Future<bool?> start() async {
    final bool? result = await _channel.invokeMethod('start', <String, dynamic>{'id': pipeLineId}) as bool?;
    return result;
  }
  
  Future<bool?> stop() async {
    final bool? result = await _channel.invokeMethod('stop', <String, dynamic>{'id': pipeLineId}) as bool?;
    return result;
  }
}

我们可以这样Mock它,然后我们的测试用例就能正常执行了。

const StandardMethodCodec methodCodec = StandardMethodCodec();
/// 如果channel名字是按规则生成的,可以拦截所有的MethodChannel,再从中找到你需要Mock的MethodChannel
messenger.allMessagesHandler = (String channel, MessageHandler? handler, ByteData? message) async {
  final MethodCall call = methodCodec.decodeMethodCall(message);
  if (channel.startWith('method_channel/pipeline')) {
    if (call.method == 'start') {
      /// Platform收到start后,需要回调registerTextureId
      final platformResultCall = MethodCall('registerTextureId', {'textureId': 0});
      messenger.handlePlatformMessage(channel, 
        methodCodec.encodeMethodCall(platformResultCall), null);
    }
  }

  /// Flutter的MethodCall统一返回true
  return methodCodec.encodeSuccessEnvelope(true);
}

Flutter如何获取键盘的完整高度

我们知道在Flutter中获取键盘高度可以通过向WidgetsBinding注册监听,当键盘弹出或消失时会回调didChangeMetrics方法。需要注意的是,在Flutter3新增IOS键盘动画后,IOS会和Android一样回调didChangeMetrics多次,并且每次回调中MediaQueryData.fromWindow(window).viewInsets.bottom的值都是此时键盘冒出来的高度,即键盘实时高度。如果我们想获取键盘的完整高度,只需要一直取和上次相比的最大值,然后保存下来就可以了。

/// Flutter3 
/// 获取键盘高度
@override
void didChangeMetrics() {
  final bottom = MediaQueryData.fromWindow(window).viewInsets.bottom;
  // 键盘存在中间态,回调是键盘冒出来的高度
  keyboardHeight = max(keyboardHeight, bottom);
  if (bottom == 0) {
    isKeyboardShow = false;
  } else if (bottom == keyboardHeight || keyboardHeight == 0) {
    isKeyboardShow = true;
  } else {
    isKeyboardShow = null;
  }
  // 键盘完全收起或展开再刷新页面
  if (isKeyboardShow != null && _preKeyboardShow != isKeyboardShow) {
    _keyboardStateNotifier.notifyListeners();
  }
  if (bottom < keyboardHeight) {
    _sp?.setDouble(KEYBOARD_MAX_HEIGHT, keyboardHeight);
  }
}

/// Flutter3之前,获取键盘高度
@override
void didChangeMetrics() {
  final bottom = MediaQueryData.fromWindow(window).viewInsets.bottom;
  /// ios点击键盘表情时,键盘高度会增加,后面点拼音后也回不到这个高度了
  if (Platform.isIOS) {
    /// ios键盘有两种高度,但不存在中间态,回调就是键盘高度
    isKeyboardShow = bottom > 0;
    if (isKeyboardShow) {
      keyboardHeight = bottom;
    }
  } else {
    /// Android键盘存在中间态,回调是键盘冒出来的高度
    keyboardHeight = max(keyboardHeight, bottom);
    if (bottom == 0) {
      isKeyboardShow = false;
    } else if (bottom == keyboardHeight || keyboardHeight == 0) {
      isKeyboardShow = true;
    } else {
      isKeyboardShow = null;
    }
  }
  // 键盘完全收起或展开再刷新页面
  if (isKeyboardShow != null && _preKeyboardShow != isKeyboardShow) {
    _keyboardStateNotifier.notifyListeners();
  }
  if (bottom < keyboardHeight) {
    _sp?.setDouble(KEYBOARD_MAX_HEIGHT, keyboardHeight);
  }
}

例如我们想在键盘上做一个输入框,只需要给输入框底部添加一个和键盘实时高度相同的Padding,就能做到像Scaffold#bottomSheet一样跟随键盘动画的效果了


Flutter快速实现新手引导气泡

当我们上线一个新功能时经常需要做个气泡引导用户使用,但如何在不改变布局的情况下给Widget加上气泡呢? 我们可以通过OverlayEntry在更高的页面层级上插入气泡,同时根据相对的Widget位置来计算气泡的位置,具体代码实现如下。 需要注意的是,虽然我们在Widget的dispose中会销毁气泡,但如果Widget是页面并且页面有弹窗,气泡会出现在弹窗上,所以使用的时候需要在弹窗前主动销毁气泡。

/// 以相对widget为中心扩展刚好容纳bubble大小的Rect
Rect _expandRelativeRect(BuildContext relativeContext, Size bubbleSize, bool allowOutsideScreen) {
  if ((relativeContext as Element)?.isElementActive != true) {
    return Rect.zero;
  }

  // 找到相对widget的位置和大小
  final RenderBox renderBox = relativeContext.findRenderObject();
  final Offset offset = renderBox.localToGlobal(Offset.zero);
  final Size size = renderBox.size;
  // 以相对widget为中心扩展刚好容纳bubble大小的Rect
  final rect = Rect.fromLTWH(offset.dx - bubbleSize.width, offset.dy - bubbleSize.height,
                             bubbleSize.width * 2 + size.width, bubbleSize.height * 2 + size.height);
  if (allowOutsideScreen) {
    return rect;
  } else {
    final screenSize = MediaQueryData.fromWindow(ui.window).size;
    final screenRect = Rect.fromLTWH(0, 0, screenSize.width, screenSize.height);
    return rect.intersect(screenRect);
  }
}
/// 构建气泡Entry
OverlayEntry bubbleEntry = OverlayEntry(builder: (bubbleContext) {
  Rect rect = _expandRelativeRect(relativeContext, bubbleSize, allowOutsideScreen);
  return Positioned.fromRect(
      rect: rect,
      child: Align(
          alignment: alignment,
          child: SizedBox(
            child: bubble.call(bubbleContext),
            width: bubbleSize.width,
            height: bubbleSize.height,
          )));
});
Overlay.of(context).insert(bubbleEntry);

/// 关闭气泡
VoidCallback hideBubbleCallback = () {
  if (!bubbleEntry.mounted) {
    return;
  }
  bubbleEntry.remove();
};

相关阅读

  • 牛春格破赛会纪录夺冠

  •   据新华社电 中国田径街头巡回赛2023年度首场比赛22日在厦门中山路举行。代表西安体育学院参赛的牛春格创造女子撑杆跳高赛会纪录并夺冠,同时刷新了自己的个人最好成绩(PB)
  • 影驰 x Keychron 星曜娘联名键盘上架,首发 499 元

  • IT之家是科技媒体中的老牌一哥了,我不仅每天都看,还会转载他家的笔记本新闻。手机、笔电等数码新品的资讯,去他家的App和网站上看准没错。今天就向大家强烈推荐IT之家公众号,另
  • 电脑键盘失灵怎么解决?

  • 电脑键盘失灵可能有多种原因,以下是一些基本的解决方法:脑键盘失灵1. 重启电脑:有时候键盘失灵仅仅是个小故障,电脑重启后就可以解决问题。2. 检查键盘连接:检查键盘是否连接到电
  • 微信键盘Mac版上线

  • 之前我在《好物推荐:微信键盘》就曾经推荐过微信键盘iOS版,时间是去年的12月20日。从那天开始,我在手机上就一直使用微信键盘,并且从此就盼望着微信团队尽快推出Mac版本和Window

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四
  • 美国对华2000亿关税清单,到底影响有多大?

  • 1 今天A股大跌,上证最大跌幅超过2%。直接导火索是美国证实计划对华2000亿美元产品加征25%关税。 听起来,2000亿美元数目巨大,我们来算笔账。 2000亿美元,按现在人民币汇率

最新文章

  • 从新React文档看未来Web的开发趋势

  • 作者 | Luke Twomey 译者 | 核子可乐 策划 | 丁晓昀 新的 React 文档终于到来,目前已经通过全新 React.dev 域与广大开发者见面。长久以来,官方文档是一直是新手们学习
  • 2023 年,我建议创业公司选择 Flutter

  • 作者 | CHRISTIAN FINDLAY 译者 | 核子可乐 策划 | 丁晓昀 作为一家初创企业,为自己的首款应用程序选择正确技术堆栈无疑至关重要。您的具体技术选择将直接影响到产品
  • 群殴特斯拉

  • 前几天,马老板的星舰首飞在半空中炸了,作为一个不是那么热血的科幻爱好者,我对这事儿不是太关心,但大家发现星舰炸了之后,马老板的另外一家小公司特斯拉,股价也炸了:当天收盘,大跌9.
  • 河北高校新增本科专业61个,撤销29个

  • 高校专业调整,透出哪些新信号日前,教育部公布了2022年度普通高等学校本科专业备案和审批结果。河北高校新增本科专业61个,其中27所高校新增56个备案本科专业,5所高校新增5个审批
  • Spring Boot配置保存日志文件

  • 关注我,回复关键字“spring”,免费领取Spring学习资料。springboot日志配置:springboot默认日志是打印再console中的,不会保存在文件中。我们项目上线肯定要保存日志用于分析的
  • 3月Flutter小报|读小报,涨知识

  • 本期内容1. Flutter如何Mock MethodChannel进行单元测试2. Flutter如何获取键盘的完整高度3. Flutter快速实现新手引导气泡Flutter如何Mock MethodChannel进行单元测试在做F