child: new Center(child:
new Text(“+” + _counter.toString(),
style: new TextStyle(color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 15.0),))
)),
bottom: 100.0
);
}
Widget getClapButton() {
// Using custom gesture detector because we want to keep increasing the claps
// when user holds the button.
return new GestureDetector(
onTapUp: onTapUp,
onTapDown: onTapDown,
child: new Container(
height: 60.0 ,
width: 60.0 ,
padding: new EdgeInsets.all(10.0),
decoration: new BoxDecoration(
border: new Border.all(color: Colors.pink, width: 1.0),
borderRadius: new BorderRadius.circular(50.0),
color: Colors.white,
boxShadow: [
new BoxShadow(color: Colors.pink, blurRadius: 8.0)
]
),
child: new ImageIcon(
new AssetImage(“images/clap.png”), color: Colors.pink,
size: 40.0),
)
);
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
new Text(
‘You have pushed the button this many times:’,
),
new Text(
‘$_counter’,
style: Theme
.of(context)
.textTheme
.display1,
),
],
),
),
floatingActionButton: new Padding(
padding: new EdgeInsets.only(right: 20.0),
child: new Stack(
alignment: FractionalOffset.center,
overflow: Overflow.visible,
children: [
getScoreButton(),
getClapButton(),
],
)
),
);
}
}
看了上面最终的效果图,我们需要做2件事:
- 更改
widgets
的大小。 - 按下按钮时显示分数
widget
,释放按钮时将其隐藏。 - 添加这些小巧的
widget
并为其设置动画。
让我们一个接一个地慢慢增加学习曲线。首先,我们需要了解有关Flutter动画的一些基本知识。
了解Flutter中基本动画的组件
动画不过是随着时间变化的一些值,例如,当我们点击按钮时,我们希望用动画来让显示分数widget
从底部升起,而当手指离开按钮时,继续上升然后隐藏。
如果仅看分数Widget
,我们需要在一段时间内更改Widget
的位置和不透明度值。
new Positioned(
child: new Opacity(opacity: 1.0,
child: new Container(
…
)),
bottom: 100.0
);
假设我们希望分数widget需要150毫秒才能从底部显示出来。在以下时间轴上考虑一下:
这是一个简单的2D图形。 position
将随着时间而改变。 请注意,对角线是直线。如果你喜欢,它也可以是曲线。
你可以使position
随时间缓慢增加,然后变得越来越快。或者,你也可以让它以超高速进入,然后在最后放慢速度。
下面是我们介绍的第一个组件:Animation Controller
。
scoreInAnimationController = new AnimationController(duration: new Duration(milliseconds: 150), vsync: this);
在这里,我们为动画创建了一个简单的控制器(Controller
)。我们已经指定希望动画运行150ms
。但是,vsync
是什么东西?
移动设备每隔几毫秒刷新一次屏幕。这就是我们将一组图像视为连续流或电影的方式。
屏幕刷新的速率因设备而异。 假设移动设备每秒刷新屏幕60次(每秒60帧)。 那就是每16.67毫秒之后,我们就会向大脑提供新的图像。 有时,图像就会错位(在屏幕刷新时发出不同的图像),并且看到屏幕撕裂。 VSync
就是解决这个问题的。
我们给控制器
设置一个监听器,然后开始动画:
scoreInAnimationController.addListener(() {
print(scoreInAnimationController.value);
});
scoreInAnimationController.forward(from: 0.0);
/* OUTPUT
I/flutter ( 1913): 0.0
I/flutter ( 1913): 0.0
I/flutter ( 1913): 0.22297333333333333
I/flutter ( 1913): 0.3344533333333333
I/flutter ( 1913): 0.4459333333333334
I/flutter ( 1913): 0.5574133333333334
I/flutter ( 1913): 0.6688933333333335
I/flutter ( 1913): 0.7803666666666668
I/flutter ( 1913): 0.8918466666666668
I/flutter ( 1913): 1.0
*/
控制器在150ms
内生成了0.0
到1.0
的数字。请注意,生成的值几乎是线性的。 0.2
、0.3
、0.4
…我们如何改变这种行为?这将在第二部分完成:曲线动画
。
曲线动画
bounceInAnimation = new CurvedAnimation(parent: scoreInAnimationController, curve: Curves.bounceIn);
bounceInAnimation.addListener(() {
print(bounceInAnimation.value);
});
/*OUTPUT
I/flutter ( 5221): 0.0
I/flutter ( 5221): 0.0
I/flutter ( 5221): 0.24945376519722218
I/flutter ( 5221): 0.16975716286388898
I/flutter ( 5221): 0.17177866222222238
I/flutter ( 5221): 0.6359024059750003
I/flutter ( 5221): 0.9119433941222221
I/flutter ( 5221): 1.0
*/
通过将parent
属性设置为我们的控制器,并提供动画遵循曲线,就可以创建一个CurvedAnimation
,Flutter曲线文档页面上提供了多种曲线供我们选择:api.flutter.dev/flutter/ani…
控制器在150ms
的时间内为曲线动画Widget
提供从0.0
到1.0
的值。曲线动画Widget
根据我们设置的曲线对这些值进行插值。
尽管我们得到了0.0
到1.0
之间的一系列值,但是我们希望显示分数的Widget
显示的值为0-100
,我们可以简单地乘以100来得到结果,或者我们可以使用第三个组件:Tween
类。
tweenAnimation = new Tween(begin: 0.0, end: 100.0).animate(scoreInAnimationController);
tweenAnimation.addListener(() {
print(tweenAnimation.value);
});
/* Output
I/flutter ( 2639): 0.0
I/flutter ( 2639): 0.0
I/flutter ( 2639): 33.452000000000005
I/flutter ( 2639): 44.602000000000004
I/flutter ( 2639): 55.75133333333334
I/flutter ( 2639): 66.90133333333334
I/flutter ( 2639): 78.05133333333333
I/flutter ( 2639): 89.20066666666668
I/flutter ( 2639): 100.0
*/
Tween
类生成begin
到end
之间的值,前面我们已经使用过线性的scoreInAnimationController
,相反,我们可以使用反弹曲线来获得不同的值。Tween
的优点远不止这些,你还可以补间其他东西,比如你可以补间color(颜色)
、offset(偏移量)
、position(位置)
、和其他Widget属性,从而进一步扩展了基础补间类。
Score Widget 位置动画
至此,我们已经掌握了足够的知识,现在可以使我们的得分Widget
在按下按钮时从底部弹出,而在离开时隐藏。
initState() {
super.initState();
scoreInAnimationController = new AnimationController(duration: new Duration(milliseconds: 150), vsync: this);
scoreInAnimationController.addListener((){
setState(() {}); // Calls render function
});
}
void onTapDown(TapDownDetails tap) {
scoreInAnimationController.forward(from: 0.0);
…
}
Widget getScoreButton() {
var scorePosition = scoreInAnimationController.value * 100;
var scoreOpacity = scoreInAnimationController.value;
return new Positioned(
child: new Opacity(opacity: scoreOpacity,
child: new Container(…)
),
bottom: scorePosition
);
}
如上图所示,点击按钮,Score Widget
从底部弹出了,但是这儿还有一个小问题:当多次点击按钮的时候,score widget
一次又一次的弹出,这是由于上述代码中的一个小错误。每次点击按钮时,我们都告诉控制器从0开始,即forward(from: 0.0)
。
score widget 退出动画
现在,我们为score Widget
添加退出动画,首先,我们添加一个枚举来更轻松地管理score Widget
的状态。
enum ScoreWidgetStatus {
HIDDEN,
BECOMING_VISIBLE,
BECOMING_INVISIBLE
}
然后,创建一个退出动画的控制器,动画控制器将使score widget
的位置从100
非线性变化到150
。我们还为动画添加了状态监听器。动画结束后,我们将得分组件
的状态设置为隐藏。
scoreOutAnimationController = new AnimationController(vsync: this, duration: duration);
scoreOutPositionAnimation = new Tween(begin: 100.0, end: 150.0).animate(
new CurvedAnimation(parent: scoreOutAnimationController, curve: Curves.easeOut)
);
scoreOutPositionAnimation.addListener((){
setState(() {});
});
scoreOutAnimationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_scoreWidgetStatus = ScoreWidgetStatus.HIDDEN;
}
});
当用户手指离开组件的时候,我们将相应地设置状态,并启动300毫秒
的计时器。 300毫秒
后,我们将为得分组件
添加位置和不透明度动画。
void onTapUp(TapUpDetails tap) {
// User removed his finger from button.
scoreOutETA = new Timer(duration, () {
scoreOutAnimationController.forward(from: 0.0);
_scoreWidgetStatus = ScoreWidgetStatus.BECOMING_INVISIBLE;
});
holdTimer.cancel();
}
我们还修改了onTapDown
事件以处理某些边角情况。
void onTapDown(TapDownDetails tap) {
// User pressed the button. This can be a tap or a hold.
if (scoreOutETA != null) scoreOutETA.cancel(); // We do not want the score to vanish!
if (_scoreWidgetStatus == ScoreWidgetStatus.HIDDEN) {
scoreInAnimationController.forward(from: 0.0);
_scoreWidgetStatus = ScoreWidgetStatus.BECOMING_VISIBLE;
}
increment(null); // Take care of tap
holdTimer = new Timer.periodic(duration, increment); // Takes care of hold
}
最后,我们需要选择用于score widget
的位置和不透明度的控制器值。一个简单的开关就完成了。
Widget getScoreButton() {
var scorePosition = 0.0;
var scoreOpacity = 0.0;
switch(_scoreWidgetStatus) {
case ScoreWidgetStatus.HIDDEN:
break;
case ScoreWidgetStatus.BECOMING_VISIBLE :
scorePosition = scoreInAnimationController.value * 100;
scoreOpacity = scoreInAnimationController.value;
break;
case ScoreWidgetStatus.BECOMING_INVISIBLE:
scorePosition = scoreOutPositionAnimation.value;
scoreOpacity = 1.0 - scoreOutAnimationController.value;
}
return …
}
score widget
的运行效果很棒,先弹出然后逐渐消失。
Score Widget 尺寸动画
到这一步,我们几乎知道如何在分数增加时也改变大小。让我们快速添加大小动画,然后继续搞火花闪烁效果
。
我已经更新了ScoreWidgetStatus
枚举来保留一个额外的VISIBLE
值。现在,我们为size
属性添加一个新的控制器。
scoreSizeAnimationController = new AnimationController(vsync: this, duration: new Duration(milliseconds: 150));
scoreSizeAnimationController.addStatusListener((status) {
if(status == AnimationStatus.completed) {
scoreSizeAnimationController.reverse();
}
});
scoreSizeAnimationController.addListener((){
setState(() {});
});
控制器在150ms
的时间内生成从0
到1
的值,完成之后((status == AnimationStatus.completed
),又会生成从1
到0
的值。这会产生很好的增长和收缩效果。
void increment(Timer t) {
scoreSizeAnimationController.forward(from: 0.0);
setState(() {
_counter++;
});
我们需要注意处理枚举的visible
属性情况。为此,我们需要在 Touch down
事件中添加一些基本条件。
void onTapDown(TapDownDetails tap) {
// User pressed the button. This can be a tap or a hold.
if (scoreOutETA != null) {
scoreOutETA.cancel(); // We do not want the score to vanish!
}
if(_scoreWidgetStatus == ScoreWidgetStatus.BECOMING_INVISIBLE) {
// We tapped down while the widget was flying up. Need to cancel that animation.
scoreOutAnimationController.stop(canceled: true);
_scoreWidgetStatus = ScoreWidgetStatus.VISIBLE;
}
else if (_scoreWidgetStatus == ScoreWidgetStatus.HIDDEN ) {
_scoreWidgetStatus = ScoreWidgetStatus.BECOMING_VISIBLE;
scoreInAnimationController.forward(from: 0.0);
}
increment(null); // Take care of tap
holdTimer = new Timer.periodic(duration, increment); // Takes care of hold
}
最后,我们使用Widget
中控制器的值
extraSize = scoreSizeAnimationController.value * 10;
…
height: 50.0 + extraSize,
width: 50.0 + extraSize,
…
完整的代码,可以在github(gist.github.com/Kartik1607/… 中找到。我们同时使用大小和位置动画。大小动画需要一些调整,我们最后会介绍。
最后,火花闪烁动画
在进行火花闪烁动画
之前,我们需要对尺寸动画进行一些调整。目前,该按钮已增长太多。解决方法很简单,我们将额外的乘数从10
更改为一个较小的数字。
现在来看看火花闪烁
动画,我们可以看到到火花
其实就是位置在变化的5
张图片
我在MS Paint
中制作了一个三角形和一个圆形的图片,并将其保存为flutter资源。然后,我们就可以将该图片用作Image asset
。
在实现动画之前,让我们考虑一下定位以及需要完成的一些任务:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/8ecfd2ec13eb429b4a8f9028b620d57a.jpeg)
文末
好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。
这里放上一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
文末
好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。
这里放上一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家
[外链图片转存中…(img-UP1aTHRv-1712512547448)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!