最近换了个新手机,看了下灵动岛动画还是挺炫丽的! 网上找了下没有,于是自己写了个
这个动画效果是一个充电脉冲波动效果
做动画的核心: 就是录制视频,用剪映工具,放慢看效果,分成几个步骤进行实现!最后 一帧一帧
1.效果图

2.功能需求
充电时候:开始从灵动到的中间圆形,慢慢出来一个圆角矩形,最终变成圆角矩形最大,同时展示文字 拔了充电:从最大的扩展效果,回到中间的圆点,并消失
-
手机充电界面(核心场景):
- 锁屏充电显示
- 音乐播放器的小组件动画的切换
-
科技感UI元素:
- 加载动画
- 进度指示器
- 状态切换过渡
3.实现思路
3.1 动画效果解析:
-
矩形收缩成圆:
- 圆角矩形同时收缩宽度和高度
- 圆角逐渐变成完美圆形
- 形成"电流聚集"的视觉效果
-
圆形收缩消失:
- 圆形继续收缩至消失
- 伴随透明度渐变为0
- 模拟"能量被吸收"的过程
-
反向脉冲扩展:
- 从消失状态扩展为圆形
- 透明度从0恢复为1
- 表现"能量释放"的反向过程
-
恢复矩形并显示文本:
- 圆形扩展回原始矩形
- 圆角恢复为初始值
- 完全展开后显示充电文本信息
- 形成完整的"充电动画循环"
3.2 具体的动画过程分析
一共分为4步骤:
3.2.1 从无,把圆角矩形慢慢变大
ini
// 第一阶段:反向扩展动画
private void startExpandAnimation() {
Log.d("pengcaihua","1 startExpandAnimation ");
// 先重置为完全透明和小尺寸
chargingRect.setScaleX(0f);
chargingRect.setScaleY(0f);
chargingRect.setAlpha(0f);
chargingRect.setVisibility(View.VISIBLE);
// 扩展开始时保持文本隐藏
percentText.setVisibility(View.INVISIBLE);
chargingText.setVisibility(View.INVISIBLE);
AnimatorSet expandAnimator = new AnimatorSet();
// 1. 圆形扩展
ObjectAnimator expandX = ObjectAnimator.ofFloat(
chargingRect, "scaleX", 0f, 0.13f);
expandX.setDuration(time);
ObjectAnimator expandY = ObjectAnimator.ofFloat(
chargingRect, "scaleY", 0f, 0.5f);
expandY.setDuration(time);
// 2. 透明度恢复
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(
chargingRect, "alpha", 0f, 1f);
fadeIn.setDuration(time);
expandAnimator.playTogether(expandX, expandY, fadeIn);
expandAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
// 扩展完成后恢复原始形状
expandAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// startRestoreAnimation(); // 开始恢复原始形状
}
});
expandAnimator.start();
}
- 水平扩展 :
scaleX
从0f
→0.13f
(宽度扩展到原始的13%)。 - 垂直扩展 :
scaleY
从0f
→0.5f
(高度扩展到原始的50%)。 - 淡入效果 :
alpha
从0f
→1f
(从透明到不透明)
3.2.2 将视图放到最大,完全展开的状态并显示文本
scss
// 第er阶段:恢复原始形状
private void startRestoreAnimation() {
Log.d("pengcaihua","2 startRestoreAnimation ");
AnimatorSet restoreAnimator = new AnimatorSet();
// 1. 恢复宽度
ObjectAnimator restoreX = ObjectAnimator.ofFloat(
chargingRect, "scaleX", 0.13f, 1f);
restoreX.setDuration(time);
// 2. 恢复高度
ObjectAnimator restoreY = ObjectAnimator.ofFloat(
chargingRect, "scaleY", 0.5f, 1f);
restoreY.setDuration(time);
// 3. 恢复圆角
float finalRadius = Math.min(chargingRect.getWidth(), chargingRect.getHeight()) / 2f;
ObjectAnimator cornerRestore = ObjectAnimator.ofFloat(
chargingRect, "cornerRadius", finalRadius, 40f);
cornerRestore.setDuration(time);
restoreAnimator.playTogether(restoreX, restoreY, cornerRestore);
restoreAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
restoreAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// 矩形完全恢复后显示文本
percentText.setVisibility(View.VISIBLE);
chargingText.setVisibility(View.VISIBLE);
// 使用Handler延迟替代Thread.sleep,避免阻塞UI线程
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
// startShrinkAnimation();
}
}, 3000);
}
});
restoreAnimator.start();
}
- 宽度恢复 :
scaleX
从0.13f
→1f
(宽度恢复到100%) - 高度恢复 :
scaleY
从0.5f
→1f
(高度恢复到100%) - 圆角变化 :
cornerRadius
从圆形半径
→40f
(从圆形变为固定40px圆角)
3.2.3 和第二步反正来,缩小,文字也消失
scss
private void startShrinkAnimation() {
Log.d("pengcaihua","3 startShrinkAnimation ");
// 收缩开始时隐藏文本
percentText.setVisibility(View.INVISIBLE);
chargingText.setVisibility(View.INVISIBLE);
AnimatorSet animatorSet = new AnimatorSet();
// 1. 宽度收缩动画 - 从两边向中间收缩
ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(
chargingRect, "scaleX", 1f, 0.13f);
scaleXAnim.setDuration(time);
// 2. 高度收缩动画 - 从上下向中间收缩
ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(
chargingRect, "scaleY", 1f, 0.5f);
scaleYAnim.setDuration(time);
// 3. 圆角动画 - 变成完美圆形
float finalRadius = Math.min(chargingRect.getWidth(), chargingRect.getHeight()) / 2f;
Log.d(TAG, "finalRadius" + finalRadius);
ObjectAnimator cornerAnim = ObjectAnimator.ofFloat(
chargingRect, "cornerRadius", 40f, finalRadius);
cornerAnim.setDuration(time);
// 第一阶段动画:同时播放所有属性动画
animatorSet.playTogether(scaleXAnim, scaleYAnim, cornerAnim);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
// 添加动画监听器,在第一阶段结束后执行第二阶段动画
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// 第二阶段动画:圆形收缩
startPerfectCircleShrinkAnimation();
}
});
// 启动第一阶段动画
animatorSet.start();
}
3.2.4 和第一步反正来,矩形圆角缩小成一个圆,然后消失不见
ini
private void startPerfectCircleShrinkAnimation() {
Log.d("pengcaihua","4 startPerfectCircleShrinkAnimation ");
AnimatorSet shrinkAnimator = new AnimatorSet();
// 1. 圆形继续收缩
ObjectAnimator shrinkX = ObjectAnimator.ofFloat(
chargingRect, "scaleX", 0.13f, 0f);
shrinkX.setDuration(time);
ObjectAnimator shrinkY = ObjectAnimator.ofFloat(
chargingRect, "scaleY", 0.5f, 0f);
shrinkY.setDuration(time);
// 2. 透明度动画
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(
chargingRect, "alpha", 1f, 0f);
fadeOut.setDuration(time);
shrinkAnimator.playTogether(shrinkX, shrinkY, fadeOut);
shrinkAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
// 收缩完成后开始反向动画
shrinkAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
startExpandAnimation(); // 开始反向扩展动画
}
});
shrinkAnimator.start();
}
4.架构图

5.总结
5.1 首次执行顺序:
startExpandAnimation()
→ startRestoreAnimation()
→ (延迟) → startShrinkAnimation()
→ startPerfectCircleShrinkAnimation()
→ 回到 startExpandAnimation()
5.2 各阶段作用总结:
方法 | 阶段 | 视觉效果 | 文本状态 |
---|---|---|---|
startExpandAnimation() |
三 | 消失→小圆形 | 隐藏 |
startRestoreAnimation() |
四 | 小圆形→完整矩形 | 显示 |
startShrinkAnimation() |
一 | 矩形→圆形 | 隐藏 |
startPerfectCircleShrinkAnimation() |
二 | 圆形→消失 | 隐藏 |
整个动画就像一次"心跳": | |||
出现(扩张)→ 饱满状态(显示信息)→ 收缩(隐藏)→ 消失 → |
这个架构实现了自包含的动画循环系统,将视图状态管理与动画执行逻辑分离,同时保持各阶段的高内聚性,适合作为灵动岛风格动画的核心引擎。

5.3 扩展:基于这个动画,你能实现心跳的动画么?
ini
public class HeartbeatAnimationActivity extends Activity {
private static final String TAG = "HeartbeatAnimation";
private View heart;
private TextView heartbeatText;
private TextView statusText;
private Button startButton;
private boolean isAnimating = false;
// 动画时长配置(毫秒)
private final long CONTRACTION_TIME = 200; // 收缩时间
private final long EXPANSION_TIME = 300; // 扩张时间
private final long CONTRACTION_PAUSE = 100; // 收缩后暂停
private final long EXPANSION_PAUSE = 800; // 扩张后暂停
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_heartbeat);
// 初始化视图
heart = findViewById(R.id.heart);
heartbeatText = findViewById(R.id.heartbeat_text);
statusText = findViewById(R.id.status_text);
startButton = findViewById(R.id.start_button);
// 设置心形视图的圆角
setupHeartShape();
startButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isAnimating) {
startHeartbeatAnimation();
startButton.setText("停止心跳");
} else {
stopHeartbeatAnimation();
startButton.setText("开始心跳");
}
isAnimating = !isAnimating;
}
});
}
private void setupHeartShape() {
GradientDrawable shape = new GradientDrawable();
shape.setShape(GradientDrawable.OVAL);
shape.setColor(0xffff6b6b); // 心形颜色
shape.setCornerRadius(100); // 圆角半径
heart.setBackground(shape);
}
private void startHeartbeatAnimation() {
heartbeatText.setVisibility(View.VISIBLE);
statusText.setText("状态: 心跳中...");
startContractionAnimation();
}
private void stopHeartbeatAnimation() {
// 重置动画状态
heart.animate().cancel();
heartbeatText.animate().cancel();
// 重置视图状态
heart.setScaleX(1f);
heart.setScaleY(1f);
heartbeatText.setScaleX(1f);
heartbeatText.setScaleY(1f);
heartbeatText.setAlpha(1f);
heartbeatText.setVisibility(View.INVISIBLE);
statusText.setText("状态: 已停止");
}
// 第一阶段:收缩动画
private void startContractionAnimation() {
Log.d(TAG, "开始收缩动画");
statusText.setText("状态: 收缩中");
AnimatorSet animatorSet = new AnimatorSet();
// 心形收缩
ObjectAnimator heartScaleX = ObjectAnimator.ofFloat(heart, "scaleX", 1f, 0.8f);
heartScaleX.setDuration(CONTRACTION_TIME);
ObjectAnimator heartScaleY = ObjectAnimator.ofFloat(heart, "scaleY", 1f, 0.8f);
heartScaleY.setDuration(CONTRACTION_TIME);
// 心跳文本收缩
ObjectAnimator textScaleX = ObjectAnimator.ofFloat(heartbeatText, "scaleX", 1f, 0.8f);
textScaleX.setDuration(CONTRACTION_TIME);
ObjectAnimator textScaleY = ObjectAnimator.ofFloat(heartbeatText, "scaleY", 1f, 0.8f);
textScaleY.setDuration(CONTRACTION_TIME);
// 心跳文本变暗
ObjectAnimator textAlpha = ObjectAnimator.ofFloat(heartbeatText, "alpha", 1f, 0.7f);
textAlpha.setDuration(CONTRACTION_TIME);
animatorSet.playTogether(heartScaleX, heartScaleY, textScaleX, textScaleY, textAlpha);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// 收缩完成后暂停片刻
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
startExpansionAnimation();
}
}, CONTRACTION_PAUSE);
}
});
animatorSet.start();
}
// 第二阶段:扩张动画
private void startExpansionAnimation() {
Log.d(TAG, "开始扩张动画");
statusText.setText("状态: 扩张中");
AnimatorSet animatorSet = new AnimatorSet();
// 心形扩张
ObjectAnimator heartScaleX = ObjectAnimator.ofFloat(heart, "scaleX", 0.8f, 1.1f, 1f);
heartScaleX.setDuration(EXPANSION_TIME);
ObjectAnimator heartScaleY = ObjectAnimator.ofFloat(heart, "scaleY", 0.8f, 1.1f, 1f);
heartScaleY.setDuration(EXPANSION_TIME);
// 心跳文本扩张
ObjectAnimator textScaleX = ObjectAnimator.ofFloat(heartbeatText, "scaleX", 0.8f, 1.1f, 1f);
textScaleX.setDuration(EXPANSION_TIME);
ObjectAnimator textScaleY = ObjectAnimator.ofFloat(heartbeatText, "scaleY", 0.8f, 1.1f, 1f);
textScaleY.setDuration(EXPANSION_TIME);
// 心跳文本变亮
ObjectAnimator textAlpha = ObjectAnimator.ofFloat(heartbeatText, "alpha", 0.7f, 1f);
textAlpha.setDuration(EXPANSION_TIME);
animatorSet.playTogether(heartScaleX, heartScaleY, textScaleX, textScaleY, textAlpha);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// 扩张完成后暂停片刻
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
if (isAnimating) {
startContractionAnimation();
}
}
}, EXPANSION_PAUSE);
}
});
animatorSet.start();
}
}