安卓动画原理解析

安卓动画分类

  • 逐帧动画「Drawable Animation、Frame Animation」
  • 补间动画「View Animaiton、Tweened Animation」:补间动画只能修改 View 组件的部分属性,动画效果是绘制出来的,组件的状态并没有改变
  • 属性动画「Property Animation」:可以修改 View 组件的任何属性,组件的状态也会改变

Drawable Animation

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:duration="100">
        <layer-list>
            <item android:drawable="@drawable/login_loading_00" />
            <item android:drawable="@drawable/login_loading_10" />
        </layer-list>
    </item>
    <item android:duration="100">
        <layer-list>
            <item android:drawable="@drawable/login_loading_01" />
            <item android:drawable="@drawable/login_loading_11" />
        </layer-list>
    </item>
</animation-list>

其中,android:oneshot = “true” 表示该动画只播放一次,等于 false 则循环播放。<item/> 标签定义各个帧显示的图片,显示顺序依照 <item/> 定义顺序,<layer-list/> 内包含的图片将层叠起来,在同一帧中一起显示,item 的二级标签支持上面列出的所有 Drawable

private AnimationDrawable loadingAnimation;
loadingAnimation.stop();
loadingAnimation.start();

View Animation

补间动画效果由四个因素决定:初始状态、结束状态、持续时间、Interpolator

关键类:AlphaAnimation,RotateAnimation,ScaleAnimation,TranslateAnimation、AnimationSet。其中,AnimationSet用于控制一组动画的执行顺序

代码实现

TranslateAnimation tAnim = new TranslateAnimation(0, 400, 0, 0);
RotateAnimation rAnima = new RotateAnimation(0, 70);
ScaleAnimation sAnima = new ScaleAnimation(0, 5, 0, 5);
AlphaAnimation aAnima = new AlphaAnimation(1.0f, 0.0f);

tAnim.setDuration(2000);
tAnim.setInterpolator(new AccelerateDecelerateInterpolator());
translation.startAnimation(tAnim);

XML 实现

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false">
    <scale
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:fromXScale="1.0"
        android:toXScale="1.4"
        android:fromYScale="1.0"
        android:toYScale="0.6"
        android:pivotX="50%"
        android:pivotY="50%"
        android:fillAfter="false"//设置fillAfter为true,动画将保持结束的状态
        android:duration="1000" />
    <set
        android:interpolator="@android:anim/accelerate_interpolator"
        android:startOffset="1000">
        <scale …  />
        <rotate
            android:fromDegrees="0"
            android:toDegrees="60"
            android:pivotX="50%"
            android:pivotY="50%"
            android:duration="400" />
    </set>
</set>

Animation anim = AnimationUtils.loadAnimation(AnimaXmlActivity.this, R.anim.myanim);
img.startAnimation(anim);

补间动画原理

核心本质是在一定时间内,通过 Matrix 矩阵变换计算刷新区域,不断刷新父视图的过程。在动画播放过程中,如果是硬件加速场景:第一帧刷新父视图和动画视图,后面仅刷新父视图,另外在动画过程中,不会触发动画视图的兄弟视图的 onDraw 方法。如果是软件绘制,则同时刷新父视图和所有子视图

补间动画并没有对 View 的原始坐标进行修改,只是在绘制的时候根据相应的 Animation 进行变换,所以事件响应还是在之前的位置。而属性动画会改变 View 的相应属性,所以响应事件是在改变之后的位置上

public void startAnimation(Animation animation) {
    animation.setStartTime(Animation.START_ON_FIRST_FRAME);
    setAnimation(animation);
    invalidateParentCaches();
    invalidate(true);
}

public void draw(Canvas canvas) {
    drawBackground(canvas);
    onDraw(canvas);
    dispatchDraw(canvas);
    onDrawForeground(canvas);
}

protected void dispatchDraw(Canvas canvas) {
    final long drawingTime = getDrawingTime();
    for (int i = 0; i < childrenCount; i++) {
        drawChild(canvas, child, drawingTime);
    }
}

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    final Animation a = getAnimation();
    if (a != null) {
        more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
        concatMatrix = a.willChangeTransformationMatrix();
        if (concatMatrix) {
            mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
        }
        transformToApply = parent.getChildTransformation();
    }
    if (drawingWithRenderNode) {
        // 首次setAnimation时调用view.invalidate,后面直接调用parent.invalidate
        renderNode = updateDisplayListIfDirty();
        if (!renderNode.hasDisplayList()) {
            // Uncommon, but possible.
            renderNode = null;
            drawingWithRenderNode = false;
        }
    }
    if (concatMatrix) {
        if (drawingWithRenderNode) {
            renderNode.setAnimationMatrix(transformToApply.getMatrix());
        } else {
            canvas.translate(-transX, -transY);
            canvas.concat(transformToApply.getMatrix());
            canvas.translate(transX, transY);
        }
        parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
    }
    if (drawingWithRenderNode) {
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        ((RecordingCanvas) canvas).drawRenderNode(renderNode);
    } else {
        // Fast path for layouts with no backgrounds
        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchDraw(canvas);
        } else {
            draw(canvas);
        }
    }
}

// 判断是否还需要播放动画,如果继续播放动画,刷新Parent
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) {
        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;
        parent.invalidate(left, top, left + (int) (region.width() + .5f), top + (int) (region.height() + .5f));
    }
}
  1. 当调用 View.startAnimation 时动画并没有马上就执行,而是通过 invalidate 层层通知到 ViewRootImpl 发起一次遍历 View 树的请求,而这次请求会等到接收到最近一帧到了的信号时才去发起遍历 View 树绘制操作
  2. 从 DecorView 开始遍历,绘制流程在遍历时会调用到 View.draw 方法,当该方法被调用时,如果 View 有绑定动画,那么会去调用applyLegacyAnimation,这个方法是专门用来处理动画相关逻辑的
  3. 在 applyLegacyAnimation 这个方法里,如果动画还没有执行过初始化,先调用动画的初始化方法 initialized,同时调用 onAnimationStart 通知动画开始了,然后调用 getTransformation 来根据当前时间计算动画进度,紧接着调用 applyTransformation 并传入动画进度来应用动画
  4. getTransformation 这个方法有返回值,如果动画还没结束会返回 true,动画已经结束或者被取消了返回 false。所以 applyLegacyAnimation 会根据 getTransformation 的返回值来决定是否通知 ViewRootImpl 再发起一次遍历请求,返回值是 true 表示动画没结束,那么就去通知 ViewRootImpl 再次发起一次遍历请求。然后当下一帧到来时,再从 DecorView 开始遍历 View 树绘制,重复上面的步骤,这样直到动画结束
  5. 有一点需要注意,动画是在每一帧的绘制流程里被执行,所以动画并不是单独执行的,也就是说,如果这一帧里有一些 View 需要重绘,那么这些工作同样是在这一帧里遍历 View 树的过程中完成的。每一帧只会发起一次 perfromTraversals 操作

Property Animation

通过动画的方式改变对象的属性,动画的执行类: ObjectAnimator、ValueAnimator、AnimatorSet

  • AnimatorSet 用于控制一组动画的执行顺序
  • AnimatorInflater 用于加载属性动画的XML文件
  • TypeEvaluator 估值器
  • TimeInterpolator 插值器
  • PropertyValuesHolder 实现一个动画更改多个效果

属性动画通过Choreographer.postFrameCallback实现

    private void start(boolean playBackwards) {
        addAnimationCallback(0);
    }

    private void addAnimationCallback(long delay) {
        if (!mSelfPulse) {
            return;
        }
        getAnimationHandler().addAnimationFrameCallback(this, delay);
    }

    public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
            getProvider().postFrameCallback(mFrameCallback);
        }
        if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback);
        }
    }

    private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {

        final Choreographer mChoreographer = Choreographer.getInstance();

        @Override
        public void postFrameCallback(Choreographer.FrameCallback callback) {
            mChoreographer.postFrameCallback(callback);
        }
    }

    private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
            }
        }
    };

ObjectAnimator

1、ObjectAnimator 提供了 ofInt、ofFloat、ofObject,这几个方法都是设置动画作用的元素、作用的属性、动画开始、结束的属性值,当只设置一个属性值的时候,会认为当然对象的属性值为开始(getPropName反射获取),设置的值为终点,如果设置两个,则一个为开始、一个为结束。动画更新的过程中,会不断调用 setPropName 更新元素的属性,所有使用 ObjectAnimator 更新的属性,必须得有 getter(设置属性值的时候)和 setter 方法

ObjectAnimator.ofFloat(view, "rotationX", 0.0F, 360.0F).setDuration(500).start();

2、如果操作的属性方法内部没有调用 View 的重绘,比如 setRotationX,则需要手动调用

anim.addUpdateListener(new AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        view.postInvalidate();
        view.invalidate();
    }
});

3、通过 ObjectAnimator 让 View 既缩小、又淡出(属性scaleX、scaleY、alpha)

public void rotateAnimRun(final View view) {
    ObjectAnimator anim = ObjectAnimator.ofFloat(view, "zhy", 1.0F,  0.0F).setDuration(500);
    anim.start();
    anim.addUpdateListener(new AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float cVal = (Float) animation.getAnimatedValue();
            view.setAlpha(cVal);
            view.setScaleX(cVal);
            view.setScaleY(cVal);
        }
    });
}

4.使用 PropertyValuesHolder 实现一个动画更改多个效果:

public void propertyValuesHolder(View view) {
    PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 1f, 0f, 1f);
    PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f);
    PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f);
    ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY,pvhZ).setDuration(1000).start();
}

ValueAnimator

ValueAnimator 本身并没有在属性上做操作,因此,不需要操作对象的属性一定要有 getter 和 setter 方法,你可以自己根据当前动画的计算值,来操作任何属性

public void verticalRun( View view) {
    ValueAnimator animator = ValueAnimator.ofFloat(0, mScreenHeight - mBlueBall.getHeight());
    animator.setDuration(1000).start();
    animator.setInterpolator(value)
    animator.addUpdateListener(new AnimatorUpdateListener() {
        @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mBlueBall.setTranslationY((Float) animation.getAnimatedValue());
            }
        });
}

实现抛物线的效果,水平方向100px/s,垂直方向加速度200px/s*s:

public void paowuxian(View view) {
    ValueAnimator valueAnimator = new ValueAnimator();
    valueAnimator.setObjectValues(new PointF(0, 0));
    valueAnimator.setDuration(3000);
    valueAnimator.setInterpolator(new LinearInterpolator());
    valueAnimator.setEvaluator(new TypeEvaluator<PointF>() {
        //fraction = t / duration
        @Override
        public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
            PointF point = new PointF();
            point.x = 200 * fraction * 3;
            point.y = 0.5f * 200 * (fraction * 3) * (fraction * 3);
            return point;
        }
    });
    valueAnimator.start();
    valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
        @Override  
        public void onAnimationUpdate(ValueAnimator animation) {
            PointF point = (PointF) animation.getAnimatedValue();
            mBlueBall.setX(point.x);
            mBlueBall.setY(point.y);
        }
    });
}

监听事件:对于动画,一般都是一些辅助效果,比如我要删除个元素,我可能希望是个淡出的效果,但是最终还是要删掉,并不是你透明度没有了,还占着位置,所以我们需要知道动画如何结束

public void fadeOut(View view) {
    ObjectAnimator anim = ObjectAnimator.ofFloat(mBlueBall, "alpha", 0.5f);
    anim.addListener(new AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) { }
        @Override
        public void onAnimationRepeat(Animator animation) { }
        @Override
        public void onAnimationEnd(Animator animation) { }
        @Override
        public void onAnimationCancel(Animator animation) { }
    });
    anim.start();
}

AnimatorListenerAdapter:有时候,我只要监听某个状态,这么长的代码我不能接受

anim.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {

    }
});

AnimatorSet

public void togetherRun(View view) {
    ObjectAnimator anim1 = ObjectAnimator.ofFloat(mBlueBall, "scaleX", 1.0f, 2f);
    ObjectAnimator anim2 = ObjectAnimator.ofFloat(mBlueBall, "scaleY", 1.0f, 2f);
    ObjectAnimator anim3 = ObjectAnimator.ofFloat(mBlueBall, "x",  cx ,  0f);
    ObjectAnimator anim4 = ObjectAnimator.ofFloat(mBlueBall, "x", cx);

    //anim1,anim2,anim3,anim4接着执行
    AnimatorSet animSet = new AnimatorSet();
    animSet.setDuration(2000);
    animSet.setInterpolator(new LinearInterpolator());
    animSet.playTogether(anim1, anim2);
    animSet.start();

    //anim1,anim2,anim3同时执行,anim4接着执行
    AnimatorSet animSet = new AnimatorSet();
    animSet.play(anim1).with(anim2);
    animSet.play(anim2).with(anim3);
    animSet.play(anim4).after(anim3);
    animSet.setDuration(1000);
    animSet.start();
}

Xml 文件实现

View Animator 在 anim 文件夹下创建动画。Property Animator 在 animator 文件夹下创建动画

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">
    //orderring表示播放顺序,还有另一个值:sequentially(表示一个接一个执行)
    <objectAnimator
        android:duration="1000"
        android:propertyName="scaleX"
        android:valueFrom="1.0"
        android:valueTo="0.5">
    </objectAnimator>
    <objectAnimator
        android:duration="1000"
        android:propertyName="scaleY"
        android:valueFrom="1.0"
        android:valueTo="0.5">
    </objectAnimator>
</set>

Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scale);
//缩放、反转等默认是中心缩放,和对称轴为反转线,本例子以左上角为中心点
mMv.setPivotX(0);
mMv.setPivotY(0);
mMv.invalidate();
anim.setTarget(mMv);
anim.start();

布局动画(Layout Animations)

LayoutTransition transition = new LayoutTransition();
transition.setAnimator(LayoutTransition.APPEARING,  null);
transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,  null);
mGridLayout.setLayoutTransition(transition);

//LayoutTransition.APPEARING:当View在ViewGroup中出现时,对此View设置动画
//LayoutTransition.CHANGE_APPEARING:当一个View在ViewGroup中出现时,此View影响其他View的位置,对其他View设置的动画
//LayoutTransition.DISAPPEARING:当一个View在ViewGroup中消失时,对此View设置的动画
//LayoutTransition.CHANGE_DISAPPEARING:当一个View在ViewGroup中消失时,此View影响其他View的位置,对其他View设置的动画

TypeEvaluator 是属性动画特有的,插值器(Interpolator)决定值的变化规律(匀速、加速blabla),即决定的是变化趋势;而具体变化数值则交给估值器

转场动画五种实现方式,其中 XML 中定义的动画可以指定 pathInterceptor(贝塞尔插值器)

Android内置了 9 种内置的插值器实现

作用资源ID对应的Java类
加速@android:anim/accelerate_interpolatorAccelerateInterpolator
减速@android:anim/decelerate_interpolatorDecelerateInterpolator
匀速@android:anim/linear_interpolatorLinearInterpolator
弹球效果@android:anim/bounce_interpolatorBounceInterpolator
先加速再减速@android:anim/accelerate_decelerate_interpolatorAccelerateDecelerateInterpolator
先退后再加速@android:anim/anticipate_interpolatorAnticipateInterpolator
周期运动@android:anim/cycle_interpolatorCycleInterpolator
快速完成动画,超出再回到结束样式@android:anim/overshoot_interpolatorOvershootInterpolator
先退后再加速,超出终点后再回终点@android:anim/anticipate_overshoot_interpolatorAnticipateOvershootInterpolator
@android:anim/anticipate_overshoot_interpolator

参考文档

浅析Android动画原理 - 掘金

Android补间动画原理分析 - 简书

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

little-sparrow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值