Flutter 组件使用:使用 Stack 替代 GlobalKey 的定位 tip-widget 实现

场景

有时候需要在指定位置进行 tip-widget 的弹出与展示,常见的方式是通过给指定位置上的指定 widget 添加 GlobalKey 来实现;

但是,使用这种方式的话,【一】大多数时候都需要进行全局定位转换(localToGlobal)及计算,【二】使用系统 Popup 控件,当 Popup 展示时,列表可能不可滑动,【三】如果需要跟随列表滑动,就需要使用实时监听滑动回调的方式去实时更新 tip-widget 的实时跟随滑动;

以上提到的问题,不仅实现上“费时费力”,还会使相关功能代码分散、不相关功能代码耦合,造成代码维护及迭代上的难度;

所以,在此提出一种“使用 Stack 替代 GlobalKey 的定位 tip-widget 实现”,通过一种“讨巧”的方式,尝试较好地解决以上提到的问题;

当然,解决方法还有很多,骚操作是程序员的乐趣,但是更好地解决问题才是程序员的追求 ~!

效果

在这里插入图片描述

说明

这种方案,是通过使用 Stack 将指定 widget 与 tip-widget 进行绑定,主要有两种场景
1、如果指定 widget 本身的父 widget 就是 Stack 的话,直接把 tip-widget 加入 Stack 进行定位维护就行
2、如果指定 widget 本身的父 widget 不是 Stack 的话,就使用一个 Stack 包裹住指定 widget,然后保证 Stack 的宽高为指定 widget 的宽高,同时设置 Stack 的 clipBehavior 为 Clip.none,最后把 tip-widget 加入 Stack 进行定位维护

如果指定 widget 本身的父 widget 不是 Stack 的话,需要注意以下限定条件
并不是所有场景都适合使用这种方案进行适配,需要根据指定 widget 的父 widget 的布局特点进行选用

比如 Column 控件,Column 控件对于子 widget 的绘制顺序是由上向下(逻辑在 RenderFlex 的 performLayout() 和 paint(PaintingContext context, Offset offset) 中);
正常情况下,Column 子 widget 间是根据其本身在 Column 中的偏移量紧密衔接的;
但是,如果使用 Stack 的 Clip.none 特性后,Stack 包裹的内容可超范围绘制显示,这时,体现在 Column 的绘制顺序的话,顺序靠后的子 widget 就会覆盖顺序靠前的子 widget 的超范围绘制的内容;
所以,在 Column 中如果 tip-widget 是向上展示的,一般能正常展示,但是,如果 tip-widget 是向下展示的,就会有覆盖的问题。

在这里插入图片描述

范例

class _TestPageState extends State<TestPage> {
  bool show = false;

  
  void initState() {
    super.initState();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Stack妙用')),
      body: SingleChildScrollView(
        child: Column(
          children: [
            Container(
              width: double.maxFinite,
              height: 150,
              color: Colors.red.withOpacity(0.5),
            ),
            Container(
              width: double.maxFinite,
              height: 150,
              color: Colors.orange.withOpacity(0.5),
            ),
            Container(
              alignment: Alignment.center,
              width: double.maxFinite,
              height: 150,
              color: Colors.yellow.withOpacity(0.5),
              child: Container(
                color: show ? Colors.red : null,
                child: Stack(
                  clipBehavior: Clip.none,
                  children: [
                    GestureDetector(
                      child: const Icon(Icons.add_circle, size: 50),
                      onTap: () {
                        setState(() {
                          show = !show;
                        });
                      },
                    ),
                    if (show)
                      Positioned(
                        top: -90,
                        left: 30,
                        child: Container(
                          alignment: Alignment.center,
                          color: Colors.red.withOpacity(0.8),
                          width: 50,
                          height: 80,
                          child: const Text('菜单'),
                        ),
                      ),
                    if (show)
                      Positioned(
                        top: 60,
                        left: 30,
                        child: Container(
                          alignment: Alignment.center,
                          color: Colors.red.withOpacity(0.8),
                          width: 50,
                          height: 80,
                          child: const Text('菜单'),
                        ),
                      ),
                  ],
                ),
              ),
            ),
            Container(
              width: double.maxFinite,
              height: 150,
              color: Colors.green.withOpacity(0.5),
            ),
            Container(
              width: double.maxFinite,
              height: 150,
              color: Colors.blue.withOpacity(0.5),
            ),
            Container(
              width: double.maxFinite,
              height: 150,
              color: Colors.indigo.withOpacity(0.5),
            ),
            Container(
              width: double.maxFinite,
              height: 150,
              color: Colors.purple.withOpacity(0.5),
            ),
          ],
        ),
      ),
    );
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值