English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
前書き
日常開発ではアニメーションは欠かせませんが、属性アニメーションはさらに強力です。私たちはただ使い方を知るだけでなく、その原理も知っておく必要があります。そうすることで、自由自在に使えるようになります。それでは、今日は最もシンプルなことから、属性アニメーションの原理について説明します。
ObjectAnimator .ofInt(mView,"width",100,500) .setDuration(1000) .start();
ObjectAnimator#ofInt
この例を元に、以下のコードです。
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) { ObjectAnimator anim = new ObjectAnimator(target, propertyName); anim.setIntValues(values); anim; }
このメソッドでは、まずObjectAnimatorオブジェクトをnewして、setIntValuesメソッドで値を設定し、それから返します。ObjectAnimatorのコンストラクタでは、setTargetメソッドで現在のアニメーションのオブジェクトを設定し、setPropertyNameで現在の属性名を設定します。ここで重点を置くのはsetIntValuesメソッドです。
public void setIntValues(int... values) { if (mValues == null || mValues.length == 0) { // まだ値はありません - このアニメーターは一部ずつ構築されています。値を以下で初期化します: // 現在のpropertyNameに関わらず if (mProperty != null) { setValues(PropertyValuesHolder.ofInt(mProperty, values)); } else { setValues(PropertyValuesHolder.ofInt(mPropertyName, values)); } } else { super.setIntValues(values); } }
まずnullかどうかを判断します。ここではnullで、mPropertyもnullですので、以下を呼び出します:
setValues(PropertyValuesHolder.ofInt(mPropertyName, values));メソッドを見てみましょう。PropertyValuesHolderクラスは属性と値を保持しています。このメソッドでは、IntPropertyValuesHolderオブジェクトを構築し、返します。
public static PropertyValuesHolder ofInt(String propertyName, int... values) { return new IntPropertyValuesHolder(propertyName, values); }
IntPropertyValuesHolderのコンストラクタは以下の通りです:
public IntPropertyValuesHolder(String propertyName, int... values) { super(propertyName); setIntValues(values); }
ここでは、まずそのクラスのコンストラクタを呼び出し、setIntValuesメソッドを呼び出し、親クラスのコンストラクタではただpropertyNameを設定しています。setIntValuesの内容は以下の通りです:
public void setIntValues(int... values) { super.setIntValues(values); mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes; }
親クラスのsetIntValuesメソッドでは、mValueTypeをint.class、mKeyframesをKeyframeSet.ofInt(values)に初期化し、その後KeyframeSetがキーフレーム集合として、mKeyframesをmIntKeyframesに割り当てます。
KeyframeSet
このクラスはキーフレームを記録するものです。そのofIntメソッドを見てみましょう。
public static KeyframeSet ofInt(int... values) { IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2); if (numKeyframes == 1) { keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f); keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]); } else { keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]); for (int i = 1; i < numKeyframes; ++i) { keyframes[i] = (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]); } } return new IntKeyframeSet(keyframes); }
ここは何ですか?传入されたvaluesに基づいてキーフレームを計算し、最後にIntKeyframeSetを返します。
戻ってObjectAnimatorの中に入ります。ここではsetValuesは親クラスValueAnimatorの
ValueAnimator#setValues
public void setValues(PropertyValuesHolder... values) { int numValues = values.length; mValues = values; mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues); for (int i = 0; i < numValues; ++i) { PropertyValuesHolder valuesHolder = values[i]; mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder); } // 新しいプロパティ/値/ターゲットが再-開始前の初期化 mInitialized = false; }
ここでの操作はシンプルで、PropertyValuesHolderをmValuesMapに格納します。
ObjectAnimator#start
このメソッドはアニメーション開始の場所です。
public void start() { // See if any of the current active/pending animators need to be canceled AnimationHandler handler = sAnimationHandler.get(); if (handler != null) { int numAnims = handler.mAnimations.size(); for (int i = numAnims - 1; i >= 0; i--) { if (handler.mAnimations.get(i) instanceof ObjectAnimator) { ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i); if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) { anim.cancel(); } } } numAnims = handler.mPendingAnimations.size(); for (int i = numAnims - 1; i >= 0; i--) { if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) { ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i); if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) { anim.cancel(); } } } numAnims = handler.mDelayedAnims.size(); for (int i = numAnims - 1; i >= 0; i--) { if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) { ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i); if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) { anim.cancel(); } } } } if (DBG) { Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration()); for (int i = 0; i < mValues.length; ++i) { PropertyValuesHolder pvh = mValues[i]; Log.d(LOG_TAG, " Values[" + i + "]: " + pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " + pvh.mKeyframes.getValue(1)); } } super.start(); }
まず、AnimationHandlerオブジェクトを取得し、空でない場合は、mAnimations、mPendingAnimations、mDelayedAnimsのアニメーションを判定し、キャンセルします。最後に、親クラスのstartメソッドを呼び出します。
ValueAnimator#start
private void start(boolean playBackwards) { if (Looper.myLooper() == null) { throw new AndroidRuntimeException("アニメーターはLooperスレッド上でのみ実行できます"); } mReversing = playBackwards; mPlayingBackwards = playBackwards; if (playBackwards && mSeekFraction != -1) { if (mSeekFraction == 0 && mCurrentIteration == 0) { // 特別なケース:seekからの逆方向-to-0はseekされないように行動するべき mSeekFraction = 0; } mSeekFraction = 1 - (mSeekFraction % 1); } else { mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction); } mCurrentIteration = (int) mSeekFraction; mSeekFraction = mSeekFraction % 1; } if (mCurrentIteration > 0 && mRepeatMode == REVERSE && (mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) { // 逆方向のアニメーターに他のiterationにseekされた場合、 // iterationに基づいて正しい再生方向を決定する if (playBackwards) { mPlayingBackwards = (mCurrentIteration % 2) == 0; } else { mPlayingBackwards = (mCurrentIteration % 2) != 0; } } int prevPlayingState = mPlayingState; mPlayingState = STOPPED; mStarted = true; mStartedDelay = false; mPaused = false; updateScaledDuration(); // 作成時からスケールファクターが変更された場合 AnimationHandler animationHandler = getOrCreateAnimationHandler(); animationHandler.mPendingAnimations.add(this); if (mStartDelay == 0) { // This sets the initial value of the animation, prior to actually starting it running if (prevPlayingState != SEEKED) { setCurrentPlayTime(0); } mPlayingState = STOPPED; mRunning = true; notifyStartListeners(); } animationHandler.start(); }
在animationHandler.start中,会调用scheduleAnimation方法,在这个种,会用mChoreographerpost一个callback,最终会执行mAnimate的run方法。mChoreographerpost涉及到VSYNC,这里不多介绍。
mAnimate#run
doAnimationFrame(mChoreographer.getFrameTime());
在这里会用过doAnimationFrame设置动画帧,我们看下这个方法的代码。
void doAnimationFrame(long frameTime) { mLastFrameTime = frameTime; // mPendingAnimations包含任何请求开始播放的动画 // 我们将清除mPendingAnimations,但开始动画可能会 // 待处理列表中添加更多项(例如,如果有一个动画 // 開始がもう一度開始をトリガーするため、mPendingAnimations}} // が空です。 while (mPendingAnimations.size() > 0) { ArrayList<ValueAnimator> pendingCopy = (ArrayList<ValueAnimator>) mPendingAnimations.clone(); mPendingAnimations.clear(); int count = pendingCopy.size(); for (int i = 0; i < count; ++i) { ValueAnimator anim = pendingCopy.get(i); // アニメーションにstartDelayがある場合、遅延リストに配置します if (anim.mStartDelay == 0) { anim.startAnimation(this); } else { mDelayedAnims.add(anim); } } } // 次に、遅延キューに座っているアニメーションを処理します、追加します // それらを準備完了のアニメーションに追加します int numDelayedAnims = mDelayedAnims.size(); for (int i = 0; i < numDelayedAnims; ++i) { ValueAnimator anim = mDelayedAnims.get(i); if (anim.delayedAnimationFrame(frameTime)) { mReadyAnims.add(anim); } } int numReadyAnims = mReadyAnims.size(); if (numReadyAnims > 0) { for (int i = 0; i < numReadyAnims; ++i) { ValueAnimator anim = mReadyAnims.get(i); anim.startAnimation(this); anim.mRunning = true; mDelayedAnims.remove(anim); } mReadyAnims.clear(); } // Now process all active animations. The return value from animationFrame() // tells the handler whether it should now be ended int numAnims = mAnimations.size(); for (int i = 0; i < numAnims; ++i) { mTmpAnimations.add(mAnimations.get(i)); } for (int i = 0; i < numAnims; ++i) { ValueAnimator anim = mTmpAnimations.get(i); if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) { mEndingAnims.add(anim); } } mTmpAnimations.clear(); if (mEndingAnims.size() > 0) { for (int i = 0; i < mEndingAnims.size(); ++i) { mEndingAnims.get(i).endAnimation(this); } mEndingAnims.clear(); } // Schedule final commit for the frame. mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, mCommit, null); // If there are still active or delayed animations, schedule a future call to // onAnimate to process the next frame of the animations. if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) { scheduleAnimation(); } }
The method is long, and the logic is as follows:}
From above, we can see that the key to executing the animation is the doAnimationFrame method. In this method, the animationFrame method is called.
ValueAniator#animationFrame
boolean animationFrame(long currentTime) { boolean done = false; switch (mPlayingState) { case RUNNING: case SEEKED: float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f; if (mDuration == 0 && mRepeatCount != INFINITE) { // Skip to the end mCurrentIteration = mRepeatCount; if (!mReversing) { mPlayingBackwards = false; } } if (fraction >= 1f) { if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) { // Time to repeat if (mListeners != null) { int numListeners = mListeners.size(); for (int i = 0; i < numListeners; ++i) { mListeners.get(i).onAnimationRepeat(this); } } if (mRepeatMode == REVERSE) { mPlayingBackwards = !mPlayingBackwards; } mCurrentIteration += (int) fraction; fraction = fraction % 1f; mStartTime += mDuration; // 注意:ここでmStartTimeCommittedの値を更新する必要はありません // 私たちが時間オフセットを追加したばかりであるため。 } else { done = true; fraction = Math.min(fraction, 1.0f); } } if (mPlayingBackwards) { fraction = 1f - fraction; } animateValue(fraction); break; } return done; }
仮想機の実行エンジンによる動的分派原則に基づいて、ここではObjectAnimatorのanimateValueメソッドが呼び出されます。
ObjectAnimator#animateValue
void animateValue(float fraction) { 最終的なObject target = getTarget(); if (mTarget != null && target == null) { // ターゲットリファレンスを失ったため、キャンセルしてクリーンアップします。 cancel(); return; } super.animateValue(fraction); int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].setAnimatedValue(target); } }
ここでは主に二つのことを行っています、
その親クラスのメソッドは以下の通りです:
void animateValue(float fraction) { fraction = mInterpolator.getInterpolation(fraction); mCurrentFraction = fraction; int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].calculateValue(fraction); } if (mUpdateListeners != null) { int numListeners = mUpdateListeners.size(); for (int i = 0; i < numListeners; ++i) { mUpdateListeners.get(i).onAnimationUpdate(this); } } }
このメソッドでは、Interpolatorを通じて現在のfractionを取得し、calculateValueを通じて現在の値を計算します。ここではIntPropertyValuesHolderのcalculateValueが呼び出されます。
void calculateValue(float fraction) { mIntAnimatedValue = mIntKeyframes.getIntValue(fraction); }
mIntKeyframesがIntKeyframeSetに対応していることを知っています。このクラスのgetIntValueメソッドでは、TypeEvaluatorを通じて現在の対応する値を計算します。これ以上詳しくは言いません。
最後に、animateValueに戻ります。値が計算された後、setAnimatedValueが呼び出され、値が設定されます。その実装を見てみましょう。
IntPropertyValuesHolder#setAnimatedValue
void setAnimatedValue(Object target) { if (mIntProperty != null) { mIntProperty.setValue(target, mIntAnimatedValue); return; } if (mProperty != null) { mProperty.set(target, mIntAnimatedValue); return; } if (mJniSetter != 0) { nCallIntMethod(target, mJniSetter, mIntAnimatedValue); return; } if (mSetter != null) { try { mTmpValueArray[0] = mIntAnimatedValue; mSetter.invoke(target, mTmpValueArray); catch (InvocationTargetException e) { Log.e("PropertyValuesHolder", e.toString()); catch (IllegalAccessException e) { Log.e("PropertyValuesHolder", e.toString()); } } }
そうですね、ここでは属性値の変更が確認できます。以下の四つの状況があります
まず、String propertyName, int... values パラメータで構築されたオブジェクトでは、mIntProperty が null で、mProperty も null です。それでは、他の二つはどのようにして来たのでしょうか?何かが欠けてるように思えます。
doAnimationFrame では startAnimation を直接呼び出すのはどうでしょうか?63;その通りです。
startAnimation
このメソッドでは initAnimation メソッドが呼び出されています。動的分派規則に従って、ここで ObjectAnimator の initAnimation メソッドが呼び出されます。ここで PropertyValuesHolder の setupSetterAndGetter メソッドを呼び出し、ここで mSetter などの初期化が行われます。詳細は省略しますが、コードを見てください。
それでは、これで Android での属性アニメーションに関するすべての内容が終わりました。この記事の内容が、Android デベロッパーの方々に少しでも役立つことを願っています。何か疑問があれば、コメントを残してください。呐喊教程へのご支援に感謝します。
声明:この記事の内容はインターネットから取得しており、著作権者に帰属します。インターネットユーザーが自発的に貢献し、自己でアップロードしたものであり、このサイトは所有権を持ちません。人工編集は行われていません。著作権侵害を疑う内容があれば、お気軽にメール:notice#wまでご連絡ください。3codebox.com(メールを送信する際、#を@に変更してください)で通報し、関連する証拠を提供してください。一旦確認が取れましたら、このサイトは即座に侵害を疑われるコンテンツを削除します。