使用flutter的showModalBottomSheet遇到的坑及解决
作者:沙漠一只雕得儿得儿 发布时间:2021-12-07 22:58:29
在使用官方的showModalBottomSheet这个组件时到目前为止
遇到了三个比较坑的地方
1. 无法直接设置圆角;
2. 组件最多只能撑满半屏幕,再多就出界了;
3. 在这个组件里面如果有选择按钮等其他一些需要改变状态的组件时,即便使用setState,状态也无法更新。
我们解决完后的效果如下
解决问题一
使用stack包裹住子组件解决圆角的问题,且需要设置背景颜色为Colors.balck54,这个颜色是bottomsheet弹出时系统的默认颜色,最简单的示例代码如下:
showModalBottomSheet(
context: context,
builder: (BuildContext bc) {
return Stack(
children: <Widget>[
Container(
height: 30.0,
width: double.infinity,
color: Colors.black54,
),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
)),
),
Container(
child: FlatButton(
child: Container(
alignment: Alignment.center,
padding:
EdgeInsets.only(top: 33.0, bottom: 33.0),
child: Text(
"bottomSheet的内容",
),
),
),
),
],
);
});
圆角有了,且圆角颜色和背景色都是black54,效果如图:
解决问题二
系统的bottomSheet最大高度是屏幕的一半,原因是源码里面限制了最大高度:
maxHeight: constraints.maxHeight * 9.0 / 16.0,
我们解决办法是直接把源码文件考出来,把这个值给去掉即可。拷贝源码唯一需要注意的一点是import导包时,源码的import 路径和我们自己导的路径不同,
源码的import: 我们导入的import:
嫌麻烦的话,文末有已经修改好的可以直接使用的bottomSheet文件。只是修改了maxHeight这个限制属性。这个去掉后,bottomSheet就没有最大高度限制了。
解决问题三
在bottomSheet里面如果有需要更改状态的组件,例如CheckBox的选中、未选中状态,这时setState(){}发现bottomSheet本身没有更新。
这边想到的方法是使用evenbus,在bottomSheet里面需要更新的地方发射更新信息,在拷贝出的系统源码中加入listen即可,如下:
@override
void initState() {
super.initState();
Manager.instance.eventBus.on<RefreshBottomSheetEvent>().listen((event) {
setState(() {
});
});
}
fire消息的代码:
Manager.instance.eventBus.fire(RefreshBottomSheetEvent());
这个event:
class RefreshBottomSheetEvent {
RefreshBottomSheetEvent();
}
下面这个即为整个修改源码的bottomSheet,改动的地方:
1. maxHeight
2.添加了eventBus的listen
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:phone_assistant/event/ContactRefreshEvent.dart';
import 'package:phone_assistant/event/RefreshBottomSheetEvent.dart';
import '../../Manager.dart';
const Duration _kBottomSheetDuration = Duration(milliseconds: 200);
const double _kMinFlingVelocity = 700.0;
const double _kCloseProgressThreshold = 0.5;
/// A material design bottom sheet.
///
/// There are two kinds of bottom sheets in material design:
///
/// * _Persistent_. A persistent bottom sheet shows information that
/// supplements the primary content of the app. A persistent bottom sheet
/// remains visible even when the user interacts with other parts of the app.
/// Persistent bottom sheets can be created and displayed with the
/// [ScaffoldState.showBottomSheet] function or by specifying the
/// [Scaffold.bottomSheet] constructor parameter.
///
/// * _Modal_. A modal bottom sheet is an alternative to a menu or a dialog and
/// prevents the user from interacting with the rest of the app. Modal bottom
/// sheets can be created and displayed with the [showModalBottomSheet]
/// function.
///
/// The [BottomSheet] widget itself is rarely used directly. Instead, prefer to
/// create a persistent bottom sheet with [ScaffoldState.showBottomSheet] or
/// [Scaffold.bottomSheet], and a modal bottom sheet with [showModalBottomSheet].
///
/// See also:
///
/// * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing
/// non-modal "persistent" bottom sheets.
/// * [showModalBottomSheet], which can be used to display a modal bottom
/// sheet.
/// * <https://material.io/design/components/sheets-bottom.html>
class BottomSheet extends StatefulWidget {
/// Creates a bottom sheet.
///
/// Typically, bottom sheets are created implicitly by
/// [ScaffoldState.showBottomSheet], for persistent bottom sheets, or by
/// [showModalBottomSheet], for modal bottom sheets.
const BottomSheet({
Key key,
this.animationController,
this.enableDrag = true,
this.elevation = 0.0,
@required this.onClosing,
@required this.builder,
}) : assert(enableDrag != null),
assert(onClosing != null),
assert(builder != null),
assert(elevation != null && elevation >= 0.0),
super(key: key);
/// The animation that controls the bottom sheet's position.
///
/// The BottomSheet widget will manipulate the position of this animation, it
/// is not just a passive observer.
final AnimationController animationController;
/// Called when the bottom sheet begins to close.
///
/// A bottom sheet might be prevented from closing (e.g., by user
/// interaction) even after this callback is called. For this reason, this
/// callback might be call multiple times for a given bottom sheet.
final VoidCallback onClosing;
/// A builder for the contents of the sheet.
///
/// The bottom sheet will wrap the widget produced by this builder in a
/// [Material] widget.
final WidgetBuilder builder;
/// If true, the bottom sheet can dragged up and down and dismissed by swiping
/// downwards.
///
/// Default is true.
final bool enableDrag;
/// The z-coordinate at which to place this material relative to its parent.
///
/// This controls the size of the shadow below the material.
///
/// Defaults to 0. The value is non-negative.
final double elevation;
@override
_BottomSheetState createState() => _BottomSheetState();
/// Creates an animation controller suitable for controlling a [BottomSheet].
static AnimationController createAnimationController(TickerProvider vsync) {
return AnimationController(
duration: _kBottomSheetDuration,
debugLabel: 'BottomSheet',
vsync: vsync,
);
}
}
class _BottomSheetState extends State<BottomSheet> {
@override
void initState() {
super.initState();
Manager.instance.eventBus.on<RefreshBottomSheetEvent>().listen((event) {
setState(() {
});
});
}
final GlobalKey _childKey = GlobalKey(debugLabel: 'BottomSheet child');
double get _childHeight {
final RenderBox renderBox = _childKey.currentContext.findRenderObject();
return renderBox.size.height;
}
bool get _dismissUnderway => widget.animationController.status == AnimationStatus.reverse;
void _handleDragUpdate(DragUpdateDetails details) {
if (_dismissUnderway)
return;
widget.animationController.value -= details.primaryDelta / (_childHeight ?? details.primaryDelta);
}
void _handleDragEnd(DragEndDetails details) {
if (_dismissUnderway)
return;
if (details.velocity.pixelsPerSecond.dy > _kMinFlingVelocity) {
final double flingVelocity = -details.velocity.pixelsPerSecond.dy / _childHeight;
if (widget.animationController.value > 0.0)
widget.animationController.fling(velocity: flingVelocity);
if (flingVelocity < 0.0)
widget.onClosing();
} else if (widget.animationController.value < _kCloseProgressThreshold) {
if (widget.animationController.value > 0.0)
widget.animationController.fling(velocity: -1.0);
widget.onClosing();
} else {
widget.animationController.forward();
}
}
@override
Widget build(BuildContext context) {
final Widget bottomSheet = Material(
key: _childKey,
elevation: widget.elevation,
child: widget.builder(context),
);
return !widget.enableDrag ? bottomSheet : GestureDetector(
onVerticalDragUpdate: _handleDragUpdate,
onVerticalDragEnd: _handleDragEnd,
child: bottomSheet,
excludeFromSemantics: true,
);
}
}
// PERSISTENT BOTTOM SHEETS
// See scaffold.dart
// MODAL BOTTOM SHEETS
class _ModalBottomSheetLayout extends SingleChildLayoutDelegate {
_ModalBottomSheetLayout(this.progress);
final double progress;
@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return BoxConstraints(
minWidth: constraints.maxWidth,
maxWidth: constraints.maxWidth,
minHeight: 0.0,
// maxHeight: constraints.maxHeight * 9.0 / 16.0,
);
}
@override
Offset getPositionForChild(Size size, Size childSize) {
return Offset(0.0, size.height - childSize.height * progress);
}
@override
bool shouldRelayout(_ModalBottomSheetLayout oldDelegate) {
return progress != oldDelegate.progress;
}
}
class _ModalBottomSheet<T> extends StatefulWidget {
const _ModalBottomSheet({ Key key, this.route }) : super(key: key);
final _ModalBottomSheetRoute<T> route;
@override
_ModalBottomSheetState<T> createState() => _ModalBottomSheetState<T>();
}
class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
@override
Widget build(BuildContext context) {
final MediaQueryData mediaQuery = MediaQuery.of(context);
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
String routeLabel;
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
routeLabel = '';
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
routeLabel = localizations.dialogLabel;
break;
}
return GestureDetector(
excludeFromSemantics: true,
onTap: () => Navigator.pop(context),
child: AnimatedBuilder(
animation: widget.route.animation,
builder: (BuildContext context, Widget child) {
// Disable the initial animation when accessible navigation is on so
// that the semantics are added to the tree at the correct time.
final double animationValue = mediaQuery.accessibleNavigation ? 1.0 : widget.route.animation.value;
return Semantics(
scopesRoute: true,
namesRoute: true,
label: routeLabel,
explicitChildNodes: true,
child: ClipRect(
child: CustomSingleChildLayout(
delegate: _ModalBottomSheetLayout(animationValue),
child: BottomSheet(
animationController: widget.route._animationController,
onClosing: () => Navigator.pop(context),
builder: widget.route.builder,
),
),
),
);
},
),
);
}
}
class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
_ModalBottomSheetRoute({
this.builder,
this.theme,
this.barrierLabel,
RouteSettings settings,
}) : super(settings: settings);
final WidgetBuilder builder;
final ThemeData theme;
@override
Duration get transitionDuration => _kBottomSheetDuration;
@override
bool get barrierDismissible => true;
@override
final String barrierLabel;
@override
Color get barrierColor => Colors.black54;
AnimationController _animationController;
@override
AnimationController createAnimationController() {
assert(_animationController == null);
_animationController = BottomSheet.createAnimationController(navigator.overlay);
return _animationController;
}
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
// By definition, the bottom sheet is aligned to the bottom of the page
// and isn't exposed to the top padding of the MediaQuery.
Widget bottomSheet = MediaQuery.removePadding(
context: context,
removeTop: true,
child: _ModalBottomSheet<T>(route: this),
);
if (theme != null)
bottomSheet = Theme(data: theme, child: bottomSheet);
return bottomSheet;
}
}
/// Shows a modal material design bottom sheet.
///
/// A modal bottom sheet is an alternative to a menu or a dialog and prevents
/// the user from interacting with the rest of the app.
///
/// A closely related widget is a persistent bottom sheet, which shows
/// information that supplements the primary content of the app without
/// preventing the use from interacting with the app. Persistent bottom sheets
/// can be created and displayed with the [showBottomSheet] function or the
/// [ScaffoldState.showBottomSheet] method.
///
/// The `context` argument is used to look up the [Navigator] and [Theme] for
/// the bottom sheet. It is only used when the method is called. Its
/// corresponding widget can be safely removed from the tree before the bottom
/// sheet is closed.
///
/// Returns a `Future` that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the modal bottom sheet was closed.
///
/// See also:
///
/// * [BottomSheet], which is the widget normally returned by the function
/// passed as the `builder` argument to [showModalBottomSheet].
/// * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing
/// non-modal bottom sheets.
/// * <https://material.io/design/components/sheets-bottom.html#modal-bottom-sheet>
Future<T> showModalBottomSheetCustom<T>({
@required BuildContext context,
@required WidgetBuilder builder,
}) {
assert(context != null);
assert(builder != null);
assert(debugCheckHasMaterialLocalizations(context));
return Navigator.push(context, _ModalBottomSheetRoute<T>(
builder: builder,
theme: Theme.of(context, shadowThemeOnly: true),
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
));
}
/// Shows a persistent material design bottom sheet in the nearest [Scaffold].
///
/// Returns a controller that can be used to close and otherwise manipulate the
/// bottom sheet.
///
/// To rebuild the bottom sheet (e.g. if it is stateful), call
/// [PersistentBottomSheetController.setState] on the controller returned by
/// this method.
///
/// The new bottom sheet becomes a [LocalHistoryEntry] for the enclosing
/// [ModalRoute] and a back button is added to the appbar of the [Scaffold]
/// that closes the bottom sheet.
///
/// To create a persistent bottom sheet that is not a [LocalHistoryEntry] and
/// does not add a back button to the enclosing Scaffold's appbar, use the
/// [Scaffold.bottomSheet] constructor parameter.
///
/// A persistent bottom sheet shows information that supplements the primary
/// content of the app. A persistent bottom sheet remains visible even when
/// the user interacts with other parts of the app.
///
/// A closely related widget is a modal bottom sheet, which is an alternative
/// to a menu or a dialog and prevents the user from interacting with the rest
/// of the app. Modal bottom sheets can be created and displayed with the
/// [showModalBottomSheet] function.
///
/// The `context` argument is used to look up the [Scaffold] for the bottom
/// sheet. It is only used when the method is called. Its corresponding widget
/// can be safely removed from the tree before the bottom sheet is closed.
///
/// See also:
///
/// * [BottomSheet], which is the widget typically returned by the `builder`.
/// * [showModalBottomSheet], which can be used to display a modal bottom
/// sheet.
/// * [Scaffold.of], for information about how to obtain the [BuildContext].
/// * <https://material.io/design/components/sheets-bottom.html#standard-bottom-sheet>
PersistentBottomSheetController<T> showBottomSheet<T>({
@required BuildContext context,
@required WidgetBuilder builder,
}) {
assert(context != null);
assert(builder != null);
return Scaffold.of(context).showBottomSheet<T>(builder);
}
来源:https://buder.blog.csdn.net/article/details/97660036


猜你喜欢
- 基于SMTP发送一个简单的邮件首先,需要一个认证器:package No001_基于SMTP的文本邮件;import javax.mail.
- Java API不能远程访问HBase今天我在虚拟机里面安装了Hbase 1.2.4,说在windows上Java API调用访问下玩玩,结
- @AutoConfiguration读取所有jar包下的 /META-INF/spring.factories 并追加到一个 LinkedM
- 前言之前做的几个微信小程序项目,大部分客户都有要在微信小程序前端提现的需求。提现功能的实现,自然使用企业付款接口,不过这个功能开通比较麻烦,
- 坑出现的环境一般情况下切割字符串会使用split或者StringTokenizer,如下代码String s = ",,o,,&q
- 在类中自定义的“函数”称为“方法”,由于C#是完全面向对象的
- 平时开发,基本不改变的常量我们都放在了配置项里,如properties或yml文件里,这个时候为了只在启动时候进行加载。如何做呢?我们通过s
- 定义:当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。特
- 如何打印GC日志排查问题在工作当中,有时候我们会需要打印GC的相关信息来定位问题。该如何做呢?先来看个示例public static voi
- 为什么写?今天去上班的公交上,有朋友在张队(张善友)的微信群里,发了一个介绍C# 6.0新特性的视频,视频7分钟,加上本人英语实在太low,
- 一、什么是ImportBeanDefinitionRegistrarImportBeanDefinitionRegistrar接口是也是sp
- file.mkdir()创建单级文件夹,file.mkdirs()创建多级文件夹,file.createNewFile()创建的是一个文件。
- 一、前言跟很多小伙伴聊天,发现一个严重的问题,很多小伙伴横向发展的貌似很不错,很多技术都能说出一二,但是如果在某个技术上深挖一下就不行了,问
- 前言Spark Sql可以通过UDF来对DataFrame的Column进行自定义操作。在特定场景下定义UDF可能需要用到Spark Con
- 介绍装饰模式,是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式。就功能而言,装饰模式相比生成子类更为灵活,这样可以给某个对象
- 概念代理:为控制A对象,而创建出新B对象,由B对象代替执行A对象所有操作,称之为代理。一个代理体系建立涉及到3个参与角色:真实对象(A),代
- 本文实例为大家分享了android自定义WaveView水波纹控件的使用方法,供大家参考,具体内容如下Github Repository a
- Java数组的定义和使用如果希望保存一组有相同类型的数据,可以使用数组。数组的定义和内存分配Java 中定义数组的语法有两种:
- 公司项目中经常设计到串口通信,TCP通信,而且大多都是实时的大数据的传输,然后大家都知道协议通讯肯定涉及到什么,封包、拆包、粘包、校验……什
- 强制下线是需要关闭所有的活动,先创建一个类来管理所有的活动。class ActivityCollector { //var ac