美文网首页
带你走一波Android自定义Animator属性动画相关事项(

带你走一波Android自定义Animator属性动画相关事项(

作者: 未扬帆的小船 | 来源:发表于2019-12-06 09:38 被阅读0次

    一、简介

    image.png

    如上图所示:android动画分类大致有两种一种是View动画一种是转场动画

    帧动画:将图片一张一张按顺序播放,展现出动画效果。

    补间动画:实现动画alpha(淡入淡出),translate(位移),scale(缩放大小),rotate(旋转)等效果,一般采用xml文件形式。

    属性动画:(重点)它是对于对象属性的动画。补间动画的内容,都可以通过属性动画实现。

    这里我们就不讲帧动画补间动画,这两个大家可以自己百度一下用法。(另外这篇文章中的动画都是在代码中实现的,如果要看xml的使用方法,可以看看Android 动画使用 scale、alpha、translate、rotate、set
    这篇其他人写的这篇文章。

    二、属性动画

    基本使用 (ViewPropertyAnimator)

    imag_view.animate().run {
    translationX(400f) //设置左移
    duration = 1000//设置动画运行的时间
    setInterpolator(LinearInterpolator()) //设置线性插值器
    }
    
    gifeditor_20191202_151011.gif
    上面是最基本的使用,api提供的有移动旋转缩放透明,看下面的api:
    image.png

    上面的api中可以看到 都存在绝对相对(方法后面-by)的方法,其中绝对的方法以上面的代码为例子,区别是:
    translationX(400f)代表将translationX变成400
    translationXBy(400f)代表将translationX增加400

    这么多api就不做逐一展示了。

    ObjectAnimator

    1. 基本的使用方式:
    1. 用 ObjectAnimator.ofXXX() 创建 ObjectAnimator 对象;
    2. 添加时长、差值器等各种参数
    3. 用 start() 方法执行动画。
    ObjectAnimator.ofFloat(imag_view,View.ROTATION,0f,180f).run {
        duration = 1000
        interpolator = LinearInterpolator()
        start()
    }
    
    gifeditor_20191202_164532.gif
    上面是对一个系统提供的View进行动画展示,主要方法是ObjectAnimator.ofFloat(imag_view,View.ROTATION,0f,180f)
    第一个参数:传入要进行属性动画的view
    第二个参数:要变化的属性值,这里是传入View.ROTATION,也就是"rotation"
    第三个参数:属性起始值
    第四个参数:属性结束值
    后面还可以加入多个参数值,从第三个参数开始到最后第n个参数,表示属性开始 ->中间值->中间值.... ->结束值
    注意一点:并非所有的属性都是可以有set get方法,可以进行属性动画。
    2. 自定义View动画

    自定义View属性动画以及使用的步骤:

    1. 为要改变的属性添加setter/getter方法
    2. setter方法中调用invalidate()使其重新绘画
    3. 在onDraw()中根据改变的属性绘画出你要的效果
    4. 使用的时候跟ObjectAnimator基本使用方式一致

    大致的模板代码

    
    class CircleView : View {
      //提供给外面改变的属性值 这里使用kotlin语法 实际上它默认已经实现了setter/getter方法
    // 但是这里个set方法我们要改写一下 记得调用 invalidate()
        var progress: Float = 0f
            set(value) {
                field = value
                invalidate()
            }
    
        @RequiresApi(Build.VERSION_CODES.M)
        override fun onDraw(canvas: Canvas?) {
            super.onDraw(canvas)
            //省略......
            canvas!!.drawArc(rectF, 135f, progress * 2.7f, false, paint)
            //省略......
            canvas.drawText("${progress.toInt()}%", centerX, centerY+40, paint)
        }
    
    }
    

    真正的实现与调用

    class CircleView : View {
    
        val paint = Paint(Paint.ANTI_ALIAS_FLAG)
        val rectF = RectF()
    
        init {
            paint.run {
                textSize = dpToPixel(50f)
                textAlign = Paint.Align.CENTER
            }
        }
    
        var radius = dpToPixel(120f)
    
        var progress: Float = 0f
            set(value) {
                field = value
                invalidate()
            }
    
        constructor(context: Context) : super(context)
        constructor(context: Context, attributeSet: AttributeSet?) : super(context, attributeSet)
    
    
        @RequiresApi(Build.VERSION_CODES.M)
        override fun onDraw(canvas: Canvas?) {
            super.onDraw(canvas)
            var centerX = width / 2f
            var centerY = height / 2f
            //画弧形进度条
            paint.run {
                color = context.getColor(R.color.colorAccent)
                style = Paint.Style.STROKE
                strokeCap = Paint.Cap.ROUND
                strokeWidth = dpToPixel(20f)
            }
            rectF.set(centerX - radius, centerY - radius, centerX + radius, centerY + radius)
            canvas!!.drawArc(rectF, 135f, progress * 2.7f, false, paint)
    
            //画百分比的数值
            paint.run {
                color = Color.BLACK
                style = Paint.Style.FILL
            }
            canvas.drawText("${progress.toInt()}%", centerX, centerY+40, paint)
        }
    
    }
    
    //调用代码
    ObjectAnimator.ofFloat(circle_view,"progress",0f,80f).run {
      duration = 2000
      interpolator = OvershootInterpolator() //插值器 超过结束值后再回弹
      repeatCount = INFINITE  // 重复次数-无限循环
      repeatMode = RESTART //重复模式-重新开始
      start()
    }
    
    gifeditor_20191202_172002.gif

    这里设置了OvershootInterpolator插值器,使得它可以超过后回弹回来。这里可以使用PropertyValuesHolders.ofKeyframe()做到类似效果,可以翻到下面对应内容看一下

    ValueAnimator

    这个是ObjectAnimator的父类,这个我并没怎么接触,后期再补充吧

    2. 设置监听器

    • 设置ObjectAnimator的监听器
    监听器类型.png
    //添加停止的监听器
    addPauseListener( object : Animator.AnimatorPauseListener{
      override fun onAnimationPause(animation: Animator?) {
        Log.i("MainActivity","addPauseListener -- onAnimationPause ------------------------------------")
      }
      override fun onAnimationResume(animation: Animator?) {
        Log.i("MainActivity","addPauseListener -- onAnimationResume ------------------------------------")
      }
    })
    //添加更新的监听器
    addUpdateListener(object :ValueAnimator.AnimatorUpdateListener{
      override fun onAnimationUpdate(animation: ValueAnimator?) {
    //    Log.i("MainActivity","addUpdateListener -- onAnimationUpdate ------------------------------------")
        }
      })
    //添加监听器
    addListener(object : Animator.AnimatorListener{
      override fun onAnimationRepeat(animation: Animator?) {
        Log.i("MainActivity","AnimatorListener -- onAnimationRepeat------------------------------------")
        }
       override fun onAnimationEnd(animation: Animator?) {
        Log.i("MainActivity","AnimatorListener -- onAnimationEnd------------------------------------")
        }
      override fun onAnimationCancel(animation: Animator?) {
      Log.i("MainActivity","AnimatorListener -- onAnimationCancel------------------------------------")
      }
      override fun onAnimationStart(animation: Animator?) {
          Log.i("MainActivity","AnimatorListener -- onAnimationStart------------------------------------")
         }
      })
    }
    

    设置点击事件监听

    R.id.start_object_animator ->{
                    省略...
                    mObjectAnim.start()
                    Log.i("MainActivity","点击开始状态 --${!mObjectAnim.isStarted}")
                }
    
                R.id.end_object_animator ->{
                    mObjectAnim.end()
                    Log.i("MainActivity","点击结束状态 --${!mObjectAnim.isRunning}")
    
                }
                R.id.cancel_object_animator ->{
                    mObjectAnim.cancel()
                    Log.i("MainActivity","点击取消状态 -- ")
                }
                R.id.pause_object_animator->{
                    if (mObjectAnim.isRunning) {
                        mObjectAnim.pause()
                        Log.i("MainActivity","点击暂停状态 --${mObjectAnim.isPaused}")
                    }
                }
                R.id.reverse_object_animator->{
                    Log.i("MainActivity","点击反向状态 --")
                    mObjectAnim.reverse()
                }
                R.id.resume_object_animator->{
                    mObjectAnim.resume()
                    Log.i("MainActivity","点击继续执行 --")
                }
    

    点击顺序 (这里点击开始按钮是重新初始化动画,请大家不要误解)
    开始start -> 暂停pause ->继续执行resume->结束end
    开始start->取消cancel
    开始start->结束end

    效果图.gif
    MainActivity: AnimatorListener -- onAnimationStart------------------------------------
    MainActivity: 点击开始状态 --false
    MainActivity: addPauseListener -- onAnimationPause ------------------------------------
    MainActivity: 点击暂停状态 --true
    MainActivity: addPauseListener -- onAnimationResume ------------------------------------
    MainActivity: 点击继续执行 --
    MainActivity: AnimatorListener -- onAnimationEnd------------------------------------
    MainActivity: 点击结束状态 --true
    MainActivity: AnimatorListener -- onAnimationStart------------------------------------
    MainActivity: 点击开始状态 --false
    MainActivity: AnimatorListener -- onAnimationCancel------------------------------------
    MainActivity: AnimatorListener -- onAnimationEnd------------------------------------
    MainActivity: 点击取消状态 -- 
    MainActivity: AnimatorListener -- onAnimationStart------------------------------------
    MainActivity: 点击开始状态 --false
    MainActivity: AnimatorListener -- onAnimationEnd------------------------------------
    MainActivity: 点击结束状态 --true
    

    上面是点击产生的log日志,其中有个addUpdateListener监听我没有打印信息,因为动画运行中就会不停打印出来,所以就没有打印出来了。
    可以看到所有的状态都是可以有回调方法监听的。

    这里有个方法要注意一下 cancel()end()这个两个方法。

    如果动画是已经结束了end()的时候,就不会有回调onAnimationCancelonAnimationEnd两个监听方法了,看一下ValueAnimator源码中有体现了

        @Override
        public void cancel() {
    关键代码1
            if (mAnimationEndRequested) {
                return;
            }
            if ((mStarted || mRunning) && mListeners != null) {
                if (!mRunning) {
                    notifyStartListeners();
                }
                ArrayList<AnimatorListener> tmpListeners =
                        (ArrayList<AnimatorListener>) mListeners.clone();
                for (AnimatorListener listener : tmpListeners) {
    关键代码2
                      listener.onAnimationCancel(this);
                }
            }
    关键代码3
            endAnimation();
        }
        private void endAnimation() {
    关键代码4
            mAnimationEndRequested = true;
      省略......
                for (int i = 0; i < numListeners; ++i) {
    关键代码5
                    tmpListeners.get(i).onAnimationEnd(this, mReversing);
                }
            }
    省略......
        }
    

    关键代码1:mAnimationEndRequested == true则不走下面的逻辑,设置true是在关键代码4中设置的,也就是说当动画结束调用了endAnimation()就不会调用到两个回调onAnimationCancelonAnimationEnd
    关键代码2跟3跟5:就是动画未结束,所以调用了回调onAnimationCancel()再调用回调onAnimationEnd()

    小结:当调用cancel()的时候,动画未结束时则回调onAnimationCancel()onAnimationEnd(),当动画结束时,则不会调用任何监听回调方法

    end方法有时候会回调两个回调 分别是onAnimationStartonAnimationEnd,看一下ValueAnimator源码中的逻辑

        public void end() {
            if (Looper.myLooper() == null) {
                throw new AndroidRuntimeException("Animators may only be run on Looper threads");
            }
            if (!mRunning) {
                // Special case if the animation has not yet started; get it ready for ending
                startAnimation();
                mStarted = true;
            } else if (!mInitialized) {
                initAnimation();
            }
            animateValue(shouldPlayBackward(mRepeatCount, mReversing) ? 0f : 1f);
            endAnimation();
        }
    

    从上面的逻辑可以看到,当调用到!mRunning == true的时候,会调用startAnimation()导致回调多一个onAnimationStart方法。

    小结: 当调用end()的时候,如果动画处于运行中,则回调onAnimationEnd,如果不处于运行中,则回调onAnimationStartonAnimationEnd

    • 设置ViewPropertyAnimator的监听器
    image.png
    相比ObjectAnimator的监听器,这里的ViewPropertyAnimator多了两个回调方法

    withStartActionwithEndAction分别在开始跟结束调用,但是只会被调用一次。
    setListener中的onAnimationRepeat回调不会被调用,因为ViewPropertyAnimator不支持重复
    其它的回调跟ObjectAnimator一致

    三. 属性动画组合

    image.png
    • ViewPropertyAnimator
      改变尺寸同时改变透明度,下面这种写法是几种动画一块运行的
    R.id.btn_viewProperty_mulity->{
      if (isSelect) {
        imag_view.animate().scaleX(1.5f).scaleY(1.5f).alpha(0f).duration =2000
      }else{
        imag_view.animate().scaleX(1.0f).scaleY(1.0f).alpha(1f).duration =2000
      }
       isSelect = !isSelect
    }
    
    gifeditor_20191203_163831.gif
    • PropertyValuesHolder
      ObjectAnimator使用时则是要通过PropertyValuesHolder来实现
      上面的代码可以用
    R.id.btn_viewProperty_mulity->{
      if (isSelect) {
        var propertyValueHolder1 = PropertyValuesHolder.ofFloat("scaleX",1f,1.5f)
        var propertyValueHolder2 = PropertyValuesHolder.ofFloat("scaleY",1f,1.5f)
        var propertyValueHolder3 = PropertyValuesHolder.ofFloat("alpha",1f,0f)
        ObjectAnimator.ofPropertyValuesHolder(imag_view,propertyValueHolder1,propertyValueHolder2,propertyValueHolder3)
        .setDuration(2000).start()
      }else{
        var propertyValueHolder1 = PropertyValuesHolder.ofFloat("scaleX",1.5f,1f)
        var propertyValueHolder2 = PropertyValuesHolder.ofFloat("scaleY",1.5f,1f)
        var propertyValueHolder3 = PropertyValuesHolder.ofFloat("alpha",0f,1f)
        ObjectAnimator.ofPropertyValuesHolder(imag_view,propertyValueHolder1,propertyValueHolder2,propertyValueHolder3)
        .setDuration(2000).start()
        }
        isSelect = !isSelect
    }
    
    • PropertyValuesHolders.ofKeyframe()同一个属性拆分
      ofKeyframe (关键帧),可以把同一个动画属性拆分成多个阶段
    var keyframe = Keyframe.ofFloat(0f,0f)//关键帧 0刚才开时的时候
    var keyframe1 = Keyframe.ofFloat(0.5f,95f)//关键帧 0.5进行到一半的时候
    var keyFrame2 = Keyframe.ofFloat(1f,80f)//关键帧1最后的时候
    var holder = PropertyValuesHolder.ofKeyframe("progress", keyframe, keyframe1, keyFrame2)
    ObjectAnimator.ofPropertyValuesHolder(circle_view,holder).setDuration(3000).start()
    
    gifeditor_20191203_175839.gif
    实现类似interpolator = OvershootInterpolator()插值器超过结束值后再回弹的效果
    • AnimatorSet
      也是组合动画的,可以让多个动画配合执行,不过它可以让动画先后有序执行~~
      playTogether(同时执行)、playSequentially(顺序执行)
      精确配置顺序with(),before(),after()
    示例代码:
    var animator1 = ObjectAnimator.ofFloat(imag_view,"alpha",0f,1f)
    animator1.interpolator = AccelerateDecelerateInterpolator()
    var animator3 = ObjectAnimator.ofFloat(imag_view,"scaleX",0f,1f)
    var animator2 = ObjectAnimator.ofFloat(imag_view,"scaleY",0f,1f)
    var animator4 = ObjectAnimator.ofFloat(imag_view,"translationX",0f,200f)
    animator4.interpolator = LinearInterpolator()
    AnimatorSet().apply {
      playTogether(animator2,animator3)
      playSequentially(animator1,animator4)
      duration = 2000
      start()
    }
    
    gifeditor_20191203_172107.gif
    解释一下上面的代码
    上面使用 AnimatorSet将多个ObjectAnimator放在一块运行,并且playTogether(animator2,animator3) 代表一起运行
    playSequentially(animator1,animator4) 代表先后顺序运行
    所以就有了下面的效果图 (x轴、y轴方向放大跟透明度由0-1)是一起的,然后在x轴方向移动。

    上面的播放逻辑也可以使用精确配置顺序with(),before(),after()来实现

    AnimatorSet().apply {
      play(animator1).with(animator2).with(animator3).before(animator4)
      duration = 2000
      start()
     }
    

    注意:每个传入给AnimatorSetanimator可以自己定义自己的动画运行时间、差值器等,但是如果在AnimatorSet设置了运行时间的话则以在AnimatorSet设置的为准。

    相关文章

      网友评论

          本文标题:带你走一波Android自定义Animator属性动画相关事项(

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