软件编程
位置:首页>> 软件编程>> Android编程>> 使用flutter的showModalBottomSheet遇到的坑及解决

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

作者:沙漠一只雕得儿得儿  发布时间:2021-12-07 22:58:29 

标签:flutter,showModalBottomSheet

在使用官方的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

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com