Flutter框架提供了Material Design风格的线性进度条(LinearProgressIndicator)组件,就是下面的样子,方方正正的,一点也不圆润。
但是很多APP的设计都按照Material Design风格来玩的,各种各样的都有,我们选择最常见的一种来看一下,下面是“淘宝APP->淘抢购”页面里面的进度条,还是带动画的。
如果直接用线性进度条(LinearProgressIndicator
)组件去做,是没办法实现上面的进度条的。正常的话会遇到下面几个问题:
- List item
- 没有参数可以设置圆角
- 组件自带的动画效果是无限循环的
- 设置了value参数就没有动画效果
- 没有参数可以设置高度
- 没有参数可以设置中间的文本组件
但是上面的问题都可以被解决,下面就是具体的解决方案了。
方法一:
设置圆角
为进度条设置圆角边框的方法就是圆角矩形剪裁(ClipRRect
)组件,用圆角矩形剪裁(ClipRRect
)组件把线性进度条(LinearProgressIndicator
)组件包装起来就可以了。
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo 主页'),
),
// 圆角矩形剪裁(`ClipRRect`)组件,使用圆角矩形剪辑其子项的组件。
body: ClipRRect(
// 边界半径(`borderRadius`)属性,圆角的边界半径。
borderRadius: BorderRadius.all(Radius.circular(10.0)),
child: LinearProgressIndicator(
value: 0.6,
backgroundColor: Color(0xffFFE3E3),
valueColor: AlwaysStoppedAnimation<Color>(Color(0xffFF4964)),
),
),
);
}
}
设置高度与宽度
为进度条添加高度与宽度限制的方法也不难,就是用大小框(SizedBox
)组件包装线性进度条(LinearProgressIndicator
)组件。
设置内容文本
为进度条的里面配置描述文本,和上面一样,就是继续包装(PS:Flutter的布局方式真的是过度包装),用堆栈(Stack
)组件包装线性进度条(LinearProgressIndicator
)组件。
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo 主页'),
),
body: Stack(
children: <Widget>[
SizedBox(
height: 10.0,
width: 98.0,
// 圆角矩形剪裁(`ClipRRect`)组件,使用圆角矩形剪辑其子项的组件。
child: ClipRRect(
// 边界半径(`borderRadius`)属性,圆角的边界半径。
borderRadius: BorderRadius.all(Radius.circular(10.0)),
child: LinearProgressIndicator(
value: 0.6,
backgroundColor: Color(0xffFFE3E3),
valueColor: AlwaysStoppedAnimation<Color>(Color(0xffFF4964)),
),
),
),
Container(
height: 10.0,
width: 98.0,
padding: EdgeInsets.only(left: 7.0),
alignment: Alignment.centerLeft,
child: Text(
'已抢60%',
style: TextStyle(
color: Color(0xffFFFFFF),
fontSize: 8.0,
),
),
),
],
),
);
}
}
设置动画效果
要为进度条设置动画效果,就不得不提到动画控制器(AnimationController
)和补间(Tween
)组件,Flutter中的大部分动画效果都可以用它们实现。具体怎么使用它们,请参考下面的代码。
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
/// 当前的进度。
double currentProgress = 0.6;
// 动画相关控制器与补间。
AnimationController animation;
Tween<double> tween;
@override
void initState() {
// AnimationController({
// double value,
// Duration duration,
// String debugLabel,
// double lowerBound: 0.0,
// double upperBound: 1.0,
// TickerProvider vsync
// })
// 创建动画控制器
animation = AnimationController(
// 这个动画应该持续的时间长短。
duration: const Duration(milliseconds: 900),
vsync: this,
// void addListener(
// VoidCallback listener
// )
// 每次动画值更改时调用监听器
// 可以使用removeListener删除监听器
)..addListener(() {
setState(() {});
});
// Tween({T begin, T end }):创建tween(补间)
tween = Tween<double>(
begin: 0.0,
end: currentProgress,
);
// 开始向前运行这个动画(朝向最后)
animation.forward();
super.initState();
}
@override
void dispose() {
animation.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// TODO:构建页面的主要内容。
}
}
在上面的代码中,我们已经设置好了动画与补间,下面就把线性进度条(LinearProgressIndicator
)组件中的value
属性值设置成动画控制。
// TODO:构建页面的主要内容。
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo 主页'),
),
body: Stack(
children: <Widget>[
SizedBox(
height: 10.0,
width: 98.0,
// 圆角矩形剪裁(`ClipRRect`)组件,使用圆角矩形剪辑其子项的组件。
child: ClipRRect(
// 边界半径(`borderRadius`)属性,圆角的边界半径。
borderRadius: BorderRadius.all(Radius.circular(10.0)),
child: LinearProgressIndicator(
// Animation<T> animate(
// Animation<double> parent
// )
// 返回一个由给定动画驱动的新动画,但它承担由该对象确定的值
value: tween.animate(animation).value,
backgroundColor: Color(0xffFFE3E3),
valueColor: AlwaysStoppedAnimation<Color>(Color(0xffFF4964)),
),
),
),
Container(
height: 10.0,
width: 98.0,
padding: EdgeInsets.only(left: 7.0),
alignment: Alignment.centerLeft,
child: Text(
'已抢${(currentProgress * 100).toInt()}%',
style: TextStyle(
color: Color(0xffFFFFFF),
fontSize: 8.0,
fontFamily: 'PingFangRegular',
),
),
),
],
),
);
这样进度条在构建时就会开始播放前进动画了。
方法二:
///横向进度条
Widget _LinearProgressIndicator(int attendance, int total) {
double lineW = 0;
if (total != 0) {// 81 是左右边距之和
lineW = (ScreenUtil.screenWidth - 81) * attendance / total;
}
return Container(
margin: EdgeInsets.only(left: 41, right: tyW(40), top: 68),
child: Stack(
children: <Widget>[
Container(
height: tyW(4),
decoration: new BoxDecoration(
color: KColorConstant.ffe8e9eb,
borderRadius: new BorderRadius.circular((2)), // 圆角度
),
),
Positioned(
left: 0,
child: Container(
width: lineW,
height: 4,
decoration: new BoxDecoration(
color: KColorConstant.ff2b78d6,
borderRadius: new BorderRadius.circular((2)), // 圆角度
),
),
)
],
),
);
}