前言:写这一篇文章的目的是记录一个自己犯下的一个蠢萌的问题,顺便复习一下Animation和Animator。最近好像喜欢上了写博客,我发现写博客不仅可以增强自己的表达能力,还可以强化巩固自己学习过的知识点,毕竟你要向读者讲明白一件事情,首先要自己搞清楚才行。
今天遇到了一个SB的Bug, 不是Bug是SB,是我SB。我在写ViewsFlipper–最易用的的仿淘宝、京东消息轮播控件这篇文章的时候,想给增加一个设置动画滚动方向的功能,因为本来只能垂直滚动,那么增加一下水平滚动好像也不是什么难事,将Animator的Property根据传入的方向设置为“translationX”或者“translationY”,然后赋予不同的Values即可。然后build-Run,点击按钮改变滚动方向,what?我的View怎么不见了?!
刚开始我怀疑是因为我的View被隐藏了,因为我有在代码中设置动态的去改变View的可见性,但是我通过Log和Debug发现,此时的View的Visibility是VISIBLE的!那就奇怪了?难道是我的Animator的Property或者Values没有设置正确?于是我又检查了这两个属性,发现好像也没问题。但是在Debug的时候,我注意到此时的View的位置好像有点不对,此时的View的x轴Left坐标是负的!并且x轴的Right坐标是0!怪不得我在屏幕上看不到View,但是它的Visibility却是VISIBLE的,尼玛的原来它在屏幕范围外面啊!
那么谁将它移动到外面去了呢?显而易见是我自己啊!先给自己一巴掌。至于原因呢,当然是Animator的功劳了。不同于Android的Animation,在动画执行完成之后,View会恢复初始位置,因为使用Animation不会改变View的位置。但是使用Animator却会改变View的位置,并且永远改变!!!你要是取消或者结束了动画,View执行到哪里就会停在哪里,不会恢复到初始位置。因为View在水平滚动的时候,执行退出动画时将View从0向左移动View一个屏幕宽度的距离,从而达到退场的目的。但是我在改变View滚动方向的函数里面,并没有考虑到已经有个View被我无情的抛出到屏幕外面了(我以为它在屏幕里面),所以导致在执行垂直滚动的时候,在屏幕外面的View就自个自的在看不到的地方滚动。
所以知识点要掌握牢固啊!不然就会像我这样,混淆了Animator和Animation,白白浪费了大把时间去做无用功。 下面就简单讲一下Animation和Animator吧,后面有时间再系统的整理一下Android动画方面的知识。
动画无外乎平移、旋转、缩放、透明度,对应的Android的Animation动画类分别是TranslateAnimation、RotateAnimation、ScaleAnimation、AlphaAnimation,他们都继承自Animation抽象类。先介绍一下Animation基类中几个重要的公共属性。
name | 作用 |
---|---|
duration | 动画时长,单位是毫秒(ms) |
repeatCount | 动画重复次数,默认0次,当设置次数小于0时,代表重复无限次 |
repeatMode | 重复类型,有Animation.RESTART和Animation.REVERSE两种,分别代表动画结束时是从头开始和反向动画,需要设置repeatCount为Animation.INFINITE才会生效,默认是RESTART |
interpolator | 插值器,控制动画执行速度,比如匀速、加速、减速执行等。 |
fillBefore | 动画执行完成之后是否回到初始位置,默认true |
fillAfter | 动画执行完成之后是否留在原地,默认false |
以上属性都可以在代码中设置,也可以在xml文件中以标签的形式指定。Animation的XML格式的动画,都存放在res/anim/目录下,分别有不同的标签。Animation并不能改变View的真实位置!!!它只能改变View的显示位置,如果你在平移或旋转之后的地方点击,是不会响应点击事件的,你要点击View原来的点击事件还是会响应点击事件的!!!
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:toXDelta="100%"
android:toYDelta="100%" />
TranslateAnimation类对应的xml标签是translate,除了Animation通用属性,Translation还有自己的属性。
name | 作用 |
---|---|
android:fromXDelta | 动画起点的X轴坐标值,可以是数值、百分值、百分值+p,分别代表在当前View的左上角,X轴加上数值(如20代表加上20px)的距离,百分值 就是加上自身宽度的百分值(如20%代表加上View width * 20%,百分值+p 就是加上父布局的宽度的百分值(如20%p代表加上Parent View width * 20%)作为起点X轴坐标。 |
android:fromYDelta | 动画起点的Y轴坐标值,属性同上 |
android:toXDelta | 动画终点的X轴坐标值,属性同上 |
android:toYDelta | 动画终点的Y轴坐标值,属性同上 |
TranslateAnimation的代码生成是通过其构造函数生成的,
上面是常用到的两个构造函数,其中第一个构造函数的值,只能传递具体的数值,对应xml中的数值,而第二个构造函数就和xml中一样了,可以传递数值,百分值,百分值+p,具体是哪一种,取决于参数前面的一个Type参数。该参数有三种类型,Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, orAnimation.RELATIVE_TO_PARENT,分别代表绝对值、相对自身和相对父布局,和xml一一对应。下面看一下实战例子,最近主要在使用kotlin,所以展示代码也用kotlin来写了,代码和java差不多,会java应该不难看懂。
val animation = if (fromXml) {
AnimationUtils.loadAnimation(this, R.anim.translate_anim)
} else {
val animationCode = TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 1f,
Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 1f
)
animationCode.duration = 3000L
animationCode.fillAfter = true
animationCode.interpolator = AccelerateInterpolator()
animationCode
}
iv_animation_like.startAnimation(animation)
在XML中设置的TranslateAnimation和在代码中生成的TranslateAnimation都使用了相对自身的属性,不同的是XML中设置重复无数次,然后往返运动(因为设置了repeatMode是reverse),代码中没有设置,默认不重复,但是动画结束后会停在原地(设置的fillAfter为true)。
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:fillBefore="true"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:toDegrees="180" />
RotateAnimation对应的xml标签是rotate,它的属性有
name | 作用 |
---|---|
fromDegrees | 开始旋转的角度 |
toDegrees | 旋转结束的角度 |
pivotX | 旋转的中心点X坐标,可以是数值,百分值,百分值+p |
pivotY | 旋转的中心点Y坐标,可以是数值,百分值,百分值+p |
构造函数:
如果没有设置旋转中心点,默认是0,其他没啥好讲的,和TranslateAnimation一样,上Code,看动画。
val animation = if (fromXml) {
AnimationUtils.loadAnimation(this, R.anim.rotate_anim)
} else {
val animationCode = RotateAnimation(0f, 180f)
animationCode.duration = 3000L
animationCode.fillAfter = true
animationCode.interpolator = AccelerateInterpolator()
animationCode
}
iv_animation_like.startAnimation(animation)
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:fillAfter="true"
android:fromXScale="100%"
android:fromYScale="100%"
android:pivotX="50"
android:pivotY="50"
android:toXScale="50%"
android:toYScale="50%" />
ScaleAnimation对应的xml标签是scale ,它的属性有
name | 作用 |
---|---|
fromXScale | X轴开始缩放时的大小 |
fromYScale | Y轴开始缩放时的大小 |
toXScale | X轴结束缩放时的大小 |
toYScale | Y轴结束缩放时的大小 |
pivotX | 缩放的中心点X坐标,可以是数值,百分值,百分值+p |
pivotY | 缩放的中心点Y坐标,可以是数值,百分值,百分值+p |
构造函数:
构造函数和XML设置一一对应,不再多言,上示例代码,看动画
val animation = if (fromXml) {
AnimationUtils.loadAnimation(this, R.anim.scale_anim)
} else {
val animationCode = ScaleAnimation(1f, 2f, 1f, 2f)
animationCode.duration = 3000L
animationCode.fillAfter = true
animationCode.interpolator = AccelerateInterpolator()
animationCode
}
iv_animation_like.startAnimation(animation)
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:fromAlpha="0.0"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:toAlpha="1.0" />
AlphaAnimation对应的xml标签是alpha ,它的属性有
name | 作用 |
---|---|
fromAlpha | 动画开始时的透明度 |
toAlpha | 动画结束时的透明度(0f—1.0f) |
构造函数:
val animation = if (fromXml) {
AnimationUtils.loadAnimation(this, R.anim.alpha_anim)
} else {
val animationCode = AlphaAnimation(1f, 0.1f)
animationCode.duration = 3000L
animationCode.fillAfter = true
animationCode.interpolator = AccelerateInterpolator()
animationCode
}
iv_animation_like.startAnimation(animation)
终于讲到导致我出bug的罪魁祸首了,不过今天就先到这里吧,明天起来再整理一下资料再写下去。睡了。。。
早起查阅了一上午资料,发现Animator的东西有点多,一篇文章大概是讲不完了,回头更新到新的文章,写好之后在这里放个链接吧。
依旧是”全球最大同性交友网站“github,路径me.fresh.lee.kotlintest.activity.AnimationActivity