Flutter实现抽屉动画

Valora ·
更新时间:2024-11-13
· 1560 次阅读

这篇会深化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 抽屉 动画

需要 登录 后方可回复, 如果你还没有账号请 注册新账号