Android视图动画原理解析

概述

视图动画支持4种动画效果,分别是translate,scale,rotate,alpha。
此外帧动画也属于视图动画。

基本使用方式

视图动画的实例化方式:
1.从xml加载:

Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.scale_animation);

scale_animation.xml:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <scale xmlns:android="http://schemas.android.com/apk/res/android"
        android:fromXScale="0.0"
        android:toXScale="1.0"
        android:fromYScale="0.0"
        android:toYScale="1.0"
        android:pivotX="50"
        android:pivotY="50"
        android:duration="700"
        />
</set>

2.直接java代码初始化

Animation animation = new ScaleAnimation(0,1,0,1,Animation.ABSOLUTE,50,Animation.ABSOLUTE,50);

视图动画使用:
视图动画怎么使用我这里就不详细说了,这里推荐一篇写的很好的博客,有需要的可以点进去看看:参考文章

优缺点

这里概括了几个视图动画的优缺点:
优点:

  • 可以实现基本的缩放,旋转,平移,透明度变换,以及这四种变换的组合动画

缺点:

  • 不能实现复杂动画效果。
  • 可以实现组合动画(AnimationSet),但是组合动画只能同时播,不能控制动画播放的先后顺序
  • 不能监听动画的执行过程,只能监听到动画开始,结束和重复事件。

原理解析

本文着重介绍视图动画的实现原理,结合分析源码讲解。

在分析源码前,先用一句话概括视图动画的原理:
当视图开启动画时,会触发视图重绘,在draw方法拿到该视图关联的Animation对象,把当前时间和一个存有Matrix的Transformation对象传给Animation,Animation会改变Transformation对象的Matrix,然后回传给视图,并且告诉视图是否动画已经结束,视图根据Matrix绘制图像,并且判断是否动画已经执行结束,如果没有就会继续调用invalidate方法重绘视图,然后又继续刚才的步骤,直到动画结束。

源码分析

下面是源码分析,直接看注释讲解:
1.开启动画。

/**
 * Start the specified animation now.
 *
 * @param animation the animation to start now
 */
public void startAnimation(Animation animation) {
     animation.setStartTime(Animation.START_ON_FIRST_FRAME);
     //设置关联动画
     setAnimation(animation);
     invalidateParentCaches();
     //触发视图重绘
     invalidate(true);
}

2.执行到View的draw方法,注意该方法的参数带有当前的时间,判断动画是否已经结束会用到。

/**
 * This method is called by ViewGroup.drawChild() to have each child view draw itself.
 *
 * This is where the View specializes rendering behavior based on layer type,
 * and hardware acceleration.
 */
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
     //判断是否启用硬件加速绘制视图
    boolean drawingWithRenderNode = mAttachInfo != null
            && mAttachInfo.mHardwareAccelerated
            && hardwareAcceleratedCanvas;

    boolean more = false;
    
    .........省去部分代码.........
    
    //用于改变图像的Transformation 
    Transformation transformToApply = null;
    boolean concatMatrix = false;
    final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
    final Animation a = getAnimation();
    if (a != null) {
        //如果视图有关联动画,则交给处理动画
        more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
        concatMatrix = a.willChangeTransformationMatrix();
        if (concatMatrix) {
            mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
        }
        //这时候拿到的Transformation 已经是经过动画变换后的,注意Transformation其实是存储在父控件的
        transformToApply = parent.getChildTransformation();
    } 

    ........省去部分代码........

            if (transformToApply != null) {
                if (concatMatrix) {
                    if (drawingWithRenderNode) {
                        //如果启用了硬件加速则会走到这里
                        renderNode.setAnimationMatrix(transformToApply.getMatrix());
                    } else {
                        // Undo the scroll translation, apply the transformation matrix,
                        // then redo the scroll translate to get the correct result.
                        canvas.translate(-transX, -transY);
                        
                        //拿到Transformation的矩阵绘制图像
                        canvas.concat(transformToApply.getMatrix());
                        canvas.translate(transX, transY);
                    }
                    parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                }

                float transformAlpha = transformToApply.getAlpha();
                if (transformAlpha < 1) {
                    alpha *= transformAlpha;
                    parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                }
            }

    ........省去部分代码........
    
    return more;
}

3.执行到View的applyLegacyAnimation方法。

/**
 * Utility function, called by draw(canvas, parent, drawingTime) to handle the less common
 * case of an active Animation being run on the view.
 */
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
        Animation a, boolean scalingRequired) {
    Transformation invalidationTransform;
    final int flags = parent.mGroupFlags;
    final boolean initialized = a.isInitialized();
    if (!initialized) {
        a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
        a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
        if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
        onAnimationStart();
    }
    
    //拿到父控件的Transformation,通过改变它的值来实现视图变换
    final Transformation t = parent.getChildTransformation();
    //将Transformation交给Animation处理,返回值表示是否动画已经执行完毕
    boolean more = a.getTransformation(drawingTime, t, 1f);
    if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
        if (parent.mInvalidationTransformation == null) {
            parent.mInvalidationTransformation = new Transformation();
        }
        invalidationTransform = parent.mInvalidationTransformation;
        a.getTransformation(drawingTime, invalidationTransform, 1f);
    } else {
        invalidationTransform = t;
    }

    
    if (more) {
        //more为true表示动画还没结束
        if (!a.willChangeBounds()) {
            if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
                    ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
                parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
            } else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
                // The child need to draw an animation, potentially offscreen, so
                // make sure we do not cancel invalidate requests
                parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                
                //动画没结束需要继续调用invalidate绘制视图
                parent.invalidate(mLeft, mTop, mRight, mBottom);
            }
        } else {
            if (parent.mInvalidateRegion == null) {
                parent.mInvalidateRegion = new RectF();
            }
            final RectF region = parent.mInvalidateRegion;
            a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
                    invalidationTransform);

            // The child need to draw an animation, potentially offscreen, so
            // make sure we do not cancel invalidate requests
            parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;

            final int left = mLeft + (int) region.left;
            final int top = mTop + (int) region.top;
           
            //动画没结束需要继续调用invalidate绘制视图
            parent.invalidate(left, top, left + (int) (region.width() + .5f),
                    top + (int) (region.height() + .5f));
        }
    }
    return more;
}

4.执行到Animation的getTransformation方法,在这里Animation会做一些通用逻辑,比如计算动画已经执行的时间占比,算出经差值器转换后的比例值,然后将该值和outTransformation一起作为参数传给applyTransformation方法,在这里对outTransformation的Matrix做变换。applyTransformation方法需要各个子类实现。

/**
 * Gets the transformation to apply at a specified point in time. Implementations of this
 * method should always replace the specified Transformation or document they are doing
 * otherwise.
 *
 * @param currentTime Where we are in the animation. This is wall clock time.
 * @param outTransformation A transformation object that is provided by the
 *        caller and will be filled in by the animation.
 * @return True if the animation is still running
 */
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) {
        //计算当前动画已经执行的时间与动画需要执行的时间的比值,得出当前动画已经执行了百分比。
        normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
                (float) duration;
    } else {
        // time is a step-change with a zero duration
        normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
    }
    
    //当normalizedTime >= 1.0f或者取消了动画则表示动画过期,也就是动画结束
    final boolean expired = normalizedTime >= 1.0f || isCanceled();
    //如果动画没过期,则mMore赋值为true,告诉视图动画还没结束,需继续执行invalidate重绘
    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);
        //真正改变outTransformation矩阵的方法
        applyTransformation(interpolatedTime, outTransformation);
    }

   ........省去部分代码........

    return mMore;
}

5.以ScaleAnimation动画为例,分析它的applyTransformation方法。

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    float sx = 1.0f;
    float sy = 1.0f;
    float scale = getScaleFactor();

    if (mFromX != 1.0f || mToX != 1.0f) {
        //根据interpolatedTime算出此时的缩放值,因为interpolatedTime不是直接等于时间变化的比例,
        //而是由差值器根据时间变化的比例经过特定算法得出的,所以图像的缩放就不一定是随着时间线性变化。
        sx = mFromX + ((mToX - mFromX) * interpolatedTime);
    }
    if (mFromY != 1.0f || mToY != 1.0f) {
        sy = mFromY + ((mToY - mFromY) * interpolatedTime);
    }

    //通过改变Transformation的矩阵来改变图像
    if (mPivotX == 0 && mPivotY == 0) {
        //如果锚点为左上角,则直接缩放即可
        t.getMatrix().setScale(sx, sy);
    } else {
        //如果锚点为特定的点,则除了根据锚点缩放
        t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
    }
}

在这里Transformation的矩阵已经被ScaleAnimation做了改变,那么当它继续执行到View的draw方法后面的部分,视图会拿到该矩阵,根据矩阵绘制图像,这样ScaleAnimation就间接改变了图像的绘制,因为这个过程是在一段时间内完成的,所以就形成了动画效果。

提个问题

视图动画会改变视图的尺寸吗?
不会,因为动画只是改变了draw的过程,并不会改变视图的尺寸大小,你看到的视图的大小改变了其实只是绘制区域改变了,但尺寸并没有变。

特别注意
1.视图动画执行之后并未改变View的真实布局属性值。切记这一点,譬如,在界面上有一个Button在屏幕上方,我们设置了平移动画移动到屏幕下方然后保持动画最后执行状态,这时点击屏幕下方动画执行之后的Button是没有任何反应的,而点击原来屏幕上方没有Button的地方却响应了点击Button的事件。

2.Animation的动画效果其实是通过父View实现的。这也是为什么执行动画的View大小和位置不变,但是图像却可以绘制在View之外的原因,严格上来说是父View在执行动画,所以动画效果的作用范围是父View的显示区域。

自定义视图动画

经过上面的分析,我们知道视图动画最终起决定性作用的是applyTransformation方法,因为该方法可以改变视图绘制的图像。所以自定义视图动画的关键就是继承Animation并且实现applyTransformation方法,在这里实现你想要的动画效果,因为用到的不多,所以就不多说了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值