使用flutter的showModalBottomSheet遇到的坑及解決
在使用官方的showModalBottomSheet這個組件時到目前為止
遇到了三個比較坑的地方
1. 無法直接設(shè)置圓角;
2. 組件最多只能撐滿半屏幕,再多就出界了;
3. 在這個組件里面如果有選擇按鈕等其他一些需要改變狀態(tài)的組件時,即便使用setState,狀態(tài)也無法更新。
我們解決完后的效果如下
解決問題一
使用stack包裹住子組件解決圓角的問題,且需要設(shè)置背景顏色為Colors.balck54,這個顏色是bottomsheet彈出時系統(tǒng)的默認(rèn)顏色,最簡單的示例代碼如下:
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的內(nèi)容", ), ), ), ), ], ); });
圓角有了,且圓角顏色和背景色都是black54,效果如圖:
解決問題二
系統(tǒng)的bottomSheet最大高度是屏幕的一半,原因是源碼里面限制了最大高度:
maxHeight: constraints.maxHeight * 9.0 / 16.0,
我們解決辦法是直接把源碼文件考出來,把這個值給去掉即可??截愒创a唯一需要注意的一點是import導(dǎo)包時,源碼的import 路徑和我們自己導(dǎo)的路徑不同,
源碼的import: 我們導(dǎo)入的import:
嫌麻煩的話,文末有已經(jīng)修改好的可以直接使用的bottomSheet文件。只是修改了maxHeight這個限制屬性。這個去掉后,bottomSheet就沒有最大高度限制了。
解決問題三
在bottomSheet里面如果有需要更改狀態(tài)的組件,例如CheckBox的選中、未選中狀態(tài),這時setState(){}發(fā)現(xiàn)bottomSheet本身沒有更新。
這邊想到的方法是使用evenbus,在bottomSheet里面需要更新的地方發(fā)射更新信息,在拷貝出的系統(tǒng)源碼中加入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); }
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Android?kotlin?跳轉(zhuǎn)手機熱點開關(guān)頁面和判斷熱點是否打開(親測可用)
跳轉(zhuǎn)手機熱點的頁面肯定是用intent,重點是action不知道是什么,網(wǎng)上最常見的就是Settings.ACTION_WIFI_SETTINGS 跳轉(zhuǎn)wifi設(shè)置頁面,本文介紹Android?kotlin?跳轉(zhuǎn)手機熱點開關(guān)頁面和判斷熱點是否打開,感興趣的朋友一起看看吧2023-08-08Android開發(fā)之在xml中設(shè)置自定義屬性的方法
下面小編就為大家分享一篇Android開發(fā)之在xml中設(shè)置自定義屬性的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01利用Android 防止系統(tǒng)字體變化、顯示大小變化影響
這篇文章主要介紹了利用Android 防止系統(tǒng)字體變化、顯示大小變化影響方法的相關(guān)資料,需要的朋友可以參考下面文章的具體內(nèi)容,希望對你有所幫助2021-10-10android利用ContentResolver訪問者獲取手機聯(lián)系人信息
這篇文章主要介紹了android利用ContentResolver訪問者獲取手機聯(lián)系人信息,非常具有實用價值,需要的朋友可以參考下。2017-02-02