2020.8.13
为了让动作间的切换更加美观,动画的使用尤为关键,下面将介绍Android中的4大动画
一.帧动画
使用一个动画集合animation-list管理多张图片,每隔一定时间就播放一张,一张即一帧,类似于Gif动图效果
- 使用xml的方式创建帧动画
步骤一:新建一个Drawable Resource File,并添加图片资源
步骤二:将ImageView的background设置为该xml
步骤三:在代码中通过获取ImageView的drawable资源,转换为AnimationDrawable,并启动
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
oneshot:用来设置是否只播放一次
android:oneshot="false">
<item android:drawable="@drawable/campfire01" android:duration="200"/>
<item android:drawable="@drawable/campfire02" android:duration="200"/>
<item android:drawable="@drawable/campfire03" android:duration="200"/>
<item android:drawable="@drawable/campfire04" android:duration="200"/>
<item android:drawable="@drawable/campfire05" android:duration="200"/>
<item android:drawable="@drawable/campfire06" android:duration="200"/>
<item android:drawable="@drawable/campfire07" android:duration="200"/>
<item android:drawable="@drawable/campfire08" android:duration="200"/>
<item android:drawable="@drawable/campfire09" android:duration="200"/>
<item android:drawable="@drawable/campfire10" android:duration="200"/>
<item android:drawable="@drawable/campfire11" android:duration="200"/>
<item android:drawable="@drawable/campfire12" android:duration="200"/>
<item android:drawable="@drawable/campfire13" android:duration="200"/>
<item android:drawable="@drawable/campfire14" android:duration="200"/>
<item android:drawable="@drawable/campfire15" android:duration="200"/>
<item android:drawable="@drawable/campfire16" android:duration="200"/>
<item android:drawable="@drawable/campfire17" android:duration="200"/>
</animation-list>
----------------------------------------------------------------------------------------
override fun onResume() {
super.onResume()
//找到ImageView对应的src的drawable资源 AnimationDrawable
(imageAnim.drawable as AnimationDrawable).start()
}
火焰燃烧效果图
想让火焰燃烧更加旺盛,可以减小duration的值
2.使用代码创建帧动画
步骤一:创建AnimationDrawable,并添加图片资源
步骤二:关联图片和帧动画,ImageView.setImageDrawable()
步骤三:启动start()
fun manulFireAnim(){
val fires=arrayOf(
R.drawable.campfire01,
R.drawable.campfire02,
R.drawable.campfire03,
R.drawable.campfire04,
R.drawable.campfire05,
R.drawable.campfire06,
R.drawable.campfire07,
R.drawable.campfire08,
R.drawable.campfire09,
R.drawable.campfire10,
R.drawable.campfire11,
R.drawable.campfire12,
R.drawable.campfire13,
R.drawable.campfire14,
R.drawable.campfire15,
R.drawable.campfire16,
R.drawable.campfire17
)
AnimationDrawable().apply {
//添加图片资源
for (item in fires){
addFrame(getDrawable(item)!!,200)
}
//关联图片和帧动画
imageAnim.setImageDrawable(this)
//启动
start()
}
}
二.补间动画
在起始状态和终点状态之间添加动画,让它的转换没有那么突兀
先看效果图
补间动画中常用的几种属性和方法
名称 | 说明 |
---|---|
duration | 动画时长,单位是毫秒 |
repeatCount | 重复次数,无限是INFINITE |
repeatMode | 重复的方式,从头或从上次末尾开始 |
reset() | 重新设置动画 |
start() | 启动动画 |
cancel() | 取消动画 |
fillAfter | 是否停留在结束状态 |
- AlphaAnimation:淡入淡出动画
- 方式①:使用xml配置文件
使用xml配置的方式,需要在res下新建文件夹anim,将所有动画的资源##.xml放在res/anim文件下
步骤一:在anim文件夹下创建动画xml,进行属性配置
步骤二:使用AnimationUtils中的loadAnimation方法加载xml中的动画并返回一个Animation对象
步骤三:ImageView.start(动画)进行启动动画
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:fillAfter="true"
android:duration="1000"
android:repeatMode="reverse"
-1代表一直重复
android:repeatCount="-1"
/>
---------------------------------------------------------------------------------------
fun AlphaXml(){
//将xml中的动画解析出来
val alphaAnimation=AnimationUtils.loadAnimation(this,R.anim.fade_out_anim) as AlphaAnimation
//将这个动画关联控件
view.startAnimation(alphaAnimation)
}
- 方式②:使用代码进行创建
步骤一:创建AlphaAnimation(起始,终点),然后进行属性配置
步骤二:关联控件启动动画
AlphaAnimation(1.0f,0.0f).apply {
duration=1000
fillAfter=true
repeatMode=Animation.REVERSE
repeatCount=Animation.INFINITE
}.also {
view.startAnimation(it)
}
以上是淡出效果的展示,淡入只需要反转起始点和终点即可
-
移动动画——TranslateAnimation
根据上图可以知道以控件左上角为原点,以及x,y的正方向,由此便可设定控件的移动方向
- 设置控件移动距离有三种方式:
①设置固定值 100 ABSOLUTE
②相对于自身的比例 100%->移动自身的一个宽度 RELATIVE_TO_SELF
③相对于父容器的比例 100%p->移动父容器的一个宽度 RELATIVE_TO_PARENT - 方式①:使用xml的方式创建
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
移动两个自身的宽度
android:toXDelta="200%"
android:fromYDelta="0"
移动一个自身的高度
android:toYDelta="100%"
android:fillAfter="true"
android:duration="1000"
/>
---------------------------------------------------------------------------------------
val translateAnimation=AnimationUtils.loadAnimation(this,R.anim.translate) as TranslateAnimation
view.startAnimation(translateAnimation)
- 方式②:使用代码创建
方式一
TranslateAnimation(0f,200f,0f,0f).apply {
duration=1000
fillAfter=true
}
方式二
TranslateAnimation(
Animation.RELATIVE_TO_SELF,0f,
Animation.RELATIVE_TO_SELF,2f,
Animation.RELATIVE_TO_SELF,0f,
Animation.RELATIVE_TO_SELF,0f
).apply {
duration=1000
fillAfter=true
}
方式三
TranslateAnimation(
Animation.RELATIVE_TO_PARENT,0f,
Animation.RELATIVE_TO_PARENT,0.5f,
Animation.RELATIVE_TO_PARENT,0f,
Animation.RELATIVE_TO_PARENT,0f
).apply {
duration=1000
fillAfter=true
}
在实际应用中方式一还是最常用的,移动的最佳距离都是靠一次又一次的不断调试才确定的,而且绝对距离便于控制
- 旋转动画——RotateAnimation
属性说明:pivotX、pivotY用来设置旋转中心在控件内部的位置
有固定值和相对于自身的百分比,百分比是最常用的方式
- 方式①:xml配置
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="360"
中心点为旋转点
android:pivotX="50%"
android:pivotY="50%"
android:duration="1000"
android:repeatCount="infinite"
/>
---------------------------------------------------------------------------------------
val rotateAnimation= AnimationUtils.loadAnimation(this,R.anim.rotate) as RotateAnimation
view.startAnimation(rotateAnimation)
- 方式②: 代码创建
方式一
RotateAnimation(
0f,360f,
Animation.RELATIVE_TO_SELF,0.5f,
Animation.RELATIVE_TO_SELF,0.5f
).apply {
duration=1000
repeatCount=Animation.INFINITE
}.also {
view.startAnimation(it)
}
方式二
RotateAnimation(0f,360f,200f,200f).apply {
duration=1000
repeatCount=Animation.INFINITE
}.also {
view.startAnimation(it)
}
- 缩放动画——ScaleAnimation
- 方式①:使用xml配置
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXScale="100%"
android:toXScale="150%"
android:fromYScale="100%"
android:toYScale="150%"
android:pivotX="50%"
android:pivotY="50%"
android:duration="1000"
/>
--------------------------------------------------------------------------------------
val scaleAnimation=AnimationUtils.loadAnimation(this,R.anim.scale)
view.startAnimation(scaleAnimation)
- 方式②:使用代码创建
ScaleAnimation(
0.5f,1.2f,0.5f,1.2f,
Animation.RELATIVE_TO_SELF,0.5f,
Animation.RELATIVE_TO_SELF,0.5f
).apply {
duration=1000
}.also {
view.startAnimation(it)
}
如果没有设置中心点坐标,那么会默认以空间的左上角为中心点进行动画
- 综合动画——AnimationSet
如果我们需要对一个控件同时进行多个动画,那么我们就需要动画集合,补件动画中是AnimationSet
- 方式①:使用xml配置
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
>
<rotate
android:fromDegrees="0"
android:toDegrees="360"
android:pivotY="50%"
android:pivotX="50%"/>
<scale
android:fromXScale="10%"
android:toXScale="150%"
android:fromYScale="10%"
android:toYScale="150%"
android:pivotX="50%"
android:pivotY="50%"/>
</set>
---------------------------------------------------------------------------------------
val animationSet=loadAnimation(this,R.anim.animationset) as AnimationSet
view.startAnimation(animationSet)
- 方式②:使用代码创建
val scaleAnimation=ScaleAnimation(
0.1f,1.5f,0.1f,1.5f,
Animation.RELATIVE_TO_SELF,0.5f,
Animation.RELATIVE_TO_SELF,0.5f
)
val rotateAnimation=RotateAnimation(
0f,360f,
Animation.RELATIVE_TO_SELF,0.5f,
Animation.RELATIVE_TO_SELF,0.5f
)
animationSet=AnimationSet(true).apply {
addAnimation(scaleAnimation)
addAnimation(rotateAnimation)
fillAfter=true
duration=1000
view.startAnimation(this)
}
- 设置动画监听事件——setAnimationListener(Animation.AnimationListener的一个对象)
AlphaAnimation(from,to).apply {
duration=1000
fillAfter=true
setAnimationListener(object :Animation.AnimationListener{
override fun onAnimationRepeat(animation: Animation?) {
}
override fun onAnimationEnd(animation: Animation?) {
Log.v("cx","动画结束啦")
}
override fun onAnimationStart(animation: Animation?) {
}
})
}
在补间动画中常用动画结束时的监听方法,这样可以实现多个动画连续播放,可以通过AnimationSet添加新的动画,也可以直接让控件启动新的动画
三.属性动画
由于补间动画仅仅是视觉上的效果,比如移动动画,看上去是控件移动了,但事实上控件本身还在原地,所以我们看到的是一种虚像,那如果该控件还需响应点击事件,我们只能点击它的起始位置,这样的话肯定是不行的,所以我们此时需要使用到属性动画
创建动画的方式:ObjectAnimator.ofFloat(控件对象,属性名,一系列的值变化),能够添加属性动画的属性都提供了set/get方法
- 旋转动画
ObjectAnimator.ofFloat(view,"rotation",0f,360f).apply {
duration=1000
start()
}
属性动画的好处就是更简洁一点,特别是对一系列连续的动画,比如这里的旋转,0f->360f->-180f->0f,就可实现连续的旋转,除了代码中绕z轴旋转外,还可绕x(rotationX),y(rotationY)轴旋转,可以设计出不一样的旋转,当然它也有不方便的地方,旋转和缩放的中心点都默认是控件的几何中心,且不可更改
- 缩放动画
由于一个属性动画的对象只能改变一个属性,而缩放动画既涉及X,又涉及Y,所以我们需要使用AnimatorSet进行管理,AnimatorSet有两种播放模式
- playSequentially(动画1,动画2....动画n)
按照动画参数的顺序进行播放 - playTogether(动画1,动画2....动画n)
将所添加的动画同时播放
val x=ObjectAnimator.ofFloat(view,"scaleX",0.1f,1.2f,1.0f,1.5f).apply {
duration=1000
}
val y=ObjectAnimator.ofFloat(view,"scaleY",0.1f,1.2f,1.0f,1.5f).apply {
duration=1000
}
AnimatorSet().apply {
//同时播放
playTogether(x,y)
start()
}
除了使用AnimatorSet可以实现同时播放动画外,还可使用PropertyValuesHolder(属性名,系列值变化)创建多个holder,然后使用ObjectAnimator.ofPropertyValuesHolder(控件,holder1,holder2....holdern)进行管理,这种方式只能同时播放动画
val holdx= PropertyValuesHolder.ofFloat("scaleX",0.1f,1.2f,1.0f,1.5f)
val holdy= PropertyValuesHolder.ofFloat("scaleY",0.1f,1.2f,1.0f,1.5f)
ObjectAnimator.ofPropertyValuesHolder(view,holdx,holdy).apply {
duration=1000
start()
}
对于移动动画也可使用以上两种方式进行管理
- 透明度动画
ObjectAnimator.ofFloat(view,"alpha",0.5f,1.0f,0f).apply {
duration=1000
start()
}
- 移动动画
val tx=PropertyValuesHolder.ofFloat("translationX",100f,200f,300f)
val ty=PropertyValuesHolder.ofFloat("translationY",100f,200f,300f)
ObjectAnimator.ofPropertyValuesHolder(view,tx,ty).apply {
duration=2000
start()
}
- 动画监听
- Animator.AnimatorListener
addListener(object : Animator.AnimatorListener{
override fun onAnimationStart(p0: Animator?) {
动画开始了
}
override fun onAnimationEnd(p0: Animator?) {
动画结束了
}
override fun onAnimationCancel(p0: Animator?) {
动画被取消
}
override fun onAnimationRepeat(p0: Animator?) {
动画正在重复
}
})
- Animator.AnimatorPauseListener
addPauseListener(object :Animator.AnimatorPauseListener{
override fun onAnimationPause(p0: Animator?) {
动画暂停了
}
override fun onAnimationResume(p0: Animator?) {
动画重新开始
}
})
- ValueAnimator.AnimatorUpdateListener
addUpdateListener(object :ValueAnimator.AnimatorUpdateListener{
override fun onAnimationUpdate(p0: ValueAnimator?) {
可以获取数据的更新范围
}
})
- 如果是对view的属性进行动画可以使用简洁版的ObjectAnimator
->ViewPropertyAnimator,使用此方式非常简洁,但它不能使一个属性的值连续变化,即值比较单一,只能另外添加动画
//旋转
view.animate().rotation(360f)
//透明度
view.animate().alpha(0f)
//每次移动100,而非移动到100
view.animate().translationXBy(100f)
//同时进行
view.animate()
.translationXBy(100f)
.translationYBy(100f).apply {
duration=1000
}
//缩放
view.animate()
.scaleX(1.5f)
.scaleY(1.5f)
//综合
view.animate()
.scaleX(1.5f)
.scaleY(1.5f)
.alpha(0.5f)
.rotation(360f)
.alpha(1.0f).apply {
duration=2000
}
- ValueAnimator->获取动画过程中每个阶段的具体数据,由于它每个一段时间会产生一个值,模拟一个连续变化的过程,因此常用来自定义动画
ValueAnimator.ofFloat(0f,100f).apply {
duration=100
addUpdateListener {
Log.v("cx","${it.animatedValue}")
}
start()
}
四.转场动画(Transition)——Activity切换之间的动画
转场动画的两种方式
- overridePendingTransition
步骤一:首先在res/anim中创建xml资源文件,然后进行配置
步骤二:在界面跳转时进行设置
overridePendingTransition(进来页面的动画,出来页面的动画)
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
android:toXDelta="-100%p"
android:duration="500"
>
</translate>
----------------------------------------------------------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="100%p"
android:toXDelta="0"
android:duration="500"
>
</translate>
----------------------------------------------------------------------------------------
startActivity(Intent(this,NextActivity::class.java))
overridePendingTransition(R.anim.up_enter,R.anim.up_eixt)
计算移动距离的起始点时Activity的左上角顶点
回到上一个页面只需要反过来计算即可,除了左右平移,还可上下平移,计算方式相同,只需修改移动属性和数据,当然还有淡入淡出等
- ActivityOptions
使用场景,当我们在浏览网页时,点击一张图片,它会立即放大,占满屏幕,这就是ActivityOptions的放大效果,即共享元素,两个页面都有同一张图片,只是大小不同,这种切换方式比较自然
- 使用步骤:
①:为共享元素设置transitonName,两个界面设置的name必须相同
②:创建ActivityOptions的对象
val options= ActivityOptions.makeSceneTransitionAnimation(this,操作的控件,transitonName)
③:启动界面
startActivity(Intent(this,NextActivity::class.java),options.toBundle())
从当前界面切换回去的时候,为了平滑,不使用finish(),而用finishAfterTransition()
- 创建多个共享元素的动画
使用Pair()进行添加
val options= ActivityOptions.makeSceneTransitionAnimation(
this,
Pair<ImageView,String>(imageView,"wiwilover"),
Pair<ImageView,String>(imageView2,"wiwilover2")
)
- 裁剪效果
从控件的某个位置裁剪,拉满整个屏幕
val options= ActivityOptions.makeClipRevealAnimation(
imageView,0,0,imageView.width,imageView.height
)
- 放大效果
从控件的某个位置开始放大,直到拉满屏幕
val options= ActivityOptions.makeScaleUpAnimation(
imageView,0,0,imageView.width,imageView.height
)
- 给窗体设置动画
给窗体设置的动画需要伴随着共享动画一起使用
window设置的是除了共享元素之外的所有控件的动画,所以不太好看,常用的还是共享元素的动画
并且window动画需要在setContentView(R.layout.activity_main)之前进行设置,无论是explode、fade、slide动画都需要添加
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
- Slide
window.enterTransition=Slide(Gravity.END).setDuration(1000) - Fade
window.enterTransition=Fade().setDuration(1000) - Explode
window.enterTransition=Explode().setDuration(1000)