【Flutter】HitTestBehavior想点哪里点哪里

点击事件响应

点击组件中的HitTestBehavior属性支持三个值:opaquetranslucentdeferToChild。其在命中测试起到一定作用可改变原有命中逻辑从而是实现不同点击触发事件。

属性值

HitTestBehaviorRenderProxyBoxWithHitTestBehavior有具体实现应用场景。

/flutter/packages/flutter/lib/src/rendering/proxy_box.dart:RenderProxyBoxWithHitTestBehavior

  @override
  bool hitTest(BoxHitTestResult result, { required Offset position }) {
    bool hitTarget = false;
    if (size.contains(position)) {
      hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position); // 若behavior 等于HitTestBehavior.opaque可命中
      if (hitTarget || behavior == HitTestBehavior.translucent) { //  若behavior等于HitTestBehavior.translucent可命中
        result.add(BoxHitTestEntry(this, position));
      }
    }
    return hitTarget;
  }

  @override
  bool hitTestSelf(Offset position) => behavior == HitTestBehavior.opaque;

在检查子节点命中情况时判断是否opaque,在检查自身命中时判断translucent,因此可知HitTestBehavior.opaque>HitTestBehavior.translucent>HitTestBehavior.deferToChild

由于基础组件对于hitTesthitTestChildrenfalse因此存在将事件消费情况(例如SizedBox无子节点情况下默认false)。为了验证HitTestBehavior可行性就需要自定义一个组件重写hitTesthitTestChildren方法。这里以ColoredBox为基础自定义组件重写_RenderColoredBox的命中判定默认都为false

  @override
  bool hitTest(BoxHitTestResult result, {required Offset position}) {
    return false;
  }

  @override
  bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
    return false;
  }

Stack中设置两个重叠子节点:

  1. 默认情况下点击组件behaviorHitTestBehavior.deferToChild根据点击组件子节点命中测试要求而定
  2. 当为HitTestBehavior.deferToChild情况下,点击事件由最上层子节点响应
  3. 当为HitTestBehavior.translucent情况下,点击事件都透传所有子节点都响应
  4. 当为HitTestBehavior.deferToChild情况下,点击事件无响应(因为自定义_ColoredBoxWithNoHitTest忽略了命中测试)
Stack(
      alignment: Alignment.center,
      children: [
        Listener(
            behavior: value,
            onPointerDown: (down) {
              showSnackBarMsg(context, 'onPointerDown -> Listener -> 外',
                  clear: false, duration: const Duration(milliseconds: 500));
            },
            child: _ColoredBoxWithNoHitTest(
              color: Colors.red.withOpacity(0.5),
              child: const SizedBox(
                height: 200,
                width: 250,
              ),
            )),
        Listener(
          behavior: value,
          onPointerDown: (down) {
            showSnackBarMsg(context, 'onPointerDown -> Listener -> 内',
                clear: false, duration: const Duration(milliseconds: 500));
          },
          child: _ColoredBoxWithNoHitTest(
            color: Colors.red.withOpacity(0.5),
            child: const SizedBox(
              height: 100,
              width: 150,
            ),
          ),
        ),
      ],
    );

在使用HitTestBehavior也需要注意到所作用节点是否支持忽略命中,因为很多情况下有些组件默认情况下实现hitTesttrue状态。

实战举例

一个布局实现如下:最外层Container设置边框可视化点击区域,内部子节点是带有GestureDetectorContainer无边框无背景色其内部子节点有ImageText等。

Container(
      decoration: buildBoxDecorationBorder(),
      child: GestureDetector(
        // behavior: HitTestBehavior.deferToChild, // 设置后点击空白区域无响应
        behavior: HitTestBehavior.translucent,  // 设置后点击空白区域有响应
        onTap: () {
          showSnackBarMsg(context, 'onTap -> GestureDetector -> Container');
        },
        child: Container(
          alignment: Alignment.center,
          height: 150,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              Image.asset('images/img_640_640.jpg', width: 50),
              const Text(
                "文本区域\n"
                "文本区域\n"
                "文本区域\n"
                "文本区域\n",
                style:
                    TextStyle(color: Colors.white, backgroundColor: Colors.red),
              ),
              ColoredBox(
                color: Colors.red.withOpacity(0.5),
                child: const SizedBox(
                  width: 150,
                  height: 100,
                  child: Center(
                    child: Text(
                      "behavior是opaque或translucent\n点击空白区域才能响应",
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ),
              )
            ],
          ),
        ),
      ),
    );

  1. 默认情况下点击子节点Container内部子组件可以响应点击事件
  2. 点击子节点Container内部空白区域无法响应点击事件
  3. 修改GestureDetectorbehaviorHitTestBehavior.translucent点击边框内任务区域都能响应点击事件

分析缘由

Container是复合组件由多种其他组件嵌套而成,例如配置color会嵌套ColoredBox,增加边框decoration会嵌套DecoratedBox

属性

ColoredBox内部实现_RenderColoredBox,它的命中测试逻辑是由`RenderProxyBoxWithHitTestBehavior`判断

/flutter/packages/flutter/lib/src/rendering/proxy_box.dart:RenderProxyBoxWithHitTestBehavior

  @override
  bool hitTest(BoxHitTestResult result, { required Offset position }) {
    bool hitTarget = false;
    if (size.contains(position)) {
      hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
      if (hitTarget || behavior == HitTestBehavior.translucent) {
        result.add(BoxHitTestEntry(this, position));
      }
    }
    return hitTarget;
  }

  @override
  bool hitTestSelf(Offset position) => behavior == HitTestBehavior.opaque;

是否命中测试以`hitTestChildren`作为判断依据,因为默认情况下`behavior`是`deferToChild`。一般情况而言子节点命中测试都是`true`,所以有`Color`的`Container`是一般是命中测试的。
非也非也阅读_RenderColoredBox源码可知默认情况下behaviorHitTestBehavior.opaque状态,因此触摸组件监听时可以直接通过命中测试。

/flutter/packages/flutter/lib/src/widgets/basic.dart:_RenderColoredBox

class _RenderColoredBox extends RenderProxyBoxWithHitTestBehavior {
  _RenderColoredBox({ required Color color })
    : _color = color,
      super(behavior: HitTestBehavior.opaque);
      ...
}

属性

DecoratedBox内部实现了RenderDecoratedBoxhitTestSelf方法实现由BoxDecoration接管,从内部实现看命中测试逻辑可知

/flutter/packages/flutter/lib/src/painting/box_decoration.dart:BoxDecoration

  @override
  bool hitTest(Size size, Offset position, { TextDirection? textDirection }) {
    ...
    switch (shape) {
      case BoxShape.rectangle: // 矩形求边框是否在范围内
        if (borderRadius != null) {
          final RRect bounds = borderRadius!.resolve(textDirection).toRRect(Offset.zero & size);
          return bounds.contains(position);
        }
        return true;
      case BoxShape.circle: // 圆形求半径是否在命中范围内
        final Offset center = size.center(Offset.zero);
        final double distance = (position - center).distance;
        return distance <= math.min(size.width, size.height) / 2.0;
    }
  }

因此设置了边框的Container只要在边框范围内命中测试都是true

小总结

如上所知当Container没有设置ColorDecoratedBox属性时若要让Container整体命中测试就必须为点击组件设置为translucent或者opaque,相反则只能响应Container内部子节点事件。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值