Android 动画总结
- 逐帧动画
- 补间动画
- 属性动画
Android开发中,动画特效是经常遇到的,接下来对这块知识点做一下总结。
一.逐帧动画
逐帧(Frame)动画,就是动画过程的每张静态的图片都收集起来,然后由Android来控制依次展示这些静态图片,再利用人眼的“视觉残留”原理,给用户呈现动画的错觉。(逐帧动画的原理和放电影的原理一致)
- 定义逐帧动画
在< animation-list… />元素中使用< item…/ >子元素定义动画的全部帧即可
<?xml version="1.0" encoding="utf-8"?>
<!--添加多个帧-->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/ic_frame0" android:duration="60"/>
<item android:drawable="@drawable/ic_frame1" android:duration="60"/>
<item android:drawable="@drawable/ic_frame2" android:duration="60"/>
<item android:drawable="@drawable/ic_frame3" android:duration="60"/>
<!--下面忽略多个item-->
</animation-list>
上面的xml文件定义了一个逐帧动画资源,android:oneshot为false指的可以循环播放动画
- 使用逐帧动画
class MainAnimActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_anim)
val framImageView = findViewById<ImageView>(R.id.fram_imge)
val playAniBtn = findViewById<Button>(R.id.play_btn)
val stopAniBtn = findViewById<Button>(R.id.stop_btn)
val anim = framImageView.background
playAniBtn.setOnClickListener{v ->
if (anim is AnimationDrawable) {
anim.start()
}
}
stopAniBtn.setOnClickListener {v ->
if (anim is AnimationDrawable) {
anim.stop()
}
}
}
}
AnimationDrawble(An object used to create frame-by-frame animations)代表的动画默认不播放,提供如下方法来开始、结束动画
start() 开始播放动画
end() 结束动画
- 代码创建逐帧动画
val animationDrawable = AnimationDrawable()
animationDrawable.addFrame(resources.getDrawable(R.drawable.ic_frame0), 60)
animationDrawable.addFrame(resources.getDrawable(R.drawable.ic_frame1), 60)
animationDrawable.addFrame(resources.getDrawable(R.drawable.ic_frame2), 60)
animationDrawable.addFrame(resources.getDrawable(R.drawable.ic_frame3), 60)
animationDrawable.isOneShot = false
framImageView.background = animationDrawable
playAniBtn.setOnClickListener{v ->
animationDrawable.start()
}
stopAniBtn.setOnClickListener {v ->
animationDrawable.stop()
}
二.补间动画
补间动画,就是指开发者只需指定动画开始、动画结束等“关键帧”,而动画变化的“中间帧”由系统计算并补齐。
Android使用Animation代表抽象的动画类
包含如下几个子类:AlphaAnimation(透明度动画)、ScaleAnimation(缩放动画)、TranslateAnimation(位移动画)、RotationAnimation(旋转动画)
AnimationSet : 组合多个补间动画时使用
- 使用补间动画
res/anim/tween_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator">
<!--缩放 -->
<scale
android:duration="3000"
android:fillAfter="true"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="0.1"
android:toYScale="0.1" />
<!--透明度-->
<alpha
android:duration="3000"
android:fromAlpha="1"
android:toAlpha="0.05" />
<!-- 旋转 -->
<rotate
android:duration="3000"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="180" />
<!--平移-->
<translate
android:duration="3000"
android:fromXDelta="100"
android:fromYDelta="100"
android:toXDelta="200"
android:toYDelta="100" />
</set>
//通过AnimationUtils得到代表动画的Animation之后,就可以调用view的startAnimation(Animation anim)方法开始对该view执行动画了
val loadAnimation = AnimationUtils.loadAnimation(this, R.anim.tween_anim)
playAniBtn.setOnClickListener{v ->
framImageView.startAnimation(loadAnimation)
}
- 代码创建补间动画
//透明度动画
val alphaAnimation = AlphaAnimation(1f, 0f)
alphaAnimation.duration = 3000
//旋转动画
/*
* 创建一个旋转动画对象
* 入参列表含义如下:
* 1.fromDegrees:从哪个角度开始旋转
* 2.toDegrees:旋转到哪个角度结束
* 3.pivotXType:旋转所围绕的圆心的x轴坐标的类型,有ABSOLUT绝对坐标、RELATIVE_TO_SELF相对于自身坐标、RELATIVE_TO_PARENT相对于父控件的坐标
* 4.pivotXValue:旋转所围绕的圆心的x轴坐标,0.5f表明是以自身这个控件的一半长度为x轴
* 5.pivotYType:y轴坐标的类型
* 6.pivotYValue:y轴坐标
*/
val rotateAnimation = RotateAnimation(0f, 360f, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.duration = 3000
//缩放动画
/*
* 创建一个缩放效果的动画
* 入参列表含义如下:
* fromX:x轴的初始值
* toX:x轴缩放后的值
* fromY:y轴的初始值
* toY:y轴缩放后的值
* pivotXType:x轴坐标的类型,有ABSOLUT绝对坐标、RELATIVE_TO_SELF相对于自身坐标、RELATIVE_TO_PARENT相对于父控件的坐标
* pivotXValue:x轴的值,0.5f表明是以自身这个控件的一半长度为x轴
* pivotYType:y轴坐标的类型
* pivotYValue:轴的值,0.5f表明是以自身这个控件的一半长度为y轴
*/
val scaleAnimation = ScaleAnimation(1f, 0.8f, 1f, 0.8f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f)
scaleAnimation.duration = 3000
//平移动画
/*
* 创建一个移动动画效果
* 入参的含义如下:
* fromXType:移动前的x轴坐标的类型
* fromXValue:移动前的x轴的坐标
* toXType:移动后的x轴的坐标的类型
* toXValue:移动后的x轴的坐标
* fromYType:移动前的y轴的坐标的类型
* fromYValue:移动前的y轴的坐标
* toYType:移动后的y轴的坐标的类型
* toYValue:移动后的y轴的坐标
*/
val translateAnimation = TranslateAnimation(Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0.2f,
Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0.2f)
translateAnimation.duration = 3000
playAniBtn.setOnClickListener { v ->
val animationSet = AnimationSet(true)
animationSet.addAnimation(alphaAnimation)
animationSet.addAnimation(rotateAnimation)
animationSet.addAnimation(scaleAnimation)
animationSet.addAnimation(translateAnimation)
//播放组合补间动画
framImageView.startAnimation(animationSet)
//播放单一补间动画
//framImageView.startAnimation(alphaAnimation)
}
- 补间动画只是去改变view的展示效果,不会真正改变view的属性
val translateAnimation = TranslateAnimation(Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 1.5f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f)
translateAnimation.fillAfter = true
//延迟2秒执行平移动画
//对比平移前点击按钮和平移后点击按钮的现象
mHandler.postDelayed(Runnable { mTransBtn?.startAnimation(translateAnimation) }, 2000)
mTransBtn?.setOnClickListener {
Toast.makeText(this@MainActivity, getString(R.string.click_trans_btn), Toast.LENGTH_SHORT).show()
}
平移后的按钮无法触发我们注册的点击事件,而此时平移前按钮的位置是可以触发该点击事件的,因为实际上这个按钮还是停留在平移前的位置,只不过补间动画将这个按钮绘制到了平移后的位置
注:属性动画对view属性进行动画,解决了该问题
三.属性动画
属性动画,可以定义任何属性的变化,从某种角度来看,属性动画是增强版的补间动画
1.补间动画只能定义在透明度、旋转、缩放、位移,这4个方面的变化,但属性动画可以定义任何属性的变化
2.补间动画只能对ui组件执行动画,但属性动画几乎可以对任何对象执行动画(不管是否显示在屏幕上)
Animator 提供了创建属性动画的基类,基本不会直接使用该类,通常只用于被继承并重写它的相关方法
ValueAnimator:主要负责计算各帧的属性值,更新对象的相关属性值时使用
ObjectAnimator:是ValueAnimator的子类,对指定对象的属性执行动画时使用
AnimatorSet:组合多个属性动画时使用
- 使用ObjectAnimator属性动画
//透明度属性动画
mAlphaAnimator = ObjectAnimator.ofFloat(mMyLove, "alpha", 0.2f, 1f)
//mAlphaAnimator?.start() //使用alpha属性动画
//缩放属性动画
mScaleAnimatorX = ObjectAnimator.ofFloat(mMyLove, "scaleX", 1f, 1.1f)
mScaleAnimatorY = ObjectAnimator.ofFloat(mMyLove, "scaleY", 1f, 1.1f)
//组合属性动画
mAnimatorSet = AnimatorSet()
mAnimatorSet?.duration = 3000
mAnimatorSet?.playTogether(mScaleAnimatorX, mScaleAnimatorY, mAlphaAnimator)
mAnimatorSet?.start() //使用组合属性动画
- 使用ValueAnimator属性动画
//ValueAnimator属性动画
mProgressAnimator = ValueAnimator.ofInt(0, 100)
mProgressAnimator?.setDuration(3000)
mProgressAnimator?.addUpdateListener(AnimatorUpdateListener { animation ->
Log.i(TAG, "onAnimationUpdate :: " + animation.animatedValue)
mProgressBar?.progress = (animation.animatedValue as Int)
})
mProgressAnimator?.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {
Toast.makeText(this@MainActivity, "动画开始", Toast.LENGTH_SHORT).show()
}
override fun onAnimationEnd(animation: Animator) {
Toast.makeText(this@MainActivity, "动画结束", Toast.LENGTH_SHORT).show()
}
override fun onAnimationCancel(animation: Animator) {
Toast.makeText(this@MainActivity, "动画取消", Toast.LENGTH_SHORT).show()
}
override fun onAnimationRepeat(animation: Animator) {
Toast.makeText(this@MainActivity, "动画重复", Toast.LENGTH_SHORT).show()
}
})
//色值ValueAnimator属性动画
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mColorAnimator = ValueAnimator.ofArgb(-0x1, -0x10000, -0xffff01, -0xff0100)
}
mColorAnimator?.duration = 3000
mColorAnimator?.addUpdateListener { animation -> mStartBtn?.setBackgroundColor((animation.animatedValue as Int)) }
- xml文件定义属性动画
res/animator/animator_set.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="3000"
android:propertyName="alpha"
android:valueFrom="0.2"
android:valueTo="1.0"
android:valueType="floatType">
</objectAnimator>
<objectAnimator
android:duration="3000"
android:propertyName="scaleX"
android:valueFrom="1.0"
android:valueTo="1.1"
android:valueType="floatType">
</objectAnimator>
<objectAnimator
android:duration="3000"
android:propertyName="scaleY"
android:valueFrom="1.0"
android:valueTo="1.1"
android:valueType="floatType">
</objectAnimator>
</set>
res/animator/animator_alpha.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:propertyName="alpha"
android:valueFrom="0.2"
android:valueTo="1.0"
android:valueType="floatType">
</objectAnimator>
代码中使用在xml中定义的属性动画
//加载xml中属性动画
val animatorset = AnimatorInflater.loadAnimator(this, R.animator.animator_set) as AnimatorSet
val animator = AnimatorInflater.loadAnimator(this, R.animator.animator_alpha)
//使用组合属性动画
animatorset.setTarget(mMyLove)
animatorset.start()
//使用属性动画
animator.setTarget(mMyLove)
animator.start()
在使用过程中,可以选择合适的动画进行动画效果实现