作者:肖瑜博
前言:
这是一款基于harmonyOS + java绘制飘动的爱心心形动画特效源码。画面上多个小的心形图案动态组成一个大的心形图案,图案的边缘随着文字滑过伴随着小图案的动画汇集效果。
效果展示
![HarmonyOS 之 爱心动画-鸿蒙开发者社区 HarmonyOS 之 爱心动画-鸿蒙开发者社区](https://dl-harmonyos.51cto.com/images/202202/2247d9d28072f0e55e6873d50f8d56baff5a78.gif)
实现思路:这个动画的实现主要是四个步骤:
一:16颗小心心,从初始位置,移动到指定的位置,并拼成一颗大心心;
二:16颗小心心渐渐放大,这个时候包括小心心的位置移动和倍数的放大;
三:16颗小心心放大之后,再像中心点移动,并且不断变小;
四:移动到中心点的后,将其中的一颗小心心放大,得到我们最后想要的样子。
一、16颗小心心,从初始位置,移动到指定的位置,并拼成一颗大心心
为了更加方便计算出16颗小心心所移动的位置,我们把拼成大心心之后的图案给做了原点坐标,并根据这个原点,计算出16颗心心的坐标,具体如下图:
![HarmonyOS 之 爱心动画-鸿蒙开发者社区 HarmonyOS 之 爱心动画-鸿蒙开发者社区](https://dl-harmonyos.51cto.com/images/202202/e30ed197380c96771de3626d565a1b06328cd3.png?x-oss-process=image/resize,w_660,h_1328)
如上图所示,我们把16颗小心心进行编号,分别是1~16心心,然后我们计算出屏幕的中心点,并把2号心心底边长的中心点与屏幕中心点重合,据此分别计算出16颗心心的坐标,其中每一颗心心之间都是没有间隔的。由此我们可以计算出每一颗心心的左边为:
|
x |
y |
1号坐标 |
centerX-itemWH*1.5 |
centerY-itemWH*2 |
2号坐标(中间) |
centerX-itemWH*0.5 |
centerY-itemWH |
3号坐标 |
centerX+itemWH*0.5 |
centerY-itemWH*2 |
4号坐标(右边顶部) |
centerX+itemWH*1.5 |
centerY-itemWH*3 |
5号坐标 |
centerX+itemWH*2.5 |
centerY-itemWH*2 |
6号坐标(右边) |
centerX+itemWH*3.5 |
centerY-itemWH |
7号坐标 |
centerX+itemWH*2.5 |
centerY |
8号坐标 |
centerX+itemWH*1.5 |
centerY+itemWH |
9号坐标 |
centerX+itemWH*0.5 |
centerY+itemWH*2 |
10号坐标(底部) |
centerX-itemWH*0.5 |
centerY+itemWH*3 |
11号坐标 |
centerX-itemWH*1.5 |
centerY+itemWH*2 |
12号坐标 |
centerX-itemWH*2.5 |
centerY+itemWH |
13号坐标 |
centerX-itemWH*3.5 |
centerY |
14号坐标(左边) |
centerX-itemWH*4.5 |
centerY-itemWH |
15号坐标 |
centerX-itemWH*3.5 |
centerY-itemWH*2 |
16号坐标(左边顶部) |
centerX-itemWH*2.5 |
centerY-itemWH*3 |
其中centerX、centerY分别为屏幕中心点x和y值;itemWH为小心心的宽高度(宽度和高度相等)。
我们在页面初始化的时候,把所有的坐标记录在locations数组里边;
屏幕宽高度的获取方法:
由此可以计算出centerX、centerY的值为,itemWH设置为30:
由动画我们可以看出,所有的心心都是由16—>14—>10—>6—>4—>2—>1号这样的轨迹运动的。号码越排前的心心运动的路径越长,动画越多,所以我们可以归结为:
7个动画的心心(1号);
6个动画的心心(2-3号);
5个动画的心心(4-5号);
4个动画的心心(6-9号);
3个动画的心心(10-13号);
2个动画的心心(14-15号);
1个动画的心心(16号)。
private AnimatorProperty creatAnimationPropertyFromLocation(float[] fromLoc, float[] toLoc, long delay, long duration, Image image){
AnimatorProperty animatorProperty = image.createAnimatorProperty();
animatorProperty.moveFromX(fromLoc[0]);
animatorProperty.moveFromY(fromLoc[1]);
animatorProperty.moveToX(toLoc[0]);
animatorProperty.moveToY(toLoc[1]);
animatorProperty.setDelay(delay);
animatorProperty.setDuration(duration);
return animatorProperty;
}
private AnimatorProperty originLocationToOneAnimation(Image image, long aniDelay){
float[] toLoc1 = locations.get(15);
AnimatorProperty animatorProperty = creatAnimationPropertyFromLocation(image.getContentPosition(), toLoc1, aniDelay, firstAniDur, image);
return animatorProperty;
}
private AnimatorProperty sixteenToFourteenAnimation(Image image){
float[] fromLoc = locations.get(15);
float[] toLoc = locations.get(13);
long duration = timePadding * 2;
AnimatorProperty animatorProperty = creatAnimationPropertyFromLocation(fromLoc, toLoc, 0, duration, image);
return animatorProperty;
}
private AnimatorProperty fourteenToTenAnimation(Image image){
float[] fromLoc = locations.get(13);
float[] toLoc = locations.get(9);
long duration = timePadding * 4;
AnimatorProperty animatorProperty = creatAnimationPropertyFromLocation(fromLoc, toLoc, 0, duration, image);
return animatorProperty;
}
private AnimatorProperty tenToSixAnimation(Image image){
float[] fromLoc = locations.get(9);
float[] toLoc = locations.get(5);
long duration = timePadding * 4;
AnimatorProperty animatorProperty = creatAnimationPropertyFromLocation(fromLoc, toLoc, 0, duration, image);
return animatorProperty;
}
private AnimatorProperty sixToFourAnimation(Image image){
float[] fromLoc = locations.get(5);
float[] toLoc = locations.get(3);
long duration = timePadding * 2;
AnimatorProperty animatorProperty = creatAnimationPropertyFromLocation(fromLoc, toLoc, 0, duration, image);
return animatorProperty;
}
private AnimatorProperty fourToTwoAnimation(Image image){
float[] fromLoc = locations.get(3);
float[] toLoc = locations.get(1);
long duration = timePadding * 2;
AnimatorProperty animatorProperty = creatAnimationPropertyFromLocation(fromLoc, toLoc, 0, duration, image);
return animatorProperty;
}
private void oneAnimate(float[] lastLoc, Image image, long aniDelay){
LogUtil.d("abcd","oneAnimate");
AnimatorProperty ani1 = creatAnimationPropertyFromLocation(image.getContentPosition(), lastLoc, aniDelay, firstAniDur, image);
ani1.start();
}
private void twoAnimate(float[] lastLoc, long lastDuration, Image image, long aniDelay){
AnimatorProperty ani1 = originLocationToOneAnimation(image, aniDelay);
float[] fromLoc = locations.get(15);
AnimatorProperty ani2 = creatAnimationPropertyFromLocation(fromLoc, lastLoc, 0, lastDuration, image);
AnimatorGroup animatorGroup = new AnimatorGroup();
animatorGroup.runSerially(ani1, ani2);
animatorGroup.start();
}
private void threeAnimate(float[] lastLoc, long lastDuration, Image image, long aniDelay){
AnimatorProperty ani1 = originLocationToOneAnimation(image, aniDelay);
AnimatorProperty ani2 = sixteenToFourteenAnimation(image);
float[] fromLoc = locations.get(13);
AnimatorProperty ani3 = creatAnimationPropertyFromLocation(fromLoc, lastLoc, 0, lastDuration, image);
AnimatorGroup animatorGroup = new AnimatorGroup();
animatorGroup.runSerially(ani1, ani2, ani3);
animatorGroup.start();
}
private void fourAnimate(float[] lastLoc, long lastDuration, Image image, long aniDelay){
AnimatorProperty ani1 = originLocationToOneAnimation(image, aniDelay);
AnimatorProperty ani2 = sixteenToFourteenAnimation(image);
AnimatorProperty ani3 = fourteenToTenAnimation(image);
float[] fromLoc = locations.get(9);
AnimatorProperty ani4 = creatAnimationPropertyFromLocation(fromLoc, lastLoc, 0, lastDuration, image);
AnimatorGroup animatorGroup = new AnimatorGroup();
animatorGroup.runSerially(ani1, ani2, ani3, ani4);
animatorGroup.start();
}
private void fiveAnimate(float[] lastLoc, long lastDuration, Image image, long aniDelay){
AnimatorProperty ani1 = originLocationToOneAnimation(image, aniDelay);
AnimatorProperty ani2 = sixteenToFourteenAnimation(image);
AnimatorProperty ani3 = fourteenToTenAnimation(image);
AnimatorProperty ani4 = tenToSixAnimation(image);
float[] fromLoc = locations.get(5);
AnimatorProperty ani5 = creatAnimationPropertyFromLocation(fromLoc, lastLoc, 0, lastDuration, image);
AnimatorGroup animatorGroup = new AnimatorGroup();
animatorGroup.runSerially(ani1, ani2, ani3, ani4, ani5);
animatorGroup.start();
}
private void sixAnimate(float[] lastLoc, long lastDuration, Image image, long aniDelay){
AnimatorProperty ani1 = originLocationToOneAnimation(image, aniDelay);
AnimatorProperty ani2 = sixteenToFourteenAnimation(image);
AnimatorProperty ani3 = fourteenToTenAnimation(image);
AnimatorProperty ani4 = tenToSixAnimation(image);
AnimatorProperty ani5 = sixToFourAnimation(image);
float[] fromLoc = locations.get(3);
AnimatorProperty ani6 = creatAnimationPropertyFromLocation(fromLoc, lastLoc, 0, lastDuration, image);
AnimatorGroup animatorGroup = new AnimatorGroup();
animatorGroup.runSerially(ani1, ani2, ani3, ani4, ani5, ani6);
animatorGroup.start();
}
private void sevenAnimate(Image image, long aniDelay){
AnimatorProperty ani1 = originLocationToOneAnimation(image, aniDelay);
AnimatorProperty ani2 = sixteenToFourteenAnimation(image);
AnimatorProperty ani3 = fourteenToTenAnimation(image);
AnimatorProperty ani4 = tenToSixAnimation(image);
AnimatorProperty ani5 = sixToFourAnimation(image);
AnimatorProperty ani6 = fourToTwoAnimation(image);
float[] fromLoc = locations.get(1);
float[] toLastLoc = locations.get(0);
long durationLast = timePadding;
AnimatorProperty ani7 = creatAnimationPropertyFromLocation(fromLoc, toLastLoc, 0, durationLast, image);
AnimatorGroup animatorGroup = new AnimatorGroup();
animatorGroup.setStateChangedListener(new Animator.StateChangedListener() {
@Override
public void onStart(Animator animator) {
}
@Override
public void onStop(Animator animator) {
}
@Override
public void onCancel(Animator animator) {
}
@Override
public void onEnd(Animator animator) {
biggerAnimation();
}
@Override
public void onPause(Animator animator) {
}
@Override
public void onResume(Animator animator) {
}
});
animatorGroup.runSerially(ani1, ani2, ani3, ani4, ani5, ani6, ani7);
animatorGroup.start();
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
其中lastLoc为最后一个动画的坐标,即终点坐标,lastDuration为最后一个动画所需要的时间,aniDelay为动画延迟开始的时间,firstAniDur为心心从初始位置移动到16号位置所需要的时间。
然后在页面初始化的时候,生成并用一个数组images保存16个Image对象,把心形图片赋值给Image对象,并延迟3秒给所有Image对象添加动画,具体添加代码为:
private long timePadding = 500;
private void getHeartAnimation(){
for (int i = 0; i < images.size(); i++){
long aniDelay = timePadding * i;
Image image = images.get(i);
float[] lastLoc = locations.get(i);
switch (i){
case 0:
sevenAnimate(image, aniDelay);
break;
case 1:
long lastDuration2 = timePadding * 2;
sixAnimate(lastLoc, lastDuration2, image, aniDelay);
break;
case 2:
long lastDuration3 = timePadding;
sixAnimate(lastLoc, lastDuration3, image, aniDelay);
break;
case 3:
long lastDuration4 = timePadding * 2;
fiveAnimate(lastLoc, lastDuration4, image ,aniDelay);
break;
case 4:
long lastDuration5 = timePadding;
fiveAnimate(lastLoc, lastDuration5, image ,aniDelay);
break;
case 5:
long lastDuration6 = timePadding * 4;
fourAnimate(lastLoc, lastDuration6, image, aniDelay);
break;
case 6:
long lastDuration7 = timePadding * 3;
fourAnimate(lastLoc, lastDuration7, image, aniDelay);
break;
case 7:
long lastDuration8 = timePadding * 2;
fourAnimate(lastLoc, lastDuration8, image, aniDelay);
break;
case 8:
long lastDuration9 = timePadding;
fourAnimate(lastLoc, lastDuration9, image, aniDelay);
break;
case 9:
float[] lastLoc10 = locations.get(9);
long lastDuration10 = timePadding * 4;
threeAnimate(lastLoc10, lastDuration10, image, aniDelay);
break;
case 10:
long lastDuration11 = timePadding * 3;
threeAnimate(lastLoc, lastDuration11, image, aniDelay);
break;
case 11:
long lastDuration12 = timePadding * 2;
threeAnimate(lastLoc, lastDuration12, image, aniDelay);
break;
case 12:
long lastDuration13 = timePadding;
threeAnimate(lastLoc, lastDuration13, image, aniDelay);
break;
case 13:
long lastDuration14 = timePadding * 2;
twoAnimate(lastLoc, lastDuration14, image, aniDelay);
break;
case 14:
long lastDuration15 = timePadding;
twoAnimate(lastLoc, lastDuration15, image, aniDelay);
break;
case 15:
oneAnimate(lastLoc, image, aniDelay);
break;
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
至此,我们的16颗小心心就可以从初始位置,移动到我们想要的位置,拼接成大心心了!
二、16颗小心心渐渐放大,这个时候包括小心心的位置移动和倍数的放大
这里我们把所有心心放大3倍,得到放大后的组合大心心。我们通过AnimatorProperty的scaleX和scaleY进行放大,通过这种方式放大后的组件,其宽高度是不变的,也就是依然是30,所以我们看到放大后的图片是这样的:
<img src=“./bigger.png” alt=“bigger” style=“zoom:50%;” />
实际上16颗小心心的大小和坐标是这样的:
![HarmonyOS 之 爱心动画-鸿蒙开发者社区 HarmonyOS 之 爱心动画-鸿蒙开发者社区](https://dl-harmonyos.51cto.com/images/202202/a838d3f38333dccea65734966dfce5df0037ea.png?x-oss-process=image/resize,w_666,h_1334)
所以我们就需要再次计算,放大3倍之后,16颗小心心的坐标,具体值为:
|
x |
y |
1号坐标 |
centerX-itemWH*3.5 |
centerY-itemWH*5 |
2号坐标(中间) |
centerX-itemWH*0.5 |
centerY-itemWH*2 |
3号坐标 |
centerX+itemWH*2.5 |
centerY-itemWH*5 |
4号坐标(右边顶部) |
centerX+itemWH*5.5 |
centerY-itemWH*8 |
5号坐标 |
centerX+itemWH*8.5 |
centerY-itemWH*5 |
6号坐标(右边) |
centerX+itemWH*11.5 |
centerY-itemWH*2 |
7号坐标 |
centerX+itemWH*8.5 |
centerY+itemWH |
8号坐标 |
centerX+itemWH*5.5 |
centerY+itemWH*4 |
9号坐标 |
centerX+itemWH*2.5 |
centerY+itemWH*7 |
10号坐标(底部) |
centerX-itemWH*0.5 |
centerY+itemWH*10 |
11号坐标 |
centerX-itemWH*3.5 |
centerY+itemWH*7 |
12号坐标 |
centerX-itemWH*6.5 |
centerY+itemWH*4 |
13号坐标 |
centerX-itemWH*9.5 |
centerY+itemWH |
14号坐标(左边) |
centerX-itemWH*12.5 |
centerY-itemWH*2 |
15号坐标 |
centerX-itemWH*9.5 |
centerY-itemWH*5 |
16号坐标(左边顶部) |
centerX-itemWH*6.5 |
centerY-itemWH*8 |
然后执行具体的代码:
三、16颗小心心放大之后,再像中心点移动,并且不断变小
由图可以看出,整颗大心心的中心点为:
等所有心心放大完成之后,我们给所有心心添加缩小的动画,具体代码为:
四、移动到中心点的后,将其中的一颗小心心放大,得到我们最后想要的样子
在所有小心心都缩小的同一个点之后,监听缩小动画完成,在完成之后的回调里,把16号小心心放大。
总结
AnimatorProperty:可以实现组件的平移、x轴y轴的放大缩小、旋转等动画效果;
AnimatorGroup:可以实现多个Animator动画对象的同时播放(runParallel)和顺序播放(runSerially)。
源码地址
https://gitee.com/YiRanRuMeng/harmony-os-love
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
下次情人节就用这个去表白!
感谢,学习了