Flutter ShowModalBottomSheet 自定义高度和滚动

  • Post author:
  • Post category:其他


这是一个官方的组件 但是两个滑动在一起会有冲突简单的实现了一下滑动到头自动收起这个组件

封装成一个通用组件

class ComModalBottomSheet {
  /// 展示ModalBottomSheet
  /// [context] 上下文
  /// [child] 里面的子组件
  /// [isDismissible] 外部是否可以点击 默认是true
  /// [enableDrag] 是否可以拖动
  /// [title] 标题
  /// [isLeftTitle]是否居中
  /// [useRootNavigator ] 是否使用跟路由
  /// [maxHeight] 最小0最大1 取之间数字 小于0.5按0.5算 大于1 按1算
  static void show(
      {required context,
      required child,
      isDismissible = true,
      enableDrag = true,
      useRootNavigator = false,
      title,
      isScrollOver = false,
      isLeftTitle = false,
      showRightTopClose = false,
      showBottomClose = true,
      maxHeight = 0.5}) {
    showModalBottomSheet(
      context: context,
      //背景颜色(下半部弹出的颜色)
      backgroundColor: Theme.of(context).scaffoldBackgroundColor,
      //上半部分的mask颜色
      barrierColor: Colors.black54,
// 如果设定高度小于0.5 是flase默认最大半屏 ,大于0.5按设定高度来
      isScrollControlled: maxHeight > 0.5,
      //外部是否可以点击
      isDismissible: isDismissible ?? true,
      //是否可以拖动
      enableDrag: enableDrag ?? true,
      //是否用根路由
      useRootNavigator: useRootNavigator ?? false,
      //顶部的圆角矩形
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.only(
            topLeft: Radius.circular(ScreenHelper.width(15)),
            topRight: Radius.circular(ScreenHelper.width(15))),
      ),

      builder: (context) {
//返回一个SingleChildScrollView 设定他永不滚动 实现自适应高度
        return SingleChildScrollView(
          physics: const NeverScrollableScrollPhysics(),
          child: Padding(
///根据设计图设置他的padding
            padding: EdgeInsets.all(ScreenHelper.width(18)),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              children: [
                //如果有标题或者有右上角的 X就展示这个组件 
                if(title!=null||showRightTopClose)
                  _buildHead(title, isLeftTitle, showRightTopClose, context),
///如果最大宽度小于0.5自适应 否则强制设置最大高度
                if (maxHeight <= 0.5)
                  child
                else
                  SizedBox(
                    height: ScreenHelper.screenHeight *
                        (maxHeight >= 0.87 ? 0.87 : maxHeight),
                    child: child,
                  ),
///是否展示下方的取消按钮
                if (showBottomClose)
                  SizedBox(
                    width: double.infinity,
                    child: comMaterialButton(context,
                        onPressed: () => Navigator.pop(context),
                        buttonName: "取消"),
                  )
              ],
            ),
          ),
        );
      },
    );
  }

  static Stack _buildHead(
      title, isLeftTitle, showRightTopClose, BuildContext context) {
    return Stack(
      children: [
        if(title!=null)
        Container(
          width: double.infinity,
          padding: EdgeInsets.symmetric(vertical: ScreenHelper.width(8)),
          margin: EdgeInsets.only(bottom: ScreenHelper.width(18)),
          child: Text(
            title ?? "",
            textAlign: isLeftTitle ? TextAlign.start : TextAlign.center,
          ),
        ),
        if (showRightTopClose)
          Positioned(
              right: 0,
              child: InkWell(
                onTap: () => Navigator.pop(context),
                child: Padding(
                  padding: EdgeInsets.all(ScreenHelper.width(8)),
                  child: comIcon(context, iconPoint: 0xe7e4),
                ),
              ))
      ],
    );
  }
}

下方按钮的封装

Padding comMaterialButton(BuildContext context,
    {required buttonName, onPressed, padding}) {
  return Padding(
    padding: EdgeInsets.symmetric(
        horizontal: ScreenHelper.width(18)),
    child: MaterialButton(
      color: Theme.of(context).textTheme.button?.color,
      height: ScreenHelper.width(44),
      shape: ContinuousRectangleBorder(
          borderRadius: BorderRadius.circular(ScreenHelper.width(15))),
      onPressed: () {
        if (onPressed != null) {
          onPressed();
        }
      },
      child: Text(
        buttonName ?? "",
        style: TextStyle(color: Theme.of(context).textTheme.subtitle2?.color),
      ),
    ),
  );
}

showModalBottomSheet里面的ScrollView的封装

class BottomSheetScrollView extends StatefulWidget {
//传进来的列表
  final List<dynamic> list;
//传进来的Widget
  final Widget Function(dynamic) child;

  const BottomSheetScrollView(
      {Key? key, required this.list, required this.child})
      : super(key: key);
  @override
  State<BottomSheetScrollView> createState() => _BottomSheetScrollViewState();
}

class _BottomSheetScrollViewState extends State<BottomSheetScrollView> {
  ///最后一次点击的位置
  double lastPoint = 0.0;

  ///当前条目的距离
  double offset = 0.0;

  ///是否滚动到头了
  bool isOverScroll = true;
///滚动控制器
  ScrollController controller = ScrollController();


  @override
  void initState() {
    // TODO: implement initState
    super.initState();
///添加监听 滚动的距离保存
    controller.addListener(() {
      offset = controller.offset;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Listener(
///添加对界面手指移动的监听
      onPointerMove: (event) {
       if (event.position.dx - lastPoint > 0) {
         如果当前的位置比按下的位置大 那么是向上滑的 那么永远滚不到最顶上
          setState(() => isOverScroll = false);
        } else {
//如果当前滚动到最顶上的时候就设置isOverScroll 是true
        setState(() => isOverScroll = offset == 0.0);
        }
      },
      onPointerDown: (event) {
///按下时记录此时的位置
        setState(() {
           lastPoint = event.position.dx;
         });
      },
      child: Padding(
        padding: EdgeInsets.symmetric(vertical: ScreenHelper.width(18)),
        child: ListView.builder(
          //控制器
            controller: controller,
            ///如果到头了就禁止它滑动,如果没到头就继续滑动
            physics: isOverScroll
                ? const NeverScrollableScrollPhysics()
                : const ClampingScrollPhysics(),
//长度是传进来的列表的长度
           itemCount: widget.list.length,
构造也是传进来的
                  itemBuilder: (context, index) =>
                      widget.child(model.list[index])),
      ),
    );
  }
}

使用

TextButton(
                                onPressed: () {
                                  model.addModelBottomSheetListener();
                                  ComModalBottomSheet.show(
                                      useRootNavigator: false,
                                      maxHeight: 0.7,
                                      isDismissible: false,
                                      context: context,
                                      child: BottomSheetScrollView(
                                        list: ["1", "2", "3", "4","1", "2", "3", "4","1", "2", "3", "4","1", "2", "3", "4","1", "2", "3", "4""1", "2", "3", "4""1", "2", "3", "4""1", "2", "3", "4"],
                                        child: (item) => ListTile(title: Text(item),),
                                      ));
                                },
                                child: Text("弹出测试"),
                              ),