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系统点击事件分发机制)这些内容,相对比较复杂,所以会分几个模块来介绍。
上一篇: 列出Jar包中的文件