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

2.功能需求
充电时候:开始从灵动到的中间圆形,慢慢出来一个圆角矩形,最终变成圆角矩形最大,同时展示文字 拔了充电:从最大的扩展效果,回到中间的圆点,并消失
- 
手机充电界面(核心场景): - 锁屏充电显示
- 音乐播放器的小组件动画的切换
 
- 
科技感UI元素: - 加载动画
- 进度指示器
- 状态切换过渡
 
3.实现思路
3.1 动画效果解析:
- 
矩形收缩成圆: - 圆角矩形同时收缩宽度和高度
- 圆角逐渐变成完美圆形
- 形成"电流聚集"的视觉效果
 
- 
圆形收缩消失: - 圆形继续收缩至消失
- 伴随透明度渐变为0
- 模拟"能量被吸收"的过程
 
- 
反向脉冲扩展: - 从消失状态扩展为圆形
- 透明度从0恢复为1
- 表现"能量释放"的反向过程
 
- 
恢复矩形并显示文本: - 圆形扩展回原始矩形
- 圆角恢复为初始值
- 完全展开后显示充电文本信息
- 形成完整的"充电动画循环"
 
3.2 具体的动画过程分析
 一共分为4步骤:
 一共分为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();
    }
}