Android开发——四大动画

2020.8.13

为了让动作间的切换更加美观,动画的使用尤为关键,下面将介绍Android中的4大动画

一.帧动画

使用一个动画集合animation-list管理多张图片,每隔一定时间就播放一张,一张即一帧,类似于Gif动图效果

  1. 使用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()
    }

火焰燃烧效果图

fire.gif

想让火焰燃烧更加旺盛,可以减小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()
        }
    }

二.补间动画

在起始状态和终点状态之间添加动画,让它的转换没有那么突兀
先看效果图

所有动画效果展示.gif

补间动画中常用的几种属性和方法

名称 说明
duration 动画时长,单位是毫秒
repeatCount 重复次数,无限是INFINITE
repeatMode 重复的方式,从头或从上次末尾开始
reset() 重新设置动画
start() 启动动画
cancel() 取消动画
fillAfter 是否停留在结束状态
  1. 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)
       }

以上是淡出效果的展示,淡入只需要反转起始点和终点即可

  1. 移动动画——TranslateAnimation


    移动方向.png

根据上图可以知道以控件左上角为原点,以及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
       }

在实际应用中方式一还是最常用的,移动的最佳距离都是靠一次又一次的不断调试才确定的,而且绝对距离便于控制

  1. 旋转动画——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)
        }
  1. 缩放动画——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)
        }

如果没有设置中心点坐标,那么会默认以空间的左上角为中心点进行动画

  1. 综合动画——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)
        }
  1. 设置动画监听事件——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添加新的动画,也可以直接让控件启动新的动画

三.属性动画

由于补间动画仅仅是视觉上的效果,比如移动动画,看上去是控件移动了,但事实上控件本身还在原地,所以我们看到的是一种虚像,那如果该控件还需响应点击事件,我们只能点击它的起始位置,这样的话肯定是不行的,所以我们此时需要使用到属性动画

动画结构图.png

创建动画的方式:ObjectAnimator.ofFloat(控件对象,属性名,一系列的值变化),能够添加属性动画的属性都提供了set/get方法

  1. 旋转动画
ObjectAnimator.ofFloat(view,"rotation",0f,360f).apply {
                duration=1000
                start()
            }

属性动画的好处就是更简洁一点,特别是对一系列连续的动画,比如这里的旋转,0f->360f->-180f->0f,就可实现连续的旋转,除了代码中绕z轴旋转外,还可绕x(rotationX),y(rotationY)轴旋转,可以设计出不一样的旋转,当然它也有不方便的地方,旋转和缩放的中心点都默认是控件的几何中心,且不可更改

  1. 缩放动画

由于一个属性动画的对象只能改变一个属性,而缩放动画既涉及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()
            }

对于移动动画也可使用以上两种方式进行管理

  1. 透明度动画
ObjectAnimator.ofFloat(view,"alpha",0.5f,1.0f,0f).apply {
                duration=1000
                start()
            }
  1. 移动动画
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()
            }
  1. 动画监听
  • 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?) {
                        可以获取数据的更新范围
                    }
                })
  1. 如果是对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
               }
  1. ValueAnimator->获取动画过程中每个阶段的具体数据,由于它每个一段时间会产生一个值,模拟一个连续变化的过程,因此常用来自定义动画
ValueAnimator.ofFloat(0f,100f).apply { 
                duration=100
                addUpdateListener { 
                    Log.v("cx","${it.animatedValue}")
                }
                start()
            }

四.转场动画(Transition)——Activity切换之间的动画

转场动画的两种方式

  1. overridePendingTransition
    步骤一:首先在res/anim中创建xml资源文件,然后进行配置
    步骤二:在界面跳转时进行设置

overridePendingTransition(进来页面的动画,出来页面的动画)


转换图解.gif
<?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的左上角顶点
回到上一个页面只需要反过来计算即可,除了左右平移,还可上下平移,计算方式相同,只需修改移动属性和数据,当然还有淡入淡出等

  1. 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
            )
  1. 给窗体设置动画
    给窗体设置的动画需要伴随着共享动画一起使用
    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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342