ios - 覆盖层内的 GestureDetector / Inkwell 在底部缺口的 iPhone 上未对齐
问题描述
我有一个全屏覆盖,其中包含一个最小化/最大化按钮,这个按钮在没有底部缺口的 Android 和 iOS 手机上完美运行。
工作设备示例:
- 像素 3(模拟器)
- iPhone 8(模拟器)
有问题的设备示例:
- iPhone 11
当叠加层被“最大化”时,命中检测是正确的。当“最小化”时,命中检测向下偏移。“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),
),
);
}
}
是什么导致命中检测未命中?
解决方案
原来问题出在我如何在这里切换 SafeArea 的顶部和底部属性:
body: SafeArea(
top: !_isMinimized,
bottom: _isMinimized,
解决方案是删除“顶部”和“底部”行,以便它们始终默认为 true。然后我手动计算标题的最小高度。
推荐阅读
- javascript - VueJS 中的多行字符串
- javascript - 如何有条件地加载 HTML 元素,或者在加载页面之前删除它们?
- spring - Spring:从具有关系的表中删除数据
- javascript - React:映射数据
- python - 如果字典中的列表中存在值,则返回键
- python - Django 在 UpdateView 类中设置 ModelForm ModelChoiceField 初始值
- android - 如何从回收站视图中获取项目?
- node.js - Stripe Connect - 如何以最安全的方式使用令牌来传递敏感数据?
- python - 有没有一种“热交换”列表的pythonic方法?
- css - CSS自定义样式复选框:使复选标记出现并(删除焦点样式)