flutter的showModalBottomSheet遇到的坑

https://blog.csdn.net/cpcpcp123/article/details/97660036

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

1. 无法直接设置圆角;

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

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

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

解决问题一:使用stack包裹住子组件解决圆角的问题,且需要设置背景颜色为Colors.balck54,这个颜色是bottomsheet弹出时系统的默认颜色,最简单的示例代码如下:

                 showModalBottomSheet(

                    context: context,

                    builder: (BuildContext bc) {

                      return Stack(

                        children: <Widget>[

                          Container(

                            height: 30.0,

                            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);

}

————————————————

版权声明:本文为CSDN博主「buder得儿得儿以得儿以得儿得儿」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/cpcpcp123/java/article/details/97660036

原文地址:https://www.cnblogs.com/sundaysme/p/12703412.html