简介
在Android开发中,动画是提升用户体验的重要工具。Android提供了多种动画框架,适用于不同场景和需求,接下来,本章节将会详细的讲解。
1.补间动画(Tween Animation)
-
补间动画的核心概念
补间动画通过 XML或代码 定义动画的其实和结束状态,系统 自动计算 中间过渡帧。
其支持的四种类型:平移(Translate)、旋转(Rotate)、缩放(Scale)、透明度(Alpha)。
需要注意的是,补间动画仅改变 View 的绘制效果,不改变实际属性(如点击事件位置不变)。
-
属性详解
-
通用属性(所有动画类型共有,之后不在重复赘述)
android:duration:动画持续时间(单位:毫秒)。android:startOffset:延迟开始时间(单位:毫秒)。android:fillAfter:动画结束后是否保存最终状态(true/false)。android:fillBefore:动画结束后是否回到初始状态(默认true)。android:interpolator:插值器(控制动画速度变化,如加速、减速)。
-
各类型动画持有属性
-
平移(translate)
android:fromXDelta="0%" // 从自身宽度的 0% 位置开始(原始位置) android:toXDelta="100%" // 移动到自身宽度的 100% 位置(向右移动一个自己宽度) android:fromYDelta="0%" // 从自身高度的 0% 开始(原始位置) android:toYDelta="50%" // 移动到自身高度的 50% 位置(向下移动自己高度的一半) -
旋转(Rotate)
android:fromDegrees="0" // 起始角度 android:toDegrees="360" // 结束角度 android:pivotX="50%" // 旋转中心X(相对于View自身) android:pivotY="50%" // 旋转中心Y -
缩放(Scale)
android:fromXScale="1.0" // 起始 X 轴缩放比例为 1.0(即正常宽度) android:toXScale="2.0" // 最终 X 轴缩放比例为 2.0(宽度变为原来的 2 倍) android:fromYScale="1.0" // 起始 Y 轴缩放比例为 1.0(即正常高度) android:toYScale="0.5" // 最终 Y 轴缩放比例为 0.5(高度变为原来的一半) android:pivotX="50%" // 缩放中心点 X,50% 表示 View 自身宽度的中心 android:pivotY="50%" // 缩放中心点 Y,50% 表示 View 自身高度的中心 -
透明度(Alpha)
android:fromAlpha="1.0" // 起始透明度:1.0,表示完全不透明 android:toAlpha="0.0" // 最终透明度:0.0,表示完全透明
-
-
-
示例
-
XML 定义动画
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:duration="2000" android:fillAfter="true" android:interpolator="@android:anim/accelerate_decelerate_interpolator"> <!-- 平移 --> <translate android:fromXDelta="0%" android:toXDelta="50%" android:fromYDelta="0%" android:toYDelta="0%" /> <!-- 旋转 --> <rotate android:fromDegrees="0" android:toDegrees="360" android:pivotX="50%" android:pivotY="50%" /> <!-- 缩放 --> <scale android:fromXScale="1.0" android:toXScale="1.5" android:fromYScale="1.0" android:toYScale="1.5" android:pivotX="50%" android:pivotY="50%" /> <!-- 透明度 --> <alpha android:fromAlpha="1.0" android:toAlpha="0.5" /> </set> -
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <Button android:id="@+id/btn_tween_animate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开启补间动画" /> <ImageView android:id="@+id/iv_target" android:layout_width="100dp" android:layout_height="100dp" android:src="@drawable/ic_launcher_foreground" /> </LinearLayout> -
MainActivityclass MainActivity : AppCompatActivity() { // 懒加载按钮视图:用于触发补间动画 private val btnTweenAnimate by lazy { findViewById<Button>(R.id.btn_tween_animate) } // 懒加载图片视图:动画作用对象 private val ivTarget by lazy { findViewById<ImageView>(R.id.iv_target) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 加载动画资源(位于 res/anim/anim_combined.xml) val animation = AnimationUtils.loadAnimation(this, R.anim.anim_combined) btnTweenAnimate.setOnClickListener { // 给 ivTarget 启动动画(组合动画:位移、旋转、缩放、透明度) ivTarget.startAnimation(animation) } } }
-
2. 属性动画(Propertry Animation)
-
属性动画的核心概念
属性动画通过动态修改对象的 属性值 (如坐标、透明度) 等实现动画,适用于任何独享(不限于View)。
核心类:
ValueAnimator:计算动画过程中的属性值,需要手动更新目标属性。ObjectAnimator:直接操作目标对象的属性(自动更新)。AnimatorSet:组合多个动画(顺序或并行执行)。
-
示例
-
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <Button android:id="@+id/btn_property_animate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开启属性动画" /> <TextView android:id="@+id/tv_target" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello Anim!" android:textSize="24sp" android:background="#FF2196F3" android:padding="16dp" /> </LinearLayout> -
MainActivityclass MainActivity : AppCompatActivity() { // 懒加载按钮视图:用于触发补间动画 private val btnPropertyAnimate by lazy { findViewById<Button>(R.id.btn_property_animate) } // 懒加载图片视图:动画作用对象 private val tvTarget by lazy { findViewById<TextView>(R.id.tv_target) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btnPropertyAnimate.setOnClickListener { startPropertyAnimation() } } private fun startPropertyAnimation() { // 水平移动动画:将 tvTarget 从 X=0 平移到 X=1000px val moveX = ObjectAnimator.ofFloat(tvTarget, "translationX", 0f, 1000f).apply { duration = 3000L // 动画持续时长:3 秒 repeatCount = 1 // 重复次数 = 1,表示除首次播放外再重复一次(总共播放两次) repeatMode = ValueAnimator.REVERSE // 重复时使用反向模式:第二次会从 1000px 平移回 0px } // 透明度动画:将 tvTarget 的 alpha 从 1.0f 动画到 0.1f val alpha = ObjectAnimator.ofFloat(tvTarget, "alpha", 1f, 0.1f).apply { duration = 3000L // 持续 3 秒 repeatCount = 1 // 重复一次 → 共两次播放 repeatMode = ValueAnimator.REVERSE // 第二次反向播放:从 0.1f 动画回 1.0f } // 背景色动画:使用 ArgbEvaluator 在蓝色与红色之间进行过渡 val colorAnima = ObjectAnimator.ofObject( tvTarget, "backgroundColor", ArgbEvaluator(), // 颜色插值器 Color.BLUE, // 起始颜色 Color.RED // 结束颜色 ).apply { duration = 3000L // 持续 3 秒 repeatCount = 1 // 重复一次 → 共两次播放 repeatMode = ValueAnimator.REVERSE // 第二次反向播放:从红色过渡回蓝色 } // 组合动画:并行执行上述三个动画 AnimatorSet().apply { playTogether(moveX, alpha, colorAnima) // 同时开始三个动画 start() // 启动动画 } } }
-
3. 帧动画(Drawable Animation)
-
帧动画的核心概念
帧动画通过 逐帧播放图片序列(类似 GIF)实现动画效果。 适用场景:步骤引导、简单动作效果(如加载动画、游戏角色动作)。 特点:
- 实现简单,但图片资源较多时可能占用较高内存。
- 适合图片数量少且尺寸小的动画。
-
示例
-
在
res/drawble/anim_loading.xml中定义动画:<?xml version="1.0" encoding="utf-8"?> <!-- res/drawable/anim_loading.xml --> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@drawable/frame1" android:duration="100" /> <item android:drawable="@drawable/frame2" android:duration="100" /> <item android:drawable="@drawable/frame3" android:duration="100" /> <item android:drawable="@drawable/frame4" android:duration="100" /> </animation-list> -
在
res/drawble/frame1.xml中定义帧动画资源:<?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="100dp" android:height="200dp" android:viewportWidth="100" android:viewportHeight="200"> <!-- 头部:圆形 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,20 A20,20 0 1,1 49.99,20Z" /> <!-- 身体:竖线 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,40 L50,100" /> <!-- 左臂:向左上45度 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,60 L20,10" /> <!-- 右臂:向右上45度 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,60 L80,80" /> <!-- 左腿:向左下 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,100 L30,150" /> <!-- 右腿:向右下 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,100 L70,150" /> </vector> -
在
res/drawble/frame2.xml中定义帧动画资源:<?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="100dp" android:height="200dp" android:viewportWidth="100" android:viewportHeight="200"> <!-- 头部:圆形 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,20 A20,20 0 1,1 49.99,20Z" /> <!-- 身体:竖线 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,40 L50,100" /> <!-- 左臂:向左上45度 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,60 L20,20" /> <!-- 右臂:向右上45度 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,60 L80,80" /> <!-- 左腿:向左下 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,100 L30,150" /> <!-- 右腿:向右下 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,100 L70,150" /> </vector> -
在
res/drawble/frame3.xml中定义帧动画资源:<?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="100dp" android:height="200dp" android:viewportWidth="100" android:viewportHeight="200"> <!-- 头部:圆形 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,20 A20,20 0 1,1 49.99,20Z" /> <!-- 身体:竖线 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,40 L50,100" /> <!-- 左臂:向左上45度 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,60 L20,30" /> <!-- 右臂:向右上45度 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,60 L80,80" /> <!-- 左腿:向左下 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,100 L30,150" /> <!-- 右腿:向右下 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,100 L70,150" /> </vector> -
在
res/drawble/frame4.xml中定义帧动画资源:<?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="100dp" android:height="200dp" android:viewportWidth="100" android:viewportHeight="200"> <!-- 头部:圆形 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,20 A20,20 0 1,1 49.99,20Z" /> <!-- 身体:竖线 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,40 L50,100" /> <!-- 左臂:向左上45度 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,60 L20,40" /> <!-- 右臂:向右上45度 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,60 L80,80" /> <!-- 左腿:向左下 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,100 L30,150" /> <!-- 右腿:向右下 --> <path android:strokeColor="#000000" android:strokeWidth="4" android:pathData="M50,100 L70,150" /> </vector> -
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <ImageView android:id="@+id/iv_loading" android:layout_width="100dp" android:layout_height="100dp" android:src="@drawable/anim_loading" /> <!-- 直接引用动画XML --> <Button android:id="@+id/btn_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开始动画" /> <Button android:id="@+id/btn_stop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="停止动画" /> </LinearLayout> -
MainActivityimport android.graphics.drawable.AnimationDrawable import android.os.Bundle import android.widget.Button import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity // MainActivity.kt class MainActivity : AppCompatActivity() { private lateinit var animationDrawable: AnimationDrawable override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val ivLoading = findViewById<ImageView>(R.id.iv_loading) val btnStart = findViewById<Button>(R.id.btn_start) val btnStop = findViewById<Button>(R.id.btn_stop) // 获取 AnimationDrawable 对象 animationDrawable = ivLoading.drawable as AnimationDrawable btnStart.setOnClickListener { if (!animationDrawable.isRunning) { animationDrawable.start() // 启动动画 } } btnStop.setOnClickListener { if (animationDrawable.isRunning) { animationDrawable.stop() // 停止动画 } } } // 建议在界面可见时启动动画(避免 onCreat 中直接调用) override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (hasFocus) { animationDrawable.start() } else { animationDrawable.stop() } } }
-
4. 过渡动画(Transition Framework)
-
过渡动画的核心概念
过渡动画用于 布局变化 (如添加/移除 View、修改属性) 或 界面切换 (Acitivity/Fragment) 时实现平滑的视觉效果。
适用场景
- 布局动态更新(如展开/折叠菜单)
- Activity/Fragment 切换动画
- 共享元素过度(如点击图片后放大到详情页)
核心类
TransitionManager:管理过渡动画的触发和执行。Transition:定义动画类型(如平移、淡入淡出)。Scene:描述布局的起始和结束状态。
-
过渡动画类型
-
内置过渡效果
过度类 说明 Fade淡入淡出效果 Slide滑动进入/退出 Explode爆炸效果(类似碎片飞散) ChangeBounds处理 View 位置和尺寸变化 ChangeTransform处理 View 的旋转和缩放 ChangeClipBounds处理裁剪区域变化 AutoTransition默认组合过渡(Fade + ChangeBounds) -
自定义过渡
可继承
Transition类,实现自定义动画逻辑。
-
-
核心方法和属性
-
TransitionManager方法 说明 beginDelayedTransition(ViewGroup)标记后续布局变化需要应用过渡动画。 go(Scene, Transition)通过 Scene切换布局并应用过渡动画。setTransition(Scene, Scene, Transition)定义两个 Scene之间的过渡动画。 -
Transition方法/属性 说明 setDuration(long)设置动画时长(毫秒)。 setInterpolator(Interpolator)设置插值器(控制动画速度曲线)。 addTarget(View)指定应用动画的目标 View。 excludeTarget(View, boolean)排除特定 View 的动画效果。
-
-
示例
-
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/root_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <!-- 控制按钮 --> <Button android:id="@+id/btn_fade" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Fade 过渡" /> <Button android:id="@+id/btn_slide" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Slide 过渡" /> <Button android:id="@+id/btn_explode" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Explode 过渡" /> <Button android:id="@+id/btn_change_bounds" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="ChangeBounds 过渡" /> <Button android:id="@+id/btn_change_transform" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="ChangeTransform 过渡" /> <Button android:id="@+id/btn_change_clip_bounds" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="ChangeClipBounds 过渡" /> <Button android:id="@+id/btn_auto_transition" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="AutoTransition 过渡" /> <Button android:id="@+id/btn_custom_transition" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="自定义 ColorTransition" /> <!-- 动画目标 View --> <View android:id="@+id/target_view" android:layout_width="100dp" android:layout_height="100dp" android:background="#FF5722" android:layout_marginTop="16dp" /> </LinearLayout> -
MainActivityimport android.animation.Animator import android.animation.ValueAnimator import android.graphics.Color import android.graphics.Rect import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.Gravity import android.view.View import android.view.ViewGroup import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateInterpolator import android.view.animation.BounceInterpolator import android.view.animation.DecelerateInterpolator import android.view.animation.LinearInterpolator import android.view.animation.OvershootInterpolator import android.widget.Button import android.widget.LinearLayout import androidx.appcompat.app.AppCompatActivity import androidx.transition.AutoTransition import androidx.transition.ChangeBounds import androidx.transition.ChangeClipBounds import androidx.transition.ChangeTransform import androidx.transition.Explode import androidx.transition.Fade import androidx.transition.Slide import androidx.transition.Transition import androidx.transition.TransitionManager import androidx.transition.TransitionValues // MainActivity.kt class MainActivity : AppCompatActivity() { private lateinit var targetView: View private var isOriginalState = true override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) targetView = findViewById(R.id.target_view) // 绑定按钮点击事件(每个过渡单独配置) findViewById<Button>(R.id.btn_fade).setOnClickListener { applyTransition(Fade().apply { duration = 1000 // 1秒淡入淡出 interpolator = LinearInterpolator() }) } findViewById<Button>(R.id.btn_slide).setOnClickListener { applyTransition(Slide().apply { duration = 800 // 0.8秒滑动 interpolator = AccelerateInterpolator() slideEdge = Gravity.START // 从左侧滑入 }) } findViewById<Button>(R.id.btn_explode).setOnClickListener { applyTransition(Explode().apply { duration = 1200 // 1.2秒爆炸效果 interpolator = OvershootInterpolator() }) } findViewById<Button>(R.id.btn_change_bounds).setOnClickListener { applyTransition(ChangeBounds().apply { duration = 1500 // 1.5秒尺寸/位置变化 interpolator = BounceInterpolator() }) } findViewById<Button>(R.id.btn_change_transform).setOnClickListener { applyTransition(ChangeTransform().apply { duration = 1000 // 1秒旋转缩放 interpolator = DecelerateInterpolator() }) } findViewById<Button>(R.id.btn_change_clip_bounds).setOnClickListener { applyTransition(ChangeClipBounds().apply { duration = 1000 // 1秒裁剪区域变化 }) } findViewById<Button>(R.id.btn_auto_transition).setOnClickListener { applyTransition(AutoTransition().apply { duration = 1000 // 1秒默认组合动画 }) } findViewById<Button>(R.id.btn_custom_transition).setOnClickListener { applyTransition(ColorTransition().apply { duration = 2000 // 2秒颜色渐变 }) } } private fun applyTransition(transition: Transition) { // 确保过渡动画生效 TransitionManager.beginDelayedTransition(findViewById(R.id.root_layout), transition) toggleViewState() } private fun toggleViewState() { if (isOriginalState) { // 大范围修改属性以确保动画可见 targetView.layoutParams = LinearLayout.LayoutParams(300.dpToPx(), 300.dpToPx()).apply { gravity = Gravity.END } targetView.rotation = 180f // 旋转180度 targetView.scaleX = 2.0f // X轴放大2倍 targetView.scaleY = 2.0f // Y轴放大2倍 targetView.clipBounds = Rect(0, 0, 150.dpToPx(), 150.dpToPx()) // 裁剪至中心区域 targetView.setBackgroundColor(Color.BLUE) // 修改颜色(用于自定义过渡) } else { // 恢复初始状态 targetView.layoutParams = LinearLayout.LayoutParams(100.dpToPx(), 100.dpToPx()) targetView.rotation = 0f targetView.scaleX = 1f targetView.scaleY = 1f targetView.clipBounds = null targetView.setBackgroundColor(Color.parseColor("#FF5722")) // 恢复初始颜色 } isOriginalState = !isOriginalState } private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).toInt() } // 自定义过渡:颜色渐变(修复版) class ColorTransition : Transition() { override fun captureStartValues(transitionValues: TransitionValues) { captureColor(transitionValues) } override fun captureEndValues(transitionValues: TransitionValues) { captureColor(transitionValues) } private fun captureColor(transitionValues: TransitionValues) { val view = transitionValues.view if (view.background is ColorDrawable) { transitionValues.values[PROPNAME_COLOR] = (view.background as ColorDrawable).color } } override fun createAnimator( sceneRoot: ViewGroup, startValues: TransitionValues?, endValues: TransitionValues? ): Animator? { if (startValues == null || endValues == null) return null val startColor = startValues.values[PROPNAME_COLOR] as Int val endColor = endValues.values[PROPNAME_COLOR] as Int val view = startValues.view return ValueAnimator.ofArgb(startColor, endColor).apply { addUpdateListener { animator -> view.background = ColorDrawable(animator.animatedValue as Int) } duration = 2000 // 显式设置动画时长 interpolator = AccelerateDecelerateInterpolator() } } companion object { private const val PROPNAME_COLOR = "ColorTransition:color" } }
-
5. 矢量动画(AnimatedVectorDrawable)
-
矢量动画核心概念
矢量动画基于
VectorDrawable和AnimatedVectorDrawable实现,特点:- 使用矢量图形(SVG 路径),放大不失真
- 通过属性动画驱动矢量图形变化
- 适合复杂路径形变、颜色渐变等效果
-
核心类与属性
-
VectorDrawable(静态矢量图)
- XML 标签:
<vector> - 关键属性:
android:width/height:画布尺寸android:viewportWidth/Height:视口坐标系<path>子元素定义路径:android:name:路径名称(用于动画绑定)android:pathData:路径指令(M、L、C 等)android:fillColor/strokeColor:填充/描边颜色
- XML 标签:
-
AnimatedVectorDrawable(动画矢量图)
- XML 标签:
<animated-vector> - 关键属性:
- 通过
<target>标签绑定动画到VectorDrawable属性:android:name:目标路径名称android:animation:关联的属性动画资源
- 通过
- XML 标签:
-
属性动画(驱动变化)
- 常用类:
ObjectAnimator(XML 或代码创建) - 关键属性(XML 中):
android:propertyName:要动画的属性(如pathData、fillColor)android:duration:持续时间android:valueFrom/android:valueTo:起始/结束值android:interpolator:插值器(如@android:interpolator/accelerate_decelerate)
- 常用类:
-
-
示例
-
创建 VectorDrawable(res/drawable/
heart.xml)<?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="64dp" android:height="64dp" android:viewportWidth="24" android:viewportHeight="24"> <path android:name="heart_path" android:pathData="M12,21.35L10.55,20.03C5.4,15.36 2,12.28 2,8.5C2,5.42 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.09C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.42 22,8.5C22,12.28 18.6,15.36 13.45,20.04L12,21.35Z" android:fillColor="#FF0000" android:strokeWidth="0.1"/> </vector> -
定义颜色动画(res/animator/
color_animator.xml)<?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:propertyName="fillColor" android:duration="1000" android:valueFrom="#FF0000" android:valueTo="#00FF00" android:valueType="colorType" android:interpolator="@android:interpolator/accelerate_decelerate"/> -
定义路径形变动画(res/animator/
path_morph.xml)<?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:propertyName="pathData" android:duration="1000" android:valueFrom="M12,21.35L10.55,20.03C5.4,15.36 2,12.28 2,8.5C2,5.42 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.09C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.42 22,8.5C22,12.28 18.6,15.36 13.45,20.04L12,21.35Z" android:valueTo="M12,21.35L10.55,20.03C3.4,15.36 2,12.28 2,8.5C2,5.42 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.09C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.42 22,8.5C22,12.28 18.6,15.36 13.45,20.04L12,21.35Z" android:valueType="pathType" android:interpolator="@android:interpolator/bounce""/> -
创建 AnimatedVectorDrawable(res/drawable/
animated_heart.xml)<?xml version="1.0" encoding="utf-8"?> <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/heart"> <target android:name="heart_path" android:animation="@animator/color_animator"/> <target android:name="heart_path" android:animation="@animator/path_morph"/> </animated-vector> -
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" android:padding="16dp"> <!-- 动画容器 --> <ImageView android:id="@+id/iv_heart" android:layout_width="100dp" android:layout_height="100dp" app:srcCompat="@drawable/animated_heart" /> <!-- 状态显示 --> <TextView android:id="@+id/tv_status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Status: Ready"/> <!-- 控制按钮 --> <Button android:id="@+id/btn_control" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Start Animation"/> <Button android:id="@+id/btn_reset" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="Reset"/> </LinearLayout> -
MainActivityclass MainActivity : AppCompatActivity() { private lateinit var ivHeart: ImageView private lateinit var tvStatus: TextView private var heartAnim: Animatable? = null private var isAnimating = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 初始化视图 ivHeart = findViewById(R.id.iv_heart) tvStatus = findViewById(R.id.tv_status) val btnControl: Button = findViewById(R.id.btn_control) val btnReset: Button = findViewById(R.id.btn_reset) // 初始化动画对象 ivHeart.post { (ivHeart.drawable as? Animatable)?.let { heartAnim = it updateStatus("Initialized") } } // 控制按钮点击 btnControl.setOnClickListener { heartAnim?.let { anim -> when { anim.isRunning -> { anim.stop() isAnimating = false updateStatus("Stopped") btnControl.text = "Start" } else -> { anim.start() isAnimating = true updateStatus("Running") btnControl.text = "Stop" } } } } // 重置按钮点击 btnReset.setOnClickListener { heartAnim?.let { anim -> if (anim.isRunning) anim.stop() resetAnimation() updateStatus("Reset") btnControl.text = "Start" } } } private fun updateStatus(text: String) { tvStatus.text = "Status: $text" } private fun resetAnimation() { (ivHeart.drawable as? AnimatedVectorDrawable)?.reset() ivHeart.postDelayed({ ivHeart.invalidate() // 强制重绘视图 }, 100) } }
-