Flutter实现抽屉动画

作者: Android_until | 来源:发表于2019-08-09 17:30 被阅读11次

    这篇会深化View拖拽实例,利用Flutter Animation、插值器以及AnimatedBuilder教大家实现带动画的抽屉效果。先来看效果:

    通过构思,我们可以设想到实现抽屉的方式就是用Stack控件将两个Widget叠加显示,用GestureDetector监听手势滑动,动态移动顶层的Widget,当监听到手势结束的时候根据手势滑动的距离动态将顶部Widget利用动画效果滑动到结束位置即可。

    实现底部Widget

    class DownDrawerWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(child: Center(child: Text("底部Widget",),),);
      }
    }
    

    这个Widget太简单了,就不细说了。

    实现顶部Widget

    class UpDrawerWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(child: Center(child: Text("顶部Widget",),),);
      }
    }
    

    实现方式和底部是一样的。

    实现可以移动的容器

    上面两个Widget都是单纯用来显示的Widget,因此继承了StatelessWidget。接下来我们需要根据手势动态移动顶部的Widget,因此需要继承StatefulWidget。

    // 顶部Widget
    class HomePageWidget extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => HomePageState();
    }
    
    class HomePageState extends State<HomePageWidget>
        with SingleTickerProviderStateMixin {
    
      @override
      void initState() {...}
    
      @override
      void dispose() {...}
    
      @override
      Widget build(BuildContext context) {...}
    
      void _onViewDragDown(DragDownDetails callback) {...}
    
      void _onViewDrag(DragUpdateDetails callback) {...}
    
      void _onViewDragUp(DragEndDetails callback) {...}
    }
    

    初始化状态initState()

    这个方法是在Widget初始化的时候系统的回调函数,我们需要在该函数中初始化动画

    AnimationController controller;
    @override
    void initState() {
        // 初始化动画控制器,这里限定动画时常为200毫秒
        controller = new AnimationController(vsync: this, duration: const Duration(milliseconds: 200));
        // vsync对象会绑定动画的定时器到一个可视的widget,所以当widget不显示时,动画定时器将会暂停,当widget再次显示时,动画定时器重新恢复执行,这样就可以避免动画相关UI不在当前屏幕时消耗资源。
        // 当使用vsync: this的时候,State对象必须with SingleTickerProviderStateMixin或TickerProviderStateMixin;TickerProviderStateMixin适用于多AnimationController的情况。
    
        // 设置动画曲线,就是动画插值器
        // 通过这个链接可以了解更多差值器,https://docs.flutter.io/flutter/animation/Curves-class.html,我们这里使用带回弹效果的bounceOut。
        CurvedAnimation curve =
            new CurvedAnimation(parent: controller, curve: Curves.bounceOut);
    
        // 增加动画监听,当手势结束的时候通过动态计算到达目标位置的距离实现动画效果。curve.value为当前动画的值,取值范围0~1。
        curve.addListener(() {
          double animValue = curve.value;
          double offset = dragUpDownX - dragDownX;
          double toPosition;
    
          // 右滑
          if (offset > 0) {
            if (offset > maxDragX / 5) {
              // 打开
              toPosition = maxDragX;
              isOpenState = true;
            } else {
              if (isOpenState) {
                toPosition = maxDragX;
                isOpenState = true;
              } else {
                toPosition = 0.0;
                isOpenState = false;
              }
            }
          } else {
            if (offset < (-maxDragX / 2.0)) {
              // 关
              toPosition = 0.0;
              isOpenState = false;
            } else {
              if (isOpenState) {
                toPosition = maxDragX;
                isOpenState = true;
              } else {
                toPosition = 0.0;
                isOpenState = false;
              }
            }
          }
    
          dragOffset = (toPosition - dragUpDownX) * animValue + dragUpDownX;
          // 刷新位置
          setState(() {});
        });
      }
    

    结束Widget dispose()

    当Widget不可用将被回收的时候,系统会回调dispose()方法,我们在这里回收动画。

    @override
    void dispose() {
        controller.dispose();
    }
    

    记录按下的位置

        double dragDownX = 0.0;
      void _onViewDragDown(DragDownDetails callback) {
        dragDownX = callback.globalPosition.dx;
      }
    

    拖动的时候刷新View的位置

    /**
       * 最大可拖动位置
       */
      final double maxDragX = 230.0;
      double dragOffset = 0.0;
      void _onViewDrag(DragUpdateDetails callback) {
        double tmpOffset = callback.globalPosition.dx - dragDownX;
    
        if (tmpOffset < 0) {
          tmpOffset += maxDragX;
        }
    
        // 边缘检测
        if (tmpOffset < 0) {
          tmpOffset = 0.0;
        } else if (tmpOffset >= maxDragX) {
          tmpOffset = maxDragX;
        }
    
        // 刷新
        if (dragOffset != tmpOffset) {
          dragOffset = tmpOffset;
          setState(() {});
        }
      }
    

    离手的时候记录位置并执行动画

      /**
       * 脱手时候的位置
       */
      double dragUpDownX = 0.0;
      void _onViewDragUp(DragEndDetails callback) {
        dragUpDownX = dragOffset;
        // 执行动画,每次都从第0帧开始执行
        controller.forward(from: 0.0);
      }
    

    支持移动的Widget

    @override
      Widget build(BuildContext context) {
        return Transform.translate(
          offset: Offset(dragOffset, 0.0),
          child: Container(
            child: GestureDetector(
                  onHorizontalDragDown: _onViewDragDown,
                  onVerticalDragDown: _onViewDragDown,
                  onHorizontalDragUpdate: _onViewDrag,
                  onVerticalDragUpdate: _onViewDrag,
                  onHorizontalDragEnd: _onViewDragUp,
                  onVerticalDragEnd: _onViewDragUp,
                  child: Container(
                    child: new UpDrawerWidget(),
              ),),),);}
    

    Flutter动画

    总结一下,想在Flutter中实现动画,需要先创建一个AnimationController控制器;如果有特殊的插值要求,再创建一个插值器,调用controller.forward()方法执行动画,通过addListener()的回调改变对应数值之后调用setState(() {})方法刷新位置即可。
    Flutter API还提供AnimatedBuilder用来简化实现动画的复杂性,让我们不用手动调用addListener()方法。

    学习路线及学习分享

    对于很多初级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

    同时,我整理了一些关于flutter—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿~

    资料免费领取方式:现在关注我并且加入群聊
    群号:1018342383

    相关文章

      网友评论

        本文标题:Flutter实现抽屉动画

        本文链接:https://www.haomeiwen.com/subject/dbihjctx.html