Flutter中自定义Drawer实现

1,134 阅读2分钟

最近产品同学想要改变一下app的首页样式,做一个如下面所示的效果。

之前在做安卓原生项目的时候,看到有人实现过这个效果,这次想了想Flutter下的实现方式,现在记录一下代码和实现思路。

1、创建Flutter工程,创建自定义Drawer类

创建CustomDrawer,将主页面作为一个child传入。

class CustomDrawer extends StatefulWidget {
  final Widget child;
  
  static CustomDrawerState of(BuildContext context) =>
      context.findRootAncestorStateOfType<CustomDrawerState>();

  const CustomDrawer({Key key, this.child}) : super(key: key);

  @override
  CustomDrawerState createState() => CustomDrawerState();
}

main.dart

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: CustomDrawer(child: MyHomePage(title: 'Flutter Demo Home Page'),),
    );
  }
}

2、创建AnimationController

因为涉及到动画操作,所以需要创建AnimationController对象,指定动画持续时间,定义一些动画过程中的常量。

class CustomDrawerState extends State<CustomDrawer>
    with SingleTickerProviderStateMixin {
  //动画持续时间
  static const Duration toggleDuration = Duration(microseconds: 250);
  //主窗口最大右移距离
  static const double maxSlide = 225;
  //拖动打开Drawer时,打开窗口的最做左边的位置
  static const double minDragStartEdge = 60;
  //拖动打开Drawer时,打开窗口操作的最右边的位置
  static const double maxDragStartEdge = maxSlide - 16;
  AnimationController _animationController;
  //是否可以拖动的标志位
  bool _canDrag = false;

  @override
  void initState() {
    super.initState();
    //创建AnimationController对象,指定动画时间
    _animationController = AnimationController(
        vsync: this, duration: CustomDrawerState.toggleDuration);
  }

3、定义抽屉的打开,关闭操作

  void close() {
    _animationController.reverse();
  }

  void open() {
    _animationController.forward();
  }

  void toggleDrawer() {
    _animationController.isCompleted ? close() : open();
  }

4、动画过程中,刷新页面

拖动过程中,需要用到GestureDector,动画过程中,通过动画执行进度,获得child组件应该平移的位置和缩放程度,更新页面。

  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: ()async{
        //当抽屉打开时,点击返回先关闭抽屉
        if (_animationController.isCompleted) {
          close();
          return false;
        }
        return true;
      },
      child: GestureDetector(
        //拖动开始执行事件
        onHorizontalDragStart: _onDragStart,
        //拖动过程中更新事件
        onHorizontalDragUpdate: _onDragUpdate,
        //拖动结束时执行事件
        onHorizontalDragEnd: _onDragEnd,
        child: AnimatedBuilder(
          animation: _animationController,
          child: widget.child,
          builder: (context, child) {
            //当前动画执行进度
            double animValue = _animationController.value;
            //根据进度计算child偏移量
            final slideAmout = maxSlide * animValue;
            //根据进度计算child缩放程度,最大缩放到70%
            final contentScale = 1.0 - 0.3 * animValue;
            return Stack(
              children: <Widget>[
                MyDrawer(),
                Transform(
                  transform: Matrix4.identity()
                    ..translate(slideAmout)
                    ..scale(contentScale,contentScale),
                  alignment: Alignment.centerLeft,
                  child: GestureDetector(
                    //当抽屉打开时,点击child组件,关闭抽屉
                    onTap: _animationController.isCompleted ? close :null,
                    child: widget.child,
                  ),
                ),
              ],
            );
          },
        ),
      ),
    );
  }

5、实现onHorizontalDragStart,onHorizontalDragUpdate、onHorizontalDragEnd 方法

 void _onDragStart(DragStartDetails details) {
    //判断拖动是否是打开抽屉
    bool isDragOpenFromLeft = _animationController.isDismissed &&
        details.globalPosition.dx < minDragStartEdge;
    //判断拖动是否是关闭抽屉
    bool isDragCloseFromRight = _animationController.isCompleted &&
        details.globalPosition.dx > maxDragStartEdge;
    _canDrag = isDragOpenFromLeft || isDragCloseFromRight;
  }

  void _onDragUpdate(DragUpdateDetails details) {
    if(_canDrag){
        //根据偏移量得到当前的动画进度
        double delta = details.primaryDelta / maxSlide;
        _animationController.value += delta;
    }
  }

  void _onDragEnd(DragEndDetails details) {
      //临界值加速度
      double _kMinFlingVelocity = 365.0;
      //如果当前动画在开始时已经结束 或者 已经执行完毕,
      if(_animationController.isDismissed || _animationController.isCompleted){
        return;
      }

      if(details.velocity.pixelsPerSecond.dx.abs() >= _kMinFlingVelocity){
        //如果当前加速度大于临界值,计算得到可见加速度
        double visualVelcoity = details.velocity.pixelsPerSecond.dx /
            MediaQuery.of(context).size.width;
            //fling()函数允许您提供速度(velocity)、力量(force)、position(通过Force对象)
            _animationController.fling(velocity: visualVelcoity);
      }else if(_animationController.value < 0.5){
          //动画执行不到一半,关闭
          close();
      }else{
        //执行大于一半,打开抽屉
        open();
      }
  }

源码地址:github.com/chenjinguan…