Flutter Stack子widget超出部分点击无响应

3,866 阅读2分钟

Stack是Flutter中的一个层次结构的布局,子widget是按层次顺序叠加在一起的,Stack有一个属性overflow,该属性有两个可选值。

  • Overflow.visible

子布局超出Stack区域可见

  • Overflow.clip

子布局超出Stack区区域将被裁剪

看如下的Demo,结合使用CustomScrollView、SliverAppBar、SliverList实现顶部可以跟随list一起滑动。顶部浮着的按钮使用Stack父布局,overflow属性设置成Overflow.visible ,Positioned bottom设置-24,实现按钮底部一半布局在顶部背景下面的效果。

实现代码如下:

Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        physics: const BouncingScrollPhysics(),
        slivers: <Widget>[
          SliverAppBar(
            stretch: true,
            expandedHeight: 300.0,
            flexibleSpace: FlexibleSpaceBar(
              stretchModes: <StretchMode>[
                StretchMode.zoomBackground,
                StretchMode.blurBackground,
                StretchMode.fadeTitle,
              ],
              centerTitle: true,
              background: Stack(
                fit: StackFit.expand,
                children: [
                  Image.network(
                    'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg',
                    fit: BoxFit.cover,
                  ),
                  const DecoratedBox(
                    decoration: BoxDecoration(
                      gradient: LinearGradient(
                        begin: Alignment(0.0, 0.5),
                        end: Alignment(0.0, 0.0),
                        colors: <Color>[
                          Color(0x60000000),
                          Color(0x00000000),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ),
            bottom: PreferredSize(
                preferredSize: Size.fromHeight(24),
                child: Container(
                  height: 24,
                  child: Stack(
                  overflow: Overflow.visible, 
                  alignment: Alignment.center, 
                  children: [
                    Positioned(
                        bottom: -24.0,
                        child: SizedBox(
                          height: 48,
                          width: 240,
                          child: RaisedButton(
                              shape: RoundedRectangleBorder(borderRadius: new BorderRadius.circular(30.0)),
                              onPressed: () {
                                print('I am taped');
                              },
                              color: Theme.of(context).accentColor,
                              child: Text('Tap me'.toUpperCase())),
                        ))
                  ]),
                )),
          ),
          SliverList(
            delegate: SliverChildListDelegate([
              ListTile(
                leading: Icon(Icons.wb_sunny),
                title: Text('Sunday'),
                subtitle: Text('sunny, h: 80, l: 65'),
              ),
           	  ...
              // ListTiles++
            ]),
          ),
        ],
      ),
    );

点击'Tap me'按钮会在console里打印'I am taped',但是测试的时候会发现console里有时打印有时不打印log;用断点debug也会有onPressed没有执行的时候。经过多次测试找到了规律,点击按钮上半部分可响应,点击按钮下半部分不会响应。

使用AS Flutter Inspect工具,可以看到Stack和RaisedButton的边界,Stack占的区域是红色部分,RaisedButton占的区域是本身可视的部分,如下图所示:

查看Stack文档,Overflow.visible注释里其实说的很清楚,超出区域可见但是不能响应点击事件。

enum Overflow {
  /// Overflowing children will be visible.
  ///
  /// The visible overflow area will not accept hit testing.
  visible,

  /// Overflowing children will be clipped to the bounds of their parent.
  clip,
}

为什么不能响应

为什么可以显示确又不让点击呢?是Flutter框架的bug还是故意为之?

针对这个问题,flutter issue上讨论的也很激烈传送门,下面一段引用是Flutter Member的回答:

I understand the disappointment. Unfortunately, performing hit-testing outside of the bounds of a stack in the overflow is prohibitively expensive in terms of performance.

I do still believe that you can always restructure your app to not require hit-testing in the overflow of a stack. Maybe, people can post some runnable code examples of where you'd like to do hit testing in the overflow of the stack and we can figure out how to restructure the code to not require that. Those example uses cases would also be good for us to include in the updated docs of overflow.

简单的翻译就是对超出边界的hit-testing非常耗性能,一般来说都可以优化代码来避免这种问题;你也可以举例说明何种case下需要对超出边界进行hit-testing,这样我们可以找出如何重构代码来避免。所以这不是一个bug,是对性能要求的取舍。