首页 > 解决方案 > 覆盖层内的 GestureDetector / Inkwell 在底部缺口的 iPhone 上未对齐

问题描述

我有一个全屏覆盖,其中包含一个最小化/最大化按钮,这个按钮在没有底部缺口的 Android 和 iOS 手机上完美运行。

工作设备示例:

有问题的设备示例:

当叠加层被“最大化”时,命中检测是正确的。当“最小化”时,命中检测向下偏移。“MinMax”FlatButton(见下文)需要在按钮文本下方的一个非常特定的位置点击。

此外,未命中检测不会随着 Overlay 的尺寸变小而逐渐偏移。您可以通过从 0.08 增加最小化大小来看到这一点。大小的任何变化都会导致命中检测在同一个位置丢失。

以下最小再现演示了该问题:

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _isMinimized = false;
  StreamController<bool> _isMinimimizedController = StreamController<bool>();
  OverlayEntry _activeOverlay;

  Widget _buildOverlay() {
    return Scaffold(
      body: SafeArea(
          top: !_isMinimized,
          bottom: _isMinimized,
          child: Column(
            children: <Widget>[
              _buildOverlayHeader(),
              Expanded(
                child: Container(
                  color: Colors.red,
                ),
              ),
            ],
          )),
    );
  }

  Widget _buildOverlayHeader() {
    return Container(
      decoration: BoxDecoration(color: Colors.green),
      child: Padding(
        padding: EdgeInsets.all(10),
        child: Row(
          children: <Widget>[
            Expanded(
              child: Container(),
            ),
            FlatButton(
              child: Text('MinMax'),
              onPressed: () {
                _isMinimized = !_isMinimized;
                _isMinimimizedController.sink.add(_isMinimized);
              },
            ),
          ],
        ),
      ),
    );
  }

  void openOverlay() {
    if (_activeOverlay != null || context == null) {
      return;
    }

    _activeOverlay = OverlayEntry(builder: (context) {
      return StreamBuilder<bool>(
          stream: _isMinimimizedController.stream,
          initialData: false,
          builder: (context, snapshot) {
            final mediaQuery = MediaQuery.of(context);

            var height = mediaQuery.size.height;
            var top = 0.0;

            if (_isMinimized) {
              height = height * 0.08;
              top = mediaQuery.size.height - height;
            }

            return AnimatedPositioned(
              duration: Duration(milliseconds: 300),
              curve: Curves.easeInOutQuart,
              width: mediaQuery.size.width,
              height: mediaQuery.size.height,
              top: top,
              left: 0,
              // Child navigator is necessary to show modals on top of the overlay
              child: Navigator(onGenerateRoute: (RouteSettings settings) {
                var builder = (BuildContext context) => _buildOverlay();
                return MaterialPageRoute(
                    builder: (builder), settings: settings);
              }),
            );
          });
    });

    Overlay.of(context).insert(_activeOverlay);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: openOverlay,
        tooltip: 'Open',
        child: Icon(Icons.add),
      ),
    );
  }
}

是什么导致命中检测未命中?

标签: iosflutter

解决方案


原来问题出在我如何在这里切换 SafeArea 的顶部和底部属性:

      body: SafeArea(
          top: !_isMinimized,
          bottom: _isMinimized,

解决方案是删除“顶部”和“底部”行,以便它们始终默认为 true。然后我手动计算标题的最小高度。


推荐阅读