Android之动画

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

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

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

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

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

帧动画:

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

首先我们需要新建一个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>

然后我们在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目录,然后在此目录下创建动画文件

我们直接来看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)

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

先新建一个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的父类:

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来达到动画效果了。

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系统点击事件分发机制)这些内容,相对比较复杂,所以会分几个模块来介绍。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值