关于Android中动画探究(一)视觉动画

883 阅读5分钟

关于Android中动画探究

视图动画(补间)

以下为Android常用的视图动画类,xml动画这里不做详解。

基本使用

  • ScaleAnimation(缩放动画)

    可变化控件的大小

        /**
         * Scale动画里x、Y指这个控件的宽高百分比,取值0~1
         * 
         * @param fromX 动画开始的X。
         * @param toX 动画结束的X。
         * @param fromY 动画开始的y。
         * @param toY 动画结束的y。
         *
         * @param 动画开始时X坐标类型,可以理解为从控件的哪个位置开始,下方动画同值。
    	 *		  取值范围为ABSOLUTE(绝对位置)、RELATIVE_TO_SELF(以自身宽或高为参考)、
    	 *		  RELATIVE_TO_PARENT(以父控件宽或高为参考)。
         * @param pivotXValue 取值0~1(1 is 100%) 
         * @param pivotYType 动画开始时坐标类型
         *        取值范围为ABSOLUTE(绝对位置)、RELATIVE_TO_SELF(以自身宽或高为参*考)、
         *        RELATIVE_TO_PARENT(以父控件宽或高为参考)。
         * @param pivotYValue 取值0~1(1 is 100%) 
         *        下面代码动画表示:从控件的左上角位置开始,放大1.5倍
         */
            val animation2 = ScaleAnimation(1f, 1.5f, 1f, 1.5f, ScaleAnimation.RELATIVE_TO_SELF, 0f, ScaleAnimation.RELATIVE_TO_SELF, 0f)
    animation2.duration = 700
    
  • RotateAnimation

      /**
     	 * 旋转动画
         * @param fromDegrees 开始前角度 0代表当前无角度
         * @param toDegrees   动画结束角度 
         * @param pivotXType  
         * @param pivotXValue 
         * @param pivotYType 
         * @param pivotYValue
         *        下面代码动画表示:从控件的中心旋转360°
         */
    val animation3 = RotateAnimation(0f, 360f, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f)
    
  • TranslateAnimation

    /**
     * 平移动画
     * @param fromXDelta 动画前X坐标
     * @param toXDelta   动画结束X坐标
     * @param fromYDelta 动画前Y
     * @param toYDelta   动画结束Y坐标
     *        下面代码动画表示:从当前控件的起始位置0,向上Y移动100像素
     */
     var animation1 = TranslateAnimation(0f, 0f, 0f, -100f)
    
  • AlphaAnimation

      /**
         * 透明度动画
         * @param fromAlpha 动画前View的通明度
         * @param toAlpha 动画结束时View的通明度
         */
    val animation4 = AlphaAnimation(1f, 0.1f)
    

进阶用法

AnimationSet组合动画,对View运行组合动画,这里需要注意AnimationSet中添加的动画是一起执行的,不能设定动画的先后执行顺序,同样也不能在动画的过程中进行操作。

/**
     * AnimationSet 有两个构造函数
     * AnimationSet(Context context, AttributeSet attrs) // 传入一组动画属性attr(执行时间等)
     * AnimationSet(boolean shareInterpolator) // 是否共用插值器
     * 注意事项:
     * 1.AnimationSet设定duration值(执行时长),会使集合中animation duration属性值失效
     * 2.AnimationSet设定repeatCount值(重复次数)无效,只会取子动画设定值
     * 3.AnimationSet设定repeatMode值(REVERSE从结束位置执行反动画,RESTART重新执行动画),会使子动画repeatMode无效
     */

/**  AnimationSet源码
*/
class AnimationSet extends Animation {
    
     @Override
    public long getDuration() {
        final ArrayList<Animation> animations = mAnimations;
        final int count = animations.size();
        long duration = 0;
        boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
        if (durationSet) {  //如果AnimationSet有设置duration,则取AnimationSet的duration
            duration = mDuration;
        } else { // 取子动画中最大的duration
            for (int i = 0; i < count; i++) {
                duration = Math.max(duration, animations.get(i).getDuration());
            }
        }
        return duration;
    }
}

//使用方式
val animationTest = AnimationSet(true)    
animationTest.addAnimation(animation1)
animationTest.addAnimation(animation2)
animationTest.addAnimation(animation3)
animationTest.addAnimation(animation4)
animationTest.duration = 900
animationTest.interpolator = LinearInterpolator() //线性插值器

视图动画原理解析

    /**
     * view.startAnimation(animation) view与animation产生联系 
     * 初始化动画的启动设定为第一祯,调用reset方法,也就是说不管动画是否在执行过程,再次执行动画依然回到第一	 
     * 帧。接着invalidate开始重绘,UI重绘机制这里不做详解,我们直接找到getAnimation()调用的地方		 
     * view.draw(Canvas canvas, ViewGroup parent, long drawingTime)
     */
    public void startAnimation(Animation animation) {
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
        setAnimation(animation);
        invalidateParentCaches();
        invalidate(true);
    }

	public void setAnimation(Animation animation) {
        mCurrentAnimation = animation;
        if (animation != null) {
            if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
                    && animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
                animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
            }
            animation.reset();
        }
    }
	

/**View类*/
@UiThread 
public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
	 boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
        // ...省略代码
        final Animation a = getAnimation();
        if (a != null) {
            //处理视图活动Animation
            more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
        }
       
     }

	 private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
            Animation a, boolean scalingRequired) {
        //.. 
        final Transformation t = parent.getChildTransformation();
        boolean more = a.getTransformation(drawingTime, t, 1f);
        if (more) {
            // 判断动画是否过期,如果没有过期,则去执行parent的invalidate,会重新执行child重绘,
            // 这里指的是重绘某一个区域,并没有改变View在父类ViewGroup的位置,这也是为什么点击位置在原地的原因。
            // 绘制下一帧动画,16ms一帧
        	parent.invalidate(left, top, left + (int) (region.width() + .5f),
                        top + (int) (region.height() + .5f));     
        }
     }
}


/**Animatio类*/
public abstract class Animation implements Cloneable {
  public boolean getTransformation(long currentTime, Transformation outTransformation) {
        if (mStartTime == -1) {
            mStartTime = currentTime;
        }

        final long startOffset = getStartOffset();
        final long duration = mDuration;
        float normalizedTime;  
        if (duration != 0) {
            // 先初始化动画的启动进度,比如1个4s的动画,计算现在动画进度百分比
            normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
                    (float) duration;
        } else {
            normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
        }
      	//判断动画是否过期
        final boolean expired = normalizedTime >= 1.0f || isCanceled(); 
        mMore = !expired;
        if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
        if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
            if (!mStarted) {
                fireAnimationStart();
                mStarted = true;
                if (NoImagePreloadHolder.USE_CLOSEGUARD) {
                    guard.open("cancel or detach or getTransformation");
                }
            }
            if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
            if (mCycleFlip) {
                normalizedTime = 1.0f - normalizedTime;
            }

            final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime); 							// 通过插值器计算出平稳值
            //执行动画,调用子类ScaleAnimation、RotateAnimation的实现方法,这里说明
            //我们也可以自定义Animation,通过重写applyTransformation方法,执行自己的动画逻辑
            applyTransformation(interpolatedTime, outTransformation); 
        }
        if (expired) { // 如果过期,判断是否有设置repeated属性,没有则调用动画结束方法
            if (mRepeatCount == mRepeated || isCanceled()) {
                if (!mEnded) {
                	// ... 动画结束
                    fireAnimationEnd();
                }
            } else {
                if (mRepeatCount > 0) {
                    mRepeated++;
                }

                if (mRepeatMode == REVERSE) {
                    mCycleFlip = !mCycleFlip;
                }

                mStartTime = -1;
                mMore = true;
		// 判断动画RepeatCount,是否重复执行
                fireAnimationRepeat();
            }
        }
        return mMore;
    }
}

视图动画的局限性

  1. 只能作用于view,但有时需求不是对于整个view的,而只是对view的某个属性的,例如颜色的变化,也无法对非View的对象进行动画处理。
  2. 只改变了view的视觉效果而已,修改了视图绘制的地方,例如控件的点击,还是动画前控件的位置。
  3. 动画效果固定,动画类型只有四种,缩放,平移,旋转,透明度的基本动画,无法对其他属性进行操作。
  4. 动画虽然可以添加监听,但是动画开始后无法对动画的执行过程进行控制。

视图动画的优点

  1. 使用方便,能满足基础动画效果