Flutter--实现底部可拖拽的抽屉页面

876 阅读1分钟

如高德地图首页,底层是地图,上层是一个可拖拽的抽屉页面,想要实现这种效果,可以使用DraggableScrollableSheet组件来实现。

使用时有以下几点需要注意:

  1. 一般是配合Stack来实现,这里有个小坑,直觉上我们可能使用Positioned为上层Sheet定位,但是会报错,需要使用Align来布局。

  2. snap设为true,再配合snapSizes可以实现分段效果,就是拖拽结束后会回弹到指定的高度。

  3. 有些复杂的业务场景还需要外部代码控制sheet高度,在初始化时传入DraggableScrollableController,通过controller来控制, controller设置后的值是不会触发snap的回弹效果的。

最后放上一个简易封装以供参考:

import 'package:flutter/material.dart';

class DraggableSheetPage extends StatelessWidget {
  const DraggableSheetPage({
    super.key,
    this.controller,
    this.initialChildSize,
    this.minChildSize,
    this.maxChildSize,
    this.snap,
    this.snapSizes,
    required this.childrenBuilder,
    required this.sheetBuilder,
  });

  final DraggableScrollableController? controller;

  final List<Widget> Function(BuildContext context) childrenBuilder;
  final Widget Function(
      BuildContext context,
      ScrollController scrollController,
      ) sheetBuilder;

  /// 默认值是 `0.5`.
  final double? initialChildSize;

  /// 默认值是 `0.1`.
  final double? minChildSize;

  /// 默认值是 `0.8`.
  final double? maxChildSize;

  /// true 表示开启分段, 拖拽结束后会会回弹到固定位置
  final bool? snap;

  /// 划分具体的回弹位置,大小必须在[minChildSize]和[maxChildSize]之间
  final List<double>? snapSizes;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        ...childrenBuilder(context),
        // 底部可拖动面板
        Align(
          alignment: Alignment.bottomCenter,
          child: DraggableScrollableSheet(
            initialChildSize: initialChildSize ?? 0.5,
            minChildSize: minChildSize ?? 0.1,
            maxChildSize: maxChildSize ?? 0.8,
            snap: snap ?? true,
            snapSizes: snapSizes ?? [0.5],
            builder: (BuildContext context, ScrollController scrollController) {
              return Card(
                elevation: 0.1,
                margin: const EdgeInsets.all(0),
                shape: const RoundedRectangleBorder(
                  borderRadius: BorderRadius.only(
                    topLeft: Radius.circular(16),
                    topRight: Radius.circular(16),
                  ),
                ),
                child: sheetBuilder(context, scrollController),
              );
            },
          ),
        ),
      ],
    );
  }
}