【Android】自定义View:属性动画

一、ViewPropertyAnimator

最常见也是最简单的动画。如下代码:

iv.animate()   //iv为ImageView
    .translationX(200f)
    .translationXBy(200f)
    .x(200f)
    .xBy(200f)
    .setDuration(1000)
    .start() 

上面代码中只列出了X轴平移的动画。ViewPropertyAnimator大部分属性见下图(引用自扔物线官网博客)

image.png

主要说明一下tanslationX,translationXBy,x,xBy的区别:

tanslationX:相对View左上角平移一个绝对位置,多次调用View的位置不变

translationXBy:相对View左上角平移一个相对位置,多次调用View的位置一直变化

x和xBy:只是把相对View的左上角换成了相对X轴top和left坐标,其他同上。

可以把动画封装成方法,多次调用该方法就能明显看到差别。

二、ObjectAnimator属性动画

用三份代码来说明ObjectAnimator的使用,三份代码的实现效果完全一样,让ImageView沿X轴平移200像素。要注意自定义View的使用以及Kotlin和Java代码的区别。

第一份:Kotlin代码,使用Android的ImageView,xml代码中只有一个ImageView。

var objectAnimator = ObjectAnimator.ofFloat(iv, "translationX", 200f)
objectAnimator.start() 

第二份:Java代码自定义View,绘制位图,然后平移。

自定义的java代码:

 public class ObjectView extends View {

    private float toX;   //注意这个自定义属性
    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Bitmap bitmap;

    public ObjectView(Context context) {
        super(context);
    }

    public ObjectView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        bitmap = getAvatar(300);
    }

    public void setToX(float toX) {  //一定要给set方法,不然不生效
        this.toX = toX;
        invalidate();   //没有invalidate也不生效
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawBitmap(bitmap, toX, 0f, paint);  //绘制的起点用自定义的属性toX
    }

    /**
     * 获取符合尺寸宽度的位图
     *
     * @param width 位图的目标宽度
     */
    private Bitmap getAvatar(int width) {
        //获取options对象
        BitmapFactory.Options options = new BitmapFactory.Options();
        //配置中设置属性获取图片的长宽设置
        options.inJustDecodeBounds = true;
        //对图片进行解码
        BitmapFactory.decodeResource(getResources(), R.drawable.girl, options);
        //取消获取图片的长宽的设置
        options.inJustDecodeBounds = false;
        options.inDensity = options.outWidth;  //实际宽度
        options.inTargetDensity = width;   //目标宽度
        return BitmapFactory.decodeResource(getResources(), R.drawable.girl, options);
    }

} 

调用的代码(xml中引用这个自定义View,id为iv):

var objectAnimator = ObjectAnimator.ofFloat(iv, "toX", 200f)  //用自定义的属性toX
objectAnimator.start() 

效果展示

2022.01.21.15.27.59.gif

第三份:kotlin代码自定义View,绘制位图,然后平移。

主要的代码:

private val paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
private var bitmap: Bitmap = getAvatar(300)
 var toX: Float = 0f
    set(value) {   //set方法要放在设置动画的属性下面,不然不生效
        field = value
        Log.d(TAG, "field的值:${value}")  //打印日志
        invalidate()
    }

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.drawBitmap(bitmap, toX, 0f, paint)
} 

调用代码同上,效果同上。输出的Log日志为:

 D/ObjectView2: field的值:0.13704896
 D/ObjectView2: field的值:2.642113
 D/ObjectView2: field的值:8.224535
 D/ObjectView2: field的值:16.132957
 D/ObjectView2: field的值:27.103138
 D/ObjectView2: field的值:40.377502
 D/ObjectView2: field的值:54.600952
 D/ObjectView2: field的值:71.09682
 D/ObjectView2: field的值:88.506294
 D/ObjectView2: field的值:106.27905
 D/ObjectView2: field的值:122.83507
 D/ObjectView2: field的值:139.71478
 D/ObjectView2: field的值:155.33917
 D/ObjectView2: field的值:168.45471
 D/ObjectView2: field的值:180.28174
 D/ObjectView2: field的值:189.57118
 D/ObjectView2: field的值:195.73195
 D/ObjectView2: field的值:199.33728
 D/ObjectView2: field的值:200.0 

总结

1、属性动画是对View的某条属性做修改然后产生了动画效果。

2、会自动调用set方法。

3、通过日志可以看出,ObjectAnimator是自动修改参数。

三、AnimatorSet组合动画

可以对多个属性动画进行组合,比如下面二个自定义属性:

var viewX = ObjectAnimator.ofFloat(iv, "toX", 400f)
var viewY = ObjectAnimator.ofFloat(iv, "toY", 800f)
var animatorSet = AnimatorSet()
animatorSet.playTogether(viewX, viewY)   //一起执行
//animatorSet.playSequentially(viewX, viewY)  //按顺序执行
animatorSet.duration=600
animatorSet.startDelay = 1500
animatorSet.interpolator = AccelerateDecelerateInterpolator ()
animatorSet.start()
//添加监听器
animatorSet.addListener(object :Animator.AnimatorListener{
    override fun onAnimationStart(animation: Animator?) {
        TODO("Not yet implemented")
    }

    override fun onAnimationEnd(animation: Animator?) {
        iv.visibility = View.GONE
    }

    override fun onAnimationCancel(animation: Animator?) {
        TODO("Not yet implemented")
    }

    override fun onAnimationRepeat(animation: Animator?) {
        TODO("Not yet implemented")
    }

}) 

四、PropertyValuesHolder和Keyframe的使用

PropertyValuesHolder单独使用的情况:

val toXHolder = PropertyValuesHolder.ofFloat("toX",200f)
var animator = ObjectAnimator.ofPropertyValuesHolder(iv, toXHolder) 

这样看似乎意义不大,但一般PropertyValuesHolder和Keyframe(关键帧)一起使用,可以实现精细控制:

val keyframeX1 = Keyframe.ofFloat(0f, 0f)   //定义关键帧
val keyframeX2 = Keyframe.ofFloat(0.2f, IMAGE_TRANSLATION_X * 0.2f)
val keyframeX3 = Keyframe.ofFloat(0.4f, IMAGE_TRANSLATION_X * 0.4f)
val keyframeX4 = Keyframe.ofFloat(0.6f, IMAGE_TRANSLATION_X * 0.6f)
val keyframeX5 = Keyframe.ofFloat(0.8f, IMAGE_TRANSLATION_X * 0.8f)
val keyframeX6 = Keyframe.ofFloat(1f, IMAGE_TRANSLATION_X * 1f)
val toXHolder = PropertyValuesHolder.ofKeyframe(   //一起使用
    "toX",
    keyframeX1,
    keyframeX2,
    keyframeX3,
    keyframeX4,
    keyframeX5,
    keyframeX6
)
var animatorX = ObjectAnimator.ofPropertyValuesHolder(iv, toXHolder)

val keyframeY1 = Keyframe.ofFloat(0f, 0f)  //定义关键帧
val keyframeY2 = Keyframe.ofFloat(0.2f, getYValue(IMAGE_TRANSLATION_X * 0.2f))
val keyframeY3 = Keyframe.ofFloat(0.4f, getYValue(IMAGE_TRANSLATION_X * 0.4f))
val keyframeY4 = Keyframe.ofFloat(0.6f, getYValue(IMAGE_TRANSLATION_X * 0.6f))
val keyframeY5 = Keyframe.ofFloat(0.8f, getYValue(IMAGE_TRANSLATION_X * 0.8f))
val keyframeY6 = Keyframe.ofFloat(1f, getYValue(IMAGE_TRANSLATION_X * 1f))
val toYHolder = PropertyValuesHolder.ofKeyframe(  //一起使用
    "toY",
    keyframeY1,
    keyframeY2,
    keyframeY3,
    keyframeY4,
    keyframeY5,
    keyframeY6
)
var animatorY = ObjectAnimator.ofPropertyValuesHolder(iv, toYHolder)

var animatorSet = AnimatorSet()
animatorSet.playTogether(animatorX, animatorY)
animatorSet.duration = 800
animatorSet.startDelay = 1500
animatorSet.start() 

五、Interpolator插值器

AccelerateDecelerateInterpolator 默认的插值器。缓慢启动,中间过程移动非常快,缓慢结束,适用于在屏幕上启动和结束的动画。

AccelerateInterpolator 缓慢启动,在移动过程中慢慢加快,适用于在屏幕上启动,屏幕外结束的动画。

AnticipateInterpolator 类似于 AccelerateInterpolator ,但是有一个导致它以负值启动的弹力,一次一个物体向下移动的动画会先向上移动,然后再向下移动,可以理解为把一个物体放在一个弹弓上向后拉然后弹射出去。

AnticipateOvershootInterpolator 前半部分与AnticipateInterpolator 相同,到达终点后会继续以这个速度继续向原来的方向运动一段距离之后再回到终点。

BounceInterpolator类似于一个有弹性的小球掉到地上,然后又弹回来,再落下去,但是每次弹起来的高度会越来越低,直到最终静止在地面上。

CycleInterpolator 运动曲线类似于 sin 函数,起始位置相当于 sin 零点,终点相当于 sin 的顶点。比如有一个处于中心的一个小球,你的目标是把它移到下方的一个位置,这个位置暂称之为终点,那这个小球的运动轨迹会是 : 先向下移动到终点,然后再向上移动到初始位置,然后在向上移动 初始位置到终点 同样的距离,然后再返回到初始位置。这是一个周期,至于动画会执行几个周期,这取决于你在构造函数中传入的浮点参数,这个参数就是周期。

DecelerateInterpolator以最大速度启动,结束时放慢速度,适用于在屏幕之外启动,在屏幕内结束的动画。

FastOutLinearInInterpolator 在 support 库中,这个插值器使用一个查找表来描述位移。简单的说,它的启动过程像 AccelerateInterpolator ,结束过程像 LinearInterpolator 。

FastOutSlowInInterpolator 在 support 库中,这个插值器也是使用一个查找表来描述位移。它的启动过程像 AccelerateInterpolator ,结束过程像 DecelerateInterpolator 。

LinearInterpolator 以一个恒定的速度变化的动画。

LinearOutSlowInInterpolator 在 support 库中,这个插值器也是使用一个查找表来描述位移。它的启动过程像 LinearInterpolator ,结束过程像 DecelerateInterpolator 。

OvershootInterpolator 类似于 AccelerateInterpolator,但是有一个是它不能立即停到终点位置的力,使它超过终点位置,然后在弹回终点,就想撞到了弹弓上。

PathInterpolator 这个是在 Android 5.0(API level 21) 中加入的,这个插值动画基于你穿给它的一个路径。X 坐标表示时间,Y 坐标代表返回的浮点值。如果你任意给定的 X 值只对应一个 Y 值,并且没有间断(即一段给定的 0~1 之间的 X 值对应一段连续的 Y 值),那么所有种类的路径都可以被支持。

六、TypeEvaluator

官方提供的TypeEvaluator的有ArgbEvaluator,FloatEvaluator,FloatArrayEvaluator,IntArrayEvaluator,IntEvaluator,PointFEvaluator,RectEvaluator

看下FloatEvaluator的源码,可以模仿官方的写法做很多事情。源码如下:

public class FloatEvaluator implements TypeEvaluator<Number> {
    ...
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat); //开始值+百分比*差值
    }
} 

自定义Evaluator实现文字滚动效果模拟抽奖

自定义Evaluator的代码

class StringCustomEvaluator : TypeEvaluator<String> {
    private val nameList = listOf("张三", "李四", "王五", "赵六", "田七", "胡八", "周九", "朱十")

    override fun evaluate(fraction: Float, startValue: String, endValue: String): String {
        val startIndex = nameList.indexOf(startValue)
        val endIndex = nameList.indexOf(endValue)
        val currentIndex = (startIndex + (endIndex - startIndex) * fraction).toInt()
        return nameList[currentIndex]
    }
} 

自定义View的代码

class ShowTextView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) {
    private val paint = TextPaint(Paint.ANTI_ALIAS_FLAG)
    private val fontMetrics = Paint.FontMetrics()
     var name: String = "张三"   //属性前面不要加private
        set(value) {
            field = value
            invalidate()
        }

    init {
        paint.getFontMetrics(fontMetrics)
        paint.textAlign = Paint.Align.CENTER
        paint.textSize = TEXT_SIZE
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawText(
            name,
            width / 2f,
            height / 2f - (fontMetrics.ascent + fontMetrics.descent) / 2f,
            paint
        )
    }
} 

动画代码

val animator = ObjectAnimator.ofObject(ctv, "name", StringCustomEvaluator(), "张三", "朱十")
animator.duration = 1000
animator.start() 

效果展示

2022.02.03.10.33.10.gif

插值器部分引用这篇博客,感谢原作者!

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接扫码免费领取↓↓↓

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值