使用flutter的showModalBottomSheet遇到的坑及解决
目录
- 遇到了三个比较坑的地方
- 我们解决完后的效果如下
- 解决问题一
- 解决问题二
- 解决问题三
在使用官方的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); }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。