欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android之动画

程序员文章站 2022-04-30 21:10:25
...

好看的界面离不开好看、流畅的动画,Android系统提供2大动画系统:传动动画和属性动画。

启动传动动画又分为帧动画和补间动画;

帧动画:即图形以一帧一帧的形式播放的动画,像gif一样的效果;

补间动画:即我们常见的alpha(透明度),translate(位移),scale(缩放)、rotate(旋转)动画。

下面我们开始分别来介绍:

帧动画:

Android之动画

效果如上所示,下面来看看怎么实现的。

首先我们需要新建一个drawable文件 animation :

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:drawable="@drawable/an_1"
        android:duration="100" />
    <item
        android:drawable="@drawable/an_2"
        android:duration="100" />
  ……
总共31张图片 an_1到an_31
</animation-list>

Android之动画

然后我们在xml中定义个ImageView并且设置src为刚刚定义的drawable:

<?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=".AActivity">

    <ImageView
        android:id="@+id/acMainImgAnimation"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/animation" />

</LinearLayout>

最后只需要在代码中启动动画即可:

val an = acMainImgAnimation.drawable as AnimationDrawable
an.start()

先获取图片的drawable,然后转为AnimationDrawable类,看名字就知道它是一个drawable的动画类,然后调用start启动即可。

补间动画:

补间动画的创建分xml和代码创建,我们先来看看xml怎么创建:

现在res目录创建anim目录,然后在此目录下创建动画文件

Android之动画

我们直接来看set.xml,即所有动画的集合,对,没错,上面提到的4种动画是可以同时设置的,任意组合。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:repeatCount="infinite"
    android:repeatMode="reverse"
    android:shareInterpolator="true">

    <alpha
        android:duration="1000"
        android:fromAlpha="0"
        android:toAlpha="1" />
    <rotate
        android:duration="1000"
        android:fromDegrees="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:repeatCount="infinite"
        android:repeatMode="reverse"
        android:startOffset="500"
        android:toDegrees="360" />
    <scale
        android:duration="1000"
        android:fromXScale="0"
        android:fromYScale="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:repeatCount="infinite"
        android:repeatMode="reverse"
        android:toXScale="1"
        android:toYScale="1" />
    <translate
        android:duration="1000"
        android:fromXDelta="0%"
        android:fromYDelta="0%"
        android:repeatCount="infinite"
        android:repeatMode="reverse"
        android:toXDelta="200%"
        android:toYDelta="0%" />
</set>

下面来分别介绍各个标签的意思:

duration:动画持续时间

interpolator:差值器,即动画在执行的时间里按照一定的速率变化。如匀速、加速、减速等等。

repeatCount:正数就是动画执行多少次,infinite无限循环。

repeatMode:重复执行的时候以什么样式reverse:倒叙,如一个从左往右的动画,第二次的时候就是从右往左。restart:重新开始,如从左往右,下一次控件会回到原始位置再从左往右。

shareInterpolator:包含的所有动画是否统一使用一个差值器。

startOffset:开始之前延时多少毫秒

关于坐标:旋转、缩放、位移动画中都会有关于x、y轴位置的设置,如果直接写数字那么就是指定的屏幕坐标,数字后面加%则是相对于自身的坐标,最后面加p则是相对于父控件。如上面最后的translate fromXDelta=“0%” 是指的X轴开始位置是自身的0坐标。

alpha 透明度:

fromAlpha:开始的透明度(0——1)

toAlpha:介绍时的透明度(0——1)

rotate 旋转:

pivotX:X轴的中心点

pivotY:Y轴中心点

fromDegrees:开始的角度

toDegrees:结束角度

scale 缩放:

fromXScale:X轴开始的比例(0——1)

fromYScale:Y轴开始的比例(0——1)

toXScale:X轴结束的比例(0——1)

toYScale:Y轴结束的比例(0——1)

translate 位移:

fromXDelta:X轴开始的位置

fromYDelta:Y轴开始的位置

toXDelta:X轴结束的位置

toYDelta:Y 轴结束的位置

//从xml加载动画
        val alphaAnimation = AnimationUtils.loadAnimation(this, R.anim.alpha)
        acMainViewAnAlpha.startAnimation(alphaAnimation)

        val translateAnimation = AnimationUtils.loadAnimation(this, R.anim.translate)
        acMainViewAnTranslate.startAnimation(translateAnimation)

        val scaleAnimation = AnimationUtils.loadAnimation(this, R.anim.scale)
        acMainViewAnScale.startAnimation(scaleAnimation)

        val rotateAnimation = AnimationUtils.loadAnimation(this, R.anim.rotate)
        acMainViewAnRotate.startAnimation(rotateAnimation)

        val animation = AnimationUtils.loadAnimation(this, R.anim.set)
        acMainViewAnSet.startAnimation(animation)

Android之动画

下面来看看代码怎么创建:

先新建一个AnimationTools

object AnimationTools {

    /**
     * 获取透明度动画
     */
    fun getAlpha(): Animation {
        //第一个参数为开始透明度,第二个为结束透明度
        val animation = AlphaAnimation(0f, 1f)
        animation.duration = 1000
        animation.repeatMode = Animation.REVERSE
        animation.repeatCount = Animation.INFINITE
        return animation
    }

    fun getScale(): Animation {
        /*
        第一个X开始位置
        第二个X结束位置
        第三个Y开始位置
        第四个Y结束位置
        第五个缩放的相对位置类型,这里设置的相对空间自身,跟xml里面写%一样
        第六个为X轴缩放中心点
        第七第八同理为Y轴
         */
        val animation = ScaleAnimation(0f, 1f, 0f, 1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f)
        animation.duration = 1000
        animation.repeatMode = Animation.REVERSE
        animation.repeatCount = Animation.INFINITE
        animation.interpolator = AccelerateDecelerateInterpolator()
        return animation
    }

    fun getRotate(): Animation {
        /*
         *第一和第二位开始角度和结束角度
         * 后面几个参数同上同理
         */
        val animation = RotateAnimation(0f, 360f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f)
        animation.duration = 1000
        animation.repeatMode = Animation.REVERSE
        animation.repeatCount = Animation.INFINITE
        animation.interpolator = AccelerateInterpolator(0.9f)
        return animation
    }

    fun getTranslate(): Animation {
        /*
        第一个为X轴开始移动相对坐标类型,同上同理为自身或者父控件
        第二个为X轴开始位置
        第三第四位X轴结束位置类型和坐标
        后四个同理为Y轴
         */
        val animation = TranslateAnimation(
            Animation.RELATIVE_TO_PARENT,
            0f,
            Animation.RELATIVE_TO_SELF,
            2f,
            Animation.RELATIVE_TO_SELF,
            0f,
            Animation.RELATIVE_TO_SELF,
            0f
        )
        animation.duration = 1000
        animation.repeatMode = Animation.REVERSE
        animation.repeatCount = Animation.INFINITE
        animation.interpolator = DecelerateInterpolator(0.5f)
        return animation
    }

    fun getSet(): Animation {
        /*
        AnimationSet通过add方法添加各种其他动画,动画之间是可以通过startOffset来添加延时错开的
         */
        val animation = AnimationSet(true)
        animation.addAnimation(getAlpha())
        animation.addAnimation(getScale())
        animation.addAnimation(getRotate())
        animation.addAnimation(getTranslate())
        return animation
    }
}

效果跟之前的是一样的。

下面我们来看看属性动画:

我们先来看一下用法:

//这里定义一个objectAnimator
        // 第一个参数是我们需要显示动画的控件
        //第二个参数是要修改的属性,需要注意的是这个属性必须是控件里面有的属性,并且可以修改的,即View是有setXXX方法的。比如我们介绍的这4种动画,这里修改的是透明度
        //后面的参数跟我们用xml或者代码创建的就是一样的了
        val alphaAnimation = ObjectAnimator.ofFloat(acMainViewAnAlpha, "alpha", 0f, 1f)
        //同样设置时间和差值器
        alphaAnimation.duration = 1000
        alphaAnimation.interpolator = LinearInterpolator()
        //这里添加了一个动画更新的监听器
        alphaAnimation.addListener(object : Animator.AnimatorListener {
            //动画从新开始
            override fun onAnimationRepeat(animation: Animator?) {
            }

            //动画结束
            override fun onAnimationEnd(animation: Animator?) {
            }

            //动画取消
            override fun onAnimationCancel(animation: Animator?) {
            }

            //动画开始
            override fun onAnimationStart(animation: Animator?) {
            }
        })
        //这里添加一个动画更新的监听器,通过参数可以获取到当前的值是多少,
        // 比如刚刚设置透明度是0到1,这里就会实时的更新获取到0到1的小数
        alphaAnimation.addUpdateListener {
            Log.d("ZLog AActivity", "onCreate: ${it.animatedValue}")
        }
        //开始运行
        alphaAnimation.start()

属性动画的运行模式就是通过不断的设置我们需要修改的属性值,然后重绘View来达到动画的效果。

那么我们就可以非常方便的来设置想要的动画了,很多自定义的控件我们就可以通过它来控制动画。

ObjectAnimator类有很多方法可以使用,它的核心是通过设置的持续时间、差值器、开始、结束的值来持续的产生数字,拿到这些数字之后再来更新View。

来看一下ObjectAnimator的父类:

Android之动画

ValueAnimator这个类就是专门用来负责产生持续的数字的。

val animation = ValueAnimator.ofInt(0, 255)
        animation.duration = 1000
        animation.addUpdateListener {
            Log.d("ZLog AActivity", "updateListener: ${it.animatedValue}")
        }
        animation.start()

上面这段代码就会在1秒之内持续的参数0到255的整数,通过这种动画的形式获取到数字后我们就可以方便的重写绘制View来达到动画效果了。

Android之动画

class ProgressTestView : View {

    private val paint = Paint()

    var progressValue = 0f

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)

    constructor(context: Context, attributeSet: AttributeSet?, style: Int) : super(context, attributeSet, style) {
        paint.strokeWidth = 30f
        paint.color = Color.RED
        startAnimation()
    }

    /**
     * 核心代码,开始动画
     */
    private fun startAnimation() {
        val animation = ValueAnimator.ofFloat(0f, 1f)
        animation.duration = 5000
        animation.interpolator = DecelerateInterpolator(0.8f)
        animation.repeatCount = ValueAnimator.INFINITE
        animation.repeatMode = ValueAnimator.REVERSE
        //上面都是基本的一些动画设置,在这里添加更新的回调
        animation.addUpdateListener {
            //通过回调参数拿到当前值
            progressValue = it.animatedValue as Float
            //触发重绘
            invalidate()
        }
        animation.start()
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.let {
            //这里更近当前进度值绘制一条横线
            val w = width * progressValue
            it.drawLine(0f, 20f, w, 20f, paint)
        }
    }

}

通过上面的代码我们能够进一步的理解ValueAnimator。刚刚的View里面控制进度或者说能进行动画效果的属性是progressValue,那么我们能不能使用之前的ObjectAnimator来处理呢?

首先我们在ProgresTextView里面取消动画的执行

constructor(context: Context, attributeSet: AttributeSet?, style: Int) : super(context, attributeSet, style) {
        paint.strokeWidth = 30f
        paint.color = Color.RED
//        startAnimation()
}

然后对progressValue属性的设置方法做一下修改:

  var progressValue = 0f
        set(value) {
            field = value
            invalidate()
        }

就是在设置的时候进行重绘。

最后在Activity的代码中加入下面的代码:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val animation = ObjectAnimator.ofFloat(acMainView, "progressValue", 0f, 1f)
        animation.duration = 5000
        animation.interpolator = DecelerateInterpolator(0.8f)
        animation.repeatCount = ValueAnimator.INFINITE
        animation.repeatMode = ValueAnimator.REVERSE
        animation.start()
    }

这样动画就能跟刚才一样执行了

好了,动画相关的内容就先讲到这里,上面关于自定义View的内容将会在后面分几个部分来介绍。因为它涉及到测量、布局、重绘和点击事件(Android系统点击事件分发机制)这些内容,相对比较复杂,所以会分几个模块来介绍。