2

【Flutter 专题】105 图解自定义 ACEPageMenu 滑动菜单 (一)

 3 years ago
source link: https://my.oschina.net/u/4580472/blog/4795609
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

      和尚尝试做一个类似 BottomSheet 的滑动 Menu,不局限于底部,可以从屏幕四周滑出;因涉及内容较多,和尚计划拆分开来总结和完善,先介绍大体结构,之后再详细学习;

39b97c4e-9790-4c5b-a2f7-40e15dfda8ba.gif

      和尚自定义的 ACEPageMenu 滑动菜单在绘制及动画主要涉及两方面,和尚简单介绍;

d95ebe57-6c64-456f-9bce-a6996b1bc353.png

AnimatedBuilder

      和尚需要 Menu 从屏幕四周滑动出来,此时一定需要 Animation 动画,而对于动画,和尚尝试用 AnimatedBuilder 来处理,虽然需要设置 AnimatedController 等,但对于动画的处理相对灵活;

1. AnimationController

      首先需要设置一个 Animation 控制器,在指定的 Duration 时长内,屏幕绘制过程中,会线性的生成 0.0-1.0 的数值用来控制动画的开始与结束以及设置动画的监听;通过 vsync 防止在屏幕外的 Animation 消耗不必要资源;

      使用 AnimationController 时需要注意在 initState() 生命周期中进行初始化和在 dispose() 结束生命周期时进行销毁;同时可以通过 addStatusListener() 对动画过程进行监听;

      a. AnimationStatus.forward 为动画开始时的回调监听,与 AnimationController.forward() 对应;

      b. AnimationStatus.completed 为动画执行结束时的回调监听;

      c. AnimationStatus.reverse 为动画反向执行时的回调监听,与 AnimationController.reverse() 对应;

      d. AnimationStatus.dismissed 为动画反向执行结束时的回调监听;

@override
void initState() {
  super.initState();
  _controller = AnimationController(
      duration: const Duration(milliseconds: 600), vsync: this);
  _controller.addStatusListener((status) {
    switch(status){
      case AnimationStatus.dismissed:
        print("Current status is dismissed !");
        break;
      case AnimationStatus.forward:
        print("Current status is forward !");
        break;
      case AnimationStatus.reverse:
        print("Current status is reverse !");
        break;
      case AnimationStatus.completed:
        print("Current status is completed !");
        break;
    }
  });
}

@override
void dispose() {
  _controller.dispose();
  super.dispose();
}

2. AnimatedBuilder

      AnimationController 之后需要设置具体 Menu Widget 所在的 AnimatedBuilder 动画构造器;在其中设置平移动画,并与 AnimationController 控制器进行关联;具体的动画相关的会在之后的博客中继续详细学习;

return AnimatedBuilder(
    animation: _controller,
    child: Container(
        color: Color(0xF3242424),
        height: 200.0,
        width: ScreenUtils.getScreenWidth()),
    builder: (BuildContext context, Widget child) {
      return Transform.translate(offset: Offset(0, _controller.value * 50), child: child);
    });
79b18a31-041c-43aa-a9b9-be8bc8dbd0cc.gif

SingleChildLayoutDelegate

      动画的处理基本搞定,重要的是如何让 Widget 从屏幕四周外部开始平移,此时和尚尝试用 SingleChildLayoutDelegate 来处理;

      SingleChildLayoutDelegate 是用于计算带有单个子对象的渲染对象的布局的委托,其本身是一个抽象类,需要自己实现对应的 Delegate 委托;和尚自定义一个 ACEMenuDelegate,主要实现两个方法,分别为:确定要应用于子项的约束的 getConstraintsForChild() 和确定子项位置的 getPositionForChild()

      当提供对应的实例时,应调用 shouldRelayout(),判断实例是否实际代表其他信息;具体的应用和尚会在之后的博客中进一步学习;

class ACEMenuDelegate extends SingleChildLayoutDelegate {
  final MenuType _menuType;
  final double _controllerValue;

ACEMenuDelegate(this._menuType, this._controllerValue);

@override
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
    return BoxConstraints(
        minWidth: (_menuType == MenuType.MENU_LEFT ||
                _menuType == MenuType.MENU_RIGHT)
            ? 0
            : constraints.maxWidth,
        maxWidth: (_menuType == MenuType.MENU_LEFT ||
                _menuType == MenuType.MENU_RIGHT)
            ? ScreenUtils.getScreenWidth() * 0.75
            : constraints.maxWidth,
        minHeight: 0.0,
        maxHeight: (_menuType == MenuType.MENU_LEFT ||
                _menuType == MenuType.MENU_RIGHT)
            ? constraints.maxHeight
            : constraints.maxHeight * 0.45);
  }

@override
  Offset getPositionForChild(Size size, Size childSize) {
    double _offsetX = Offset.zero.dx, _offsetY = Offset.zero.dy;
    switch (_menuType) {
      case MenuType.MENU_TOP:
        _offsetY = -childSize.height * (1 - _controllerValue);
        break;
      case MenuType.MENU_BOTTOM:
        _offsetY = size.height - childSize.height * _controllerValue;
        break;
      case MenuType.MENU_LEFT:
        _offsetX = -childSize.width * (1 - _controllerValue);
        break;
      case MenuType.MENU_RIGHT:
        _offsetX = size.width - childSize.width * _controllerValue;
        break;
    }
    return Offset(_offsetX, _offsetY);
  }

@override
  bool shouldRelayout(ACEMenuDelegate oldDelegate) {
    return _controllerValue != oldDelegate._controllerValue;
  }
}

      ACEPageMenu 源码


      和尚今天只是大概介绍一下功能实现,对于细节部分以及手势操作正在进一步完善,对于动画和委托的学习会在之后进一步学习;如有错误,请多多指导!

来源:阿策小和尚

cc028041-3614-428b-8e7e-bc237c52b49b.png

本文分享自微信公众号 - 阿策小和尚(gh_8297e718c166)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK