共计 4250 个字符,预计需要花费 11 分钟才能阅读完成。
简介
flutter 中自带了 drawer 组件,能够实现通用的菜单性能,那么有没有一种可能,咱们能够通过自定义动画来实现一个别样的菜单呢?
答案是必定的,一起来看看吧。
定义一个菜单我的项目
因为这里的次要目标是实现菜单的动画,所以这里的菜单比较简单,咱们的 menu 是一个 StatefulWidget, 外面就是一个 Column 组件,column 中有四行诗:
static const _menuTitles = [
'迟日江山丽',
'春风花草香',
'泥融飞燕子',
'沙暖睡鸳鸯',
];
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child:_buildContent());
}
Widget _buildContent() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [const SizedBox(height: 16),
..._buildListItems()],
);
}
List<Widget> _buildListItems() {final listItems = <Widget>[];
for (var i = 0; i < _menuTitles.length; ++i) {
listItems.add(
Padding(padding: const EdgeInsets.symmetric(horizontal: 36.0, vertical: 16),
child: Text(_menuTitles[i],
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.w500,
),
),
)
);
}
return listItems;
}
让 menu 动起来
怎么让 menu 动起来呢?咱们须要给最外层的 AnimateMenuApp 增加一个 AnimationController, 所以须要在_AnimateMenuAppState 增加 SingleTickerProviderStateMixin 的 mixin, 如下所示:
class _AnimateMenuAppState extends State<AnimateMenuApp>
with SingleTickerProviderStateMixin {late AnimationController _drawerSlideController;
而后在 initState 中对_drawerSlideController 进行初始化:
void initState() {super.initState();
_drawerSlideController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 150),
);
}
在让 menu 动起来之前,咱们须要设计一下动画的款式。如果咱们的动画是让 menu 从右向左飞出。那么咱们能够应用 FractionalTranslation 来进行 offset 进行地位变换。
并且当菜单没有开启的时候,咱们须要显示一个空的组件,这里用 SizedBox 来代替。
当菜单开启的时候,就执行这个 FractionalTranslation 的动画,所以咱们的 build 办法须要这样写:
Widget _buildDrawer() {
return AnimatedBuilder(
animation: _drawerSlideController,
builder: (context, child) {
return FractionalTranslation(translation: Offset(1.0 - _drawerSlideController.value, 0.0),
child: _isDrawerClosed() ? const SizedBox() : const Menu(),);
},
);
}
FractionalTranslation 中的 Offset 是依据_drawerSlideController 的 value 来进行变动的。
那么_drawerSlideController 的 value 怎么变动呢?
咱们定义一个_toggleDrawer 办法,在点击菜单按钮的时候来触发这个办法,从而实现_drawerSlideController 的 value 变动:
void _toggleDrawer() {if (_isDrawerOpen() || _isDrawerOpening()) {_drawerSlideController.reverse();
} else {_drawerSlideController.forward();
}
}
同时,咱们定义上面几个判断菜单状态的办法:
bool _isDrawerOpen() {return _drawerSlideController.value == 1.0;}
bool _isDrawerOpening() {return _drawerSlideController.status == AnimationStatus.forward;}
bool _isDrawerClosed() {return _drawerSlideController.value == 0.0;}
因为菜单图标须要依据菜单状态来产生扭转,菜单的状态又是依赖于_drawerSlideController,所以,咱们把 IconButton 放到一个 AnimatedBuilder 外面,从而实现动态变化的成果:
PreferredSizeWidget _buildAppBar() {
return AppBar(
title: const Text(
'动画菜单',
style: TextStyle(color: Colors.black,),
),
backgroundColor: Colors.transparent,
elevation: 0.0,
automaticallyImplyLeading: false,
actions: [
AnimatedBuilder(
animation: _drawerSlideController,
builder: (context, child) {
return IconButton(
onPressed: _toggleDrawer,
icon: _isDrawerOpen() || _isDrawerOpening()
? const Icon(
Icons.clear,
color: Colors.black,
)
: const Icon(
Icons.menu,
color: Colors.black,
),
);
},
),
],
);
}
最初实现的成果如下:
增加菜单外部的动画
下面的例子中整个菜单是作为一个整体来动画的,有没有可能菜单外面的每一个 item 也有本人的动画呢?
答案当然是必定的。
咱们只须要在下面的根底上将 menu 组件增加动画反对即可:
class _MenuState extends State<Menu> with SingleTickerProviderStateMixin
动画中的位移咱们抉择应用 Transform.translate,同时还增加了淡入淡出的成果,也就是把下面例子中的 Padding 用 AnimatedBuilder 包裹起来,如下所示:
List<Widget> _buildListItems() {final listItems = <Widget>[];
for (var i = 0; i < _menuTitles.length; ++i) {
listItems.add(
AnimatedBuilder(
animation: _itemController,
builder: (context, child) {
final animationPercent = Curves.easeOut.transform(_itemSlideIntervals[i].transform(_itemController.value),
);
final opacity = animationPercent;
final slideDistance = (1.0 - animationPercent) * 150;
return Opacity(
opacity: opacity,
child: Transform.translate(offset: Offset(slideDistance, 0),
child: child,
),
);
},
child: Padding(padding: const EdgeInsets.symmetric(horizontal: 36.0, vertical: 16),
child: Text(_menuTitles[i],
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.w500,
),
),
),
),
);
}
return listItems;
}
AnimatedBuilder 中的 builder 返回的是一个 Opacity 对象,外面蕴含了 opacity 和 child 两个属性。其中最终要的一个变动值是 animationPercent,这个值是依据_itemController 的 value 和初始设置的各个 item 的变动工夫来决定的。
每个 item 的值是不一样的:
void _createAnimationIntervals() {for (var i = 0; i < _menuTitles.length; ++i) {final startTime = _initialDelayTime + (_staggerTime * i);
final endTime = startTime + _itemSlideTime;
_itemSlideIntervals.add(
Interval(
startTime.inMilliseconds / _animationDuration.inMilliseconds,
endTime.inMilliseconds / _animationDuration.inMilliseconds,
),
);
}
}
最初运行后果如下:
总结
在 flutter 中所有皆可动画,咱们只须要把握动画创作的窍门即可。
本文的例子:https://github.com/ddean2009/learn-flutter.git