Flutter实现抽屉动画

2022-07-16,,

这篇会深化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()方法。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

《Flutter实现抽屉动画.doc》

下载本文的Word格式文档,以方便收藏与打印。