• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

Android高级进阶之路三看完这篇再不会Android动画框架我跪搓衣板

android 搞代码 3年前 (2022-03-01) 38次浏览 已收录 0个评论

Android动画可作用于View/ViewGroup,Actvity,Fragment实现炫酷的交互成果。通过几天的探索,搞清楚了各类动画的应用和动画的实现原理,在此记录以下。
只管Android动画有好几种类别,然而各种动画的实现外围都是TimeInterpolator->Interpolator->各种Interpolator。大抵过程是通过Interpolator计算出工夫相干的input,通过这个input计算出各类fraction,利用各类Interpolator计算出的fraction计算出各种状态参数(工夫相干),将这些参数应用到动画成果上,Android会通过肯定的机制一直反复这个过程(16ms为周期)就形成了咱们看到的动画。

从动画的分类上有以下几种类别:

1、FrameAnimation

FrameAnimation顾名思义就是帧动画,通过逐帧播放来实现的动画。Frame Animation能够通过xml来实现,也可利用代码来实现:
xml实现时根结点必须是<animation-list>,根结点内蕴含多个<item>元素,例如:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot=["true" | "false"] >
    <item
        android:drawable="@drawable/frame1"
        android:duration="250" />
    <item
        android:drawable="@drawable/frame2"
        android:duration="250" />
    <item
        android:drawable="@drawable/frame3"
        android:duration="250" />
    <item
        android:drawable="@drawable/frame4"
        android:duration="250" />
</animation-list>

对于这种动画,就是一直的更换显示的Drawable来实现动态效果。

2、TweenAnimation

能够对View进行一系列的变换,如平移,翻转,缩放,淡入淡出,也能够将他们组合起来造成混合的动画成果。TweenAnimation的实现形式也有两种,别离能够用代码和xml实现。
xml:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@[package:]anim/interpolator_resource"
    android:shareInterpolator=["true" | "false"] >
    <alpha
        android:fromAlpha="float"
        android:toAlpha="float" />
    <scale
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float" />
    <translate
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" />
    <rotate
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float" />
    <set>
        ...
    </set>
</set>

加载xml动画能够用Android SDK提供的工具类:

AnimationUtils.loadAnimations();

至于代码实现形式:

//提供了以下几种Animation
AlphaAnimation TranslateAnimation ScaleAnimation RotateAnimation
AnimationSet.addAnimation(Animation)
//应用办法根本与定义xml统一

Animation的运作依赖两个办法:

 /**
     * 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;
        }

        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);
            applyTransformation(interpolatedTime, outTransformation);
        }

        if (expired) {
            if (mRepeatCount == mRepeated || isCanceled()) {
                if (!mEnded) {
                    mEnded = true;
                    guard.close();
                    fireAnimationEnd();
                }
            } else {
                if (mRepeatCount > 0) {
                    mRepeated++;
                }

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

                mStartTime = -1;
                mMore = true;

                fireAnimationRepeat();
            }
        }

        if (!mMore && mOneMoreTime) {
            mOneMoreTime = false;
            return true;
        }

        return mMore;
    }

protected void applyTransformation(float interpolatedTime, Transformation t) {
    }

每次调用getTransformation会计算一个normalizedTime,这个normalizedTime会作为interpolator的input传到interpolator中:

            final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);

失去一个通过inpterpolator计算过的fraction(interpolatedTime)。之后以这个interpolatedTime为参数回调applyTransformation:

applyTransformation(interpolatedTime, outTransformation);

applyTransformation的另一参数,outTransformation是一个Transformation对象,其中的两个成员变量如下:

    protected Matrix mMatrix;
    protected float mAlpha;

也就是说通过这个outTransformation,能够对它的alpha或者matrix进行运算,而后Android会读取通过Animation运算的outTransformation里的这两个变量而后作用于要实现动画成果的组件View/ViewGroup,Actvity,Fragment上实现动画成果。至于如何实现这个过程,上面会有剖析。
通过下面的剖析得悉,Animation的运行依赖Android自身的机制回调(每帧都得回调getTransformation和applyTransformation)无奈本身进行运算计算fraction,并且可参加运算的只有Transformation对象里的alpha和matrix,所以Animation只能实现简略的Alpha,Scale,Translate,Rotate变换成果。

3、PropertyAnimator

Android提供了3种,别离是:

ObjectAnimator
TimeAnimator
ValueAnimator

下面剖析的Animation受限于Android自身的回调,只能实现Alpha,Scale,Translate,Rotate的变换。而Animator没有此限度,它不依赖于Android自身的机制回调,然而它意依赖于Looper的Thread,上源码:

 private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mReversing = playBackwards;
        // Special case: reversing from seek-to-0 should act as if not seeked at all.
        if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
            if (mRepeatCount == INFINITE) {
                // Calculate the fraction of the current iteration.
                float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
                mSeekFraction = 1 - fraction;
            } else {
                mSeekFraction = 1 + mRepeatCount - mSeekFraction;
            }
        }
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        // Resets mLastFrameTime when start() is called, so that if the animation was running,
        // calling start() would put the animation in the
        // started-but-not-yet-reached-the-first-frame phase.
        mLastFrameTime = 0;
        AnimationHandler animationHandler = AnimationHandler.getInstance();
        animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));

        if (mStartDelay == 0 || mSeekFraction >= 0) {
            // If there's no start delay, init the animation and notify start listeners right away
            // to be consistent with the previous behavior. Otherwise, postpone this until the first
            // frame after the start delay.
            startAnimation();
            if (mSeekFraction == -1) {
                // No seek, start at play time 0\. Note that the reason we are not using fraction 0
                // is because for animations with 0 duration, we want to be consistent with pre-N
                // behavior: skip to the final value immediately.
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }

从start开始剖析,从代码可知只能在Looper Thread上开启Animator,一看到Looper咱们就霎时豁然开朗,原来Animator的实现也离不卡Handler机制。

AnimationHandler animationHandler = AnimationHandler.getInstance();
        animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));

Animator取得一个AnimationHandler实例,并把本身作为回调传给这个AnimationHandler:

public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
     ...
}
public class AnimationHandler {
    ...

/**
     * Callbacks that receives notifications for animation timing and frame commit timing.
     */
    interface AnimationFrameCallback {
        /**
         * Run animation based on the frame time.
         * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
         *                  base.
         */
        void doAnimationFrame(long frameTime);

        /**
         * This notifies the callback of frame commit time. Frame commit time is the time after
         * traversals happen, as opposed to the normal animation frame time that is before
         * traversals. This is used to compensate expensive traversals that happen as the
         * animation starts. When traversals take a long time to complete, the rendering of the
         * initial frame will be delayed (by a long time). But since the startTime of the
         * animation is set before the traversal, by the time of next frame, a lot of time would
         * have passed since startTime was set, the animation will consequently skip a few frames
         * to respect the new frameTime. By having the commit time, we can adjust the start time to
         * when the first frame was drawn (after any expensive traversals) so that no frames
         * will be skipped.
         *
         * @param frameTime The frame time after traversals happen, if any, in the
         *                  {@link SystemClock#uptimeMillis()} time base.
         */
        void commitAnimationFrame(long frameTime);
    }
}

然而咱们发现。。。特么的这个AnimationHandler基本就不是一个Handler。
从新回到Animator的执行流程上。。。
在start函数里咱们看到有一个:

animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));

跟进去:

/**
     * Register to get a callback on the next frame after the delay.
     */
    public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
            getProvider().postFrameCallback(mFrameCallback);
        }
        if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback);
        }

        if (delay > 0) {
            mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
        }
    }

发现有这样一句话:

getProvider().postFrameCallback(mFrameCallback);

Provider又是什么呢?

private AnimationFrameCallbackProvider getProvider() {
        if (mProvider == null) {
            mProvider = new MyFrameCallbackProvider();
        }
        return mProvider;
    }

MyFrameCallbackProvider又是什么呢?

/**
     * Default provider of timing pulse that uses Choreographer for frame callbacks.
     */
    private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {

        final Choreographer mChoreographer = Choreographer.getInstance();

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

        @Override
        public void postCommitCallback(Runnable runnable) {
            mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
        }

        @Override
        public long getFrameTime() {
            return mChoreographer.getFrameTime();
        }

        @Override
        public long getFrameDelay() {
            return Choreographer.getFrameDelay();
        }

        @Override
        public void setFrameDelay(long delay) {
            Choreographer.setFrameDelay(delay);
        }
    }

Choreographer又是什么呢?

public final class Choreographer {
    ...
/**
     * Posts a frame callback to run on the next frame.
     * <p>
     * The callback runs once then is automatically removed.
     * </p>
     *
     * @param callback The frame callback to run during the next frame.
     *
     * @see #postFrameCallbackDelayed
     * @see #removeFrameCallback
     */
    public void postFrameCallback(FrameCallback callback) {
        postFrameCallbackDelayed(callback, 0);
    }
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
        if (callback == null) {
            throw new IllegalArgumentException("callback must not be null");
        }

        postCallbackDelayedInternal(CALLBACK_ANIMATION,
                callback, FRAME_CALLBACK_TOKEN, delayMillis);
    }
private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

看到这里。。。终于现出原形了呈现了一个mHandler,mHandler又是什么呢?

public final class Choreographer {
    private final FrameHandler mHandler;
    ...
}
它是Choreographer的一个成员变量,从命名上看仿佛是一个与Frame(帧)相干的Handler:
Java
private final class FrameHandler extends Handler {
        public FrameHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DO_FRAME:
                    doFrame(System.nanoTime(), 0);
                    break;
                case MSG_DO_SCHEDULE_VSYNC:
                    doScheduleVsync();
                    break;
                case MSG_DO_SCHEDULE_CALLBACK:
                    doScheduleCallback(msg.arg1);
                    break;
            }
        }
    }

然而还有一点:

getProvider().postFrameCallback(mFrameCallback);

这个mFrameCallback对应的类是:

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

这又是一系列简单的callback,剖析明确了也写不明确,但FrameHandler的doFrame最终会调用咱们的mFrameCallback,也就是会调用到doAnimationFrame,最终会调用到Animator的animateValue办法。再之后大家都晓得了。。。会调用AnimatorUpdateListener的onAnimationUpdate。一顿剖析,大抵过程明确了,然而这个机制也太简单了,牵扯了太多的方面,太多的类。什么时候本人能有这样的设计能力啊。。
对于Choreographer这个类:
*https://www.cnblogs.com/kross/p/4087780.html
这篇文件有比拟具体的剖析,临时还不能了解那么多。

其它的

对于Chroeographer源码正文是这样写的:

/**
 * Coordinates the timing of animations, input and drawing.
 * <p>
 * The choreographer receives timing pulses (such as vertical synchronization)
 * from the display subsystem then schedules work to occur as part of rendering
 * the next display frame.
 * </p><p>
 * Applications typically interact with the choreographer indirectly using
 * higher level abstractions in the animation framework or the view hierarchy.
 * Here are some examples of things you can do using the higher-level APIs.
 * </p>
 * <ul>
 * <li>To post an animation to be processed on a regular time basis synchronized with
 * display frame rendering, use {@link android.animation.ValueAnimator#start}.</li>
 * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
 * frame, use {@link View#postOnAnimation}.</li>
 * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
 * frame after a delay, use {@link View#postOnAnimationDelayed}.</li>
 * <li>To post a call to {@link View#invalidate()} to occur once at the beginning of the
 * next display frame, use {@link View#postInvalidateOnAnimation()} or
 * {@link View#postInvalidateOnAnimation(int, int, int, int)}.</li>
 * <li>To ensure that the contents of a {@link View} scroll smoothly and are drawn in
 * sync with display frame rendering, do nothing.  This already happens automatically.
 * {@link View#onDraw} will be called at the appropriate time.</li>
 * </ul>
 * <p>
 * However, there are a few cases where you might want to use the functions of the
 * choreographer directly in your application.  Here are some examples.
 * </p>
 * <ul>
 * <li>If your application does its rendering in a different thread, possibly using GL,
 * or does not use the animation framework or view hierarchy at all
 * and you want to ensure that it is appropriately synchronized with the display, then use
 * {@link Choreographer#postFrameCallback}.</li>
 * <li>... and that's about it.</li>
 * </ul>
 * <p>
 * Each {@link Looper} thread has its own choreographer.  Other threads can
 * post callbacks to run on the choreographer but they will run on the {@link Looper}
 * to which the choreographer belongs.
 * </p>
 */

有一句话引起了我的留神:
If your application does its rendering in a different thread, possibly using GL,

  • or does not use the animation framework or view hierarchy at all
    也就是说,通常咱们的View只能在创立它的线程内进行事件处理,动画,或者绘制的起因在于,这些框架和机制的实现都依赖于这个类,这个类是线程相干。若要在其余线程内渲染能够间接应用Choreographer#postFrameCallback???
    有待探索。。

文章转自 https://www.gaodaima.com/p/435&#8230; ,如有侵权,请分割删除。

相干视频:

【Android 根底课程】风行框架之热修复解读_哔哩哔哩_bilibili
【Android 根底课程】高阶UI进阶, 从零到精通(二)_哔哩哔哩_bilibili
【Android进阶零碎学习——面对层出不穷的第三方SDK,身为架构师该怎么做?_哔哩哔哩_bilibili
教你疾速成长为一位具备外围竞争力的挪动架师_哔哩哔哩_bilibili
Android架构师核心技术——申请型框架通用设计方案_哔哩哔哩_bilibili


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Android高级进阶之路三看完这篇再不会Android动画框架我跪搓衣板

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址