在使用官方的showModalBottomSheet这个组件时到目前为止遇到了三个比较坑的地方:

1. 无法直接设置圆角;

2. 组件最多只能撑满半屏幕,再多就出界了;

3. 在这个组件里面如果有选择按钮等其他一些需要改变状态的组件时,即便使用setState,状态也无法更新。

我们解决完后的效果如下,

使用flutter的showModalBottomSheet遇到的坑及解决

解决问题一

使用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,效果如图:

使用flutter的showModalBottomSheet遇到的坑及解决

解决问题二

系统的bottomSheet最大高度是屏幕的一半,原因是源码里面限制了最大高度:

maxHeight: constraints.maxHeight * 9.0 / 16.0,

我们解决办法是直接把源码文件考出来,把这个值给去掉即可。拷贝源码唯一需要注意的一点是import导包时,源码的import 路径和我们自己导的路径不同,

源码的import:                                                     我们导入的import:

使用flutter的showModalBottomSheet遇到的坑及解决           使用flutter的showModalBottomSheet遇到的坑及解决

嫌麻烦的话,文末有已经修改好的可以直接使用的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

相关文章: