美文网首页
【Android】自定义View:属性动画

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

作者: 蜗牛是不是牛 | 来源:发表于2023-02-21 16:20 被阅读0次

    一、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

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

    相关文章

      网友评论

          本文标题:【Android】自定义View:属性动画

          本文链接:https://www.haomeiwen.com/subject/dbcukdtx.html