Android的动画详解

ModelEngine·创作计划征文活动 10w+人浏览 878人参与

在Android开发中,动画是提升用户体验的重要部分。下面我为你梳理一下Android的动画类型、它们的优缺点、使用场景,并解答你关于点击事件和属性限制的具体问题。

三种核心动画类型

动画类型

核心机制

优点

缺点

典型使用场景

补间动画

定义开始和结束状态,系统计算中间帧。

实现简单,无需逐帧定义,动画效果流畅。

只改变视图绘制位置,不改变视图实际属性(如位置、大小),导致点击事件仍在原处触发。

视图的淡入淡出、移动、旋转、缩放等效果。

属性动画

通过持续改变目标对象的属性值来实现动画。

真正改变视图属性,交互逻辑正确;可对任意对象的属性进行动画。

API 11及以上原生支持,兼容旧版本需额外处理。

复杂交互动画,如改变背景色、自定义属性的动画。

帧动画

顺序播放一系列静态图片。

制作简单,控制精准。

可能占用大量内存;动画效果不灵活。

类似Loading的动画、复杂特效。

三种动画的使用示例

帧动画

帧动画像翻页书,通过快速切换图片实现动画效果。

定义XML动画文件

res/drawable/目录下创建XML文件,例如 anim_frame.xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false"> <!-- false表示循环播放 -->
    <item
        android:drawable="@drawable/frame1"
        android:duration="100" />
    <item
        android:drawable="@drawable/frame2"
        android:duration="100" />
    <!-- ... 更多帧 -->
</animation-list>

在代码中启动动画

ImageView imageView = findViewById(R.id.imageView);
imageView.setBackgroundResource(R.drawable.anim_frame);
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getBackground();

// 注意:不建议在onCreate()中直接调用start(),因为窗口未完全初始化
// 推荐在onWindowFocusChanged()或用户交互后启动
@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus) {
        animationDrawable.start();
    }
}

关键点:为避免动画不播放,启动时机很重要,最好在 onWindowFocusChanged或用户交互后开始。

补间动画

补间动画通过定义开始和结束状态,由系统自动生成中间帧的动画。它提供了四种基本变换。

1. 定义动画XML文件

res/anim/目录下创建文件,例如 anim_set.xml,可以组合多种效果:

<!-- 淡入同时缩放的组合动画 -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false"
    android:fillAfter="true"> <!-- 动画结束后保持状态 -->

    <alpha
        android:fromAlpha="0.0"
        android:toAlpha="1.0"
        android:duration="1000" />

    <scale
        android:fromXScale="0.5"
        android:toXScale="1.0"
        android:fromYScale="0.5"
        android:toYScale="1.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:duration="1000" />
</set>

2. 在代码中应用动画

ImageView imageView = findViewById(R.id.imageView);
// 加载XML动画
Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_set);
imageView.startAnimation(animation);

// 或者,也可以完全用代码创建动画
AnimationSet set = new AnimationSet(true);
AlphaAnimation alphaAnim = new AlphaAnimation(0.0f, 1.0f);
alphaAnim.setDuration(1000);
set.addAnimation(alphaAnim);

ScaleAnimation scaleAnim = new ScaleAnimation(0.5f, 1.0f, 0.5f, 1.0f, 
                                             Animation.RELATIVE_TO_SELF, 0.5f, 
                                             Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnim.setDuration(1000);
set.addAnimation(scaleAnim);

imageView.startAnimation(set);

核心局限:补间动画只是视觉上的变化,视图的实际位置和大小等属性并未改变。例如,你将一个按钮从顶部平移到底部,点击按钮底部不会响应,但点击其原始位置却会。

属性动画

属性动画通过动态地改变对象的属性值来实现动画,是功能最强大的动画系统。

1. 值动画:手动管理数值变化

// 创建一个在0到100之间变化的数值动画
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 100);
valueAnimator.setDuration(1000);

valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        int currentValue = (int) animation.getAnimatedValue();
        // 手动将当前值应用到目标对象,例如更新View的布局参数
        ViewGroup.LayoutParams params = someView.getLayoutParams();
        params.height = currentValue;
        someView.setLayoutParams(params);
    }
});
valueAnimator.start();

2. 对象动画:自动更新属性

// 自动改变View的透明度(系统会自动寻找并调用setAlpha方法)
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(imageView, "alpha", 0f, 1f);
alphaAnimator.setDuration(1000);
alphaAnimator.start();

// 平移动画(实际改变的是translationX属性)
ObjectAnimator translateAnimator = ObjectAnimator.ofFloat(imageView, "translationX", 0f, 200f);
translateAnimator.setDuration(1000);
translateAnimator.start();

3. 组合动画:协调多个动画

使用 AnimatorSet可以精确控制多个动画的播放顺序:

ObjectAnimator animX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.5f);
ObjectAnimator animY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.5f);

AnimatorSet set = new AnimatorSet();
set.playTogether(animX, animY); // 同时执行缩放X和Y
// 或者定义顺序:set.play(anim1).before(anim2).after(anim3);
set.setDuration(500);
set.start();

4. 使用XML定义属性动画

res/animator/目录下创建文件,例如 animator_set.xml

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">
    <objectAnimator
        android:propertyName="translationX"
        android:duration="1000"
        android:valueFrom="0"
        android:valueTo="200"
        android:valueType="floatType" />
</set>

加载并应用XML属性动画:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.animator_set);
set.setTarget(imageView); // 设置动画作用的目标视图
set.start();

深入原理与难点解答

为什么补间动画的点击事件还在原位置?

补间动画(Tween Animation)的点击事件仍在视图原始位置触发,这源于其视觉与交互的分离机制

  1. 视觉变化原理:补间动画通过父视图在绘制子视图时应用一个不断变化的 Transformation(其中包含一个Matrix)来实现。这相当于给视图的“外表”加了一个特效,视图被画在了不同的位置,但它的实际属性(如left, top, right, bottom)从未改变

  2. 点击检测逻辑:Android系统检测点击事件时,依据的是视图的实际占位区域(即其原始布局位置),而非其绘制在屏幕上的视觉效果。因此,即使视图看起来移动了,系统仍认为它在老地方。

解决方案:若必须在补间动画后使点击事件跟随视图,可在父视图的 onTouchEvent中拦截点击事件,使用动画的变换矩阵进行逆向计算,判断点击是否落在视图的当前显示区域内。但这种方法复杂,更推荐使用属性动画从根本上解决问题。

为什么属性动画能改变属性?哪些属性不可改变?

属性动画(Property Animation)能够改变属性,是因为其设计核心是持续修改目标对象的属性值

  1. 工作机制:以 ObjectAnimator为例,你需要告诉它要操作哪个对象的哪个属性(如 ViewtranslationX)。动画过程中,系统会根据已播放的时间和设定的插值器,计算出一个当前值,然后自动调用该属性对应的 setter 方法(如 setTranslationX(float))来更新属性值。由于视图的属性被真实改变,其绘制位置和交互区域自然会更新。

  2. 不可动画的属性:属性动画功能强大,但并非万能。以下几种情况的属性通常无法直接用于属性动画:

    • 没有对应的 Setter 方法:属性动画依赖于 setter 方法来更新属性。如果一个属性是 final的(不可变)或只有 getter 方法而没有 setter 方法,则无法直接通过属性动画进行修改。

    • 属性变化无法触发界面更新:即使有 setter 方法,但如果设置新值后,无法以某种方式通知系统重绘视图(例如调用 invalidate()方法),那么动画在屏幕上也看不到任何效果。

    • 线程不安全的属性:如果某个属性的 setter 方法非线程安全,而属性动画在后台线程更新其值,则可能导致并发问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

峰哥的Android进阶之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值