本头条核心宗旨
欢迎来到「技术刚刚好」作者,「技术刚刚好」是个人维护,每天至少更新一篇Flutter技术文章,实时为大家播报Flutter最新消息。如果你刚好也在关注Flutter这门技术,那就跟我一起学习进步吧,你的赞,收藏,转发是对我个人最大的支持,维护不易,欢迎关注。
技术刚刚好经历
近几年,移动端跨平台开发技术层出不穷,从Facebook家的ReactNative,到阿里家WEEX,前端技术在移动端跨平台开发中大展身手,技术刚刚好作为一名Android开发,经历了从Reactjs到Vuejs的不断学习。而在2018年,我们的主角变成了Flutter,这是Goolge开源的一个移动端跨平台解决方案,可以快速开发精美的移动App。希望跟大家一起学习,一起进步!
本文核心要点
本文会带大家学习布局调整的问题,我会以一个具体的实例来讲解本文的内容,首先会写一段代码引出本文要讲解的问题,然后在带大家一起解决它,干货满满,欢迎点赞,收藏。
在我工作当中有一次突然遇到这样一个问题,我们将在这篇文章当中来讨论并解决它。我将带大家用Flutter文档和 dart 带的工具,教大家来解决问题。
本文会学习BoxConstraints,ModalBottomSheet,Align,Stack,Positioned的一些窗口小部件。
我相信大家做移动APP开发从底部弹出一个菜单已经并不陌生了。在Flutter当中已经提供了一个API来。showModalBottomSheet方法。
这种对话框我们项目用到的比较多,所以我们应该创建一个类来封装它。
import 'package:flutter/material.dart';
Future<T> showBottomDialog<T>({
@required BuildContext context,
String title,
String content,
Widget titleWidget,
Widget contentWidget,
List<Widget> actions,
bool allowBackNavigation = false,
}) {
assert(title != null || titleWidget != null,
'title and titleWidget both must not be null');
assert(content != null || contentWidget != null,
'content and contentWidget both must not be null');
final theme = Theme.of(context);
return showModalBottomSheet(
context: context,
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
),
),
isDismissible: allowBackNavigation,
builder: (context) => WillPopScope(
onWillPop: () async => allowBackNavigation,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
titleWidget ??
Text(
title,
textAlign: TextAlign.left,
style: Theme.of(context).textTheme.headline2,
),
SizedBox(height: 16),
contentWidget ??
Text(
content,
textAlign: TextAlign.left,
style: Theme.of(context)
.textTheme
.bodyText2
.copyWith(height: 1.5),
),
SizedBox(height: 48),
if (actions != null)
...actions
else
OutlineButton(
child: Text('GOT IT!'),
borderSide: BorderSide(color: theme.primaryColor),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
),
),
);
}
然后,使用该包装器函数变得很容易,如下所示:
class DialogExample extends StatelessWidget {
void _showDialog(BuildContext context) {
showBottomDialog(
context: context,
allowBackNavigation: true,
title: 'Do you wish to purchase add-ons?',
content:
'Add-ons help you save some extra money when you purchase them along with our original products. Plus, they help your chances of winning as well.',
actions: [
RaisedButton(
child: Text('YES, GO AHEAD'),
onPressed: () {},
),
FlatButton(
child: Text('SKIP'),
onPressed: () {},
)
],
);
}
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: RaisedButton(
child: Text('Show Dialog'),
onPressed: () => _showDialog(context),
),
),
);
}
}
这个时候我们就遇到一个问题了,这个时候我们要添加一个IconButton来关闭对话框,这个时候我们可以定义一个isDismissable:true来设置该对话框的关闭或者打开。然后添加一个按钮。
Stack(
children : <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
titleText,
contentText,
if(_mutipleCTAButtons_ != null) ..._multipleCTAButtons,
else commonCTAButton
]
),
if(closeButton)
Align(
alignment : Alignment.topRight,
child : IconButton(
icon: Icon(
Icons.close,
size: 24,
),
onPressed: onClose ?? () => Navigator.pop(context),
)
)
]
)
对showBottomDialog函数的更改如下
Future<T> showBottomDialog<T>({
@required BuildContext context,
String title,
String content,
Widget titleWidget,
Widget contentWidget,
List<Widget> actions,
bool allowBackNavigation = false,
bool showCloseButton = false,
Function onClose,
}) {
assert(title != null || titleWidget != null,
'title and titleWidget must not both be null');
assert(content != null || contentWidget != null,
'content and contentWidget must not both be null');
final theme = Theme.of(context);
return showModalBottomSheet(
...
builder: (context) => WillPopScope(
onWillPop: () async => allowBackNavigation,
child: Stack(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Column(
...
...
// Removed for brevity
],
),
),
if (showCloseButton)
Align(
alignment : Alignment.topRight,
child: IconButton(
icon: Icon(Icons.close),
onPressed: onClose ?? () => Navigator.pop(context),
),
),
],
),
),
);
}
然后使用示例上面的使用方面的更改变为:
class DialogExample extends StatelessWidget {
void _showDialog(BuildContext context) {
showBottomDialog(
...
...
showCloseButton : true,
);
}
}
这个时候显示效果如下
这个时候问题就出现了,布局占据了屏幕的高度,差不多屏幕一半高了,是什么原因了,这个时候我们用Dart开发工具来找问题。
首先让我们看看哪个Widget通过打开Dart DevTools导致BottomSheet的高度增加,而无需假定它必须与IconButton对齐,因为添加它是导致问题的原因,对吗?
高亮区域代表我们的BottomSheet的布局层次结构。让我们在DevTool中打开“ 选择小部件”模式,然后开始选择小部件,以查看哪个小部件占用了布局空间。
阅读Flutter文档
在开始阅读文档之前,我们先来看一下StackDevTools中的尺寸(宽度和高度)。
可以看出,这些尺寸是具有约束条件的,而我们有没有设置约束,所以其中minWidth和maxWidth分别设置为和分别等于screenWidth和不设置为“ 0”和“ screenWidth”(即0.0<=w<=414),这意味着明确要求它占据整个屏幕宽度,而不考虑其子级的宽度。而且,maxHeight精确地设置为9/16的screenHeight。结果,Stack的大小被设置为w = 414(screenWidth)和h = 504(screenHeight),这意味着它占据了允许的整个高度。
因此,我们的BottomSheet约束设置如下:
final minWidth = constraints.maxWidth;
final maxWidth = constraints.maxWidth
本文暂时没有评论,来添加一个吧(●'◡'●)