English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Einleitung
Im täglichen Entwicklungsalltag können wir uns nicht von Animationen trennen, und die Eigenschaftsanimation ist noch mächtiger. Wir müssen nicht nur wissen, wie man sie verwendet, sondern auch ihren Prinzipien verstehen. Nur so können wir sicher und zuversichtlich arbeiten. Also, heute beginnen wir mit dem Einfachsten und lernen wir die Prinzipien der Eigenschaftsanimation kennen.
ObjectAnimator .ofInt(mView,"width",100,500) .setDuration(1000) .start();
ObjectAnimator#ofInt
Nehmen wir dies als Beispiel, der Code lautet wie folgt.
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) { ObjectAnimator anim = new ObjectAnimator(target, propertyName); anim.setIntValues(values); return anim; }
In diesem Verfahren wird zunächst ein ObjectAnimator-Objekt neu erstellt, dann werden die Werte über die Methode setIntValues eingestellt und schließlich zurückgegeben. Im Konstruktor von ObjectAnimator wird das aktuelle Animationselement über die Methode setTarget festgelegt und der aktuelle Attributname über setPropertyName festgelegt. Wir konzentrieren uns auf die Methode setIntValues.
public void setIntValues(int... values) { if (mValues == null || mValues.length == 0) { // Noch keine Werte - dieser Animator wird Stück für Stück konstruiert. Initialisiere die Werte mit // egal, was der aktuelle propertyName ist if (mProperty != null) { setValues(PropertyValuesHolder.ofInt(mProperty, values)); } else { setValues(PropertyValuesHolder.ofInt(mPropertyName, values)); } } else { super.setIntValues(values); } }
Zunächst wird überprüft, ob mValues null ist, hier ist es null und mProperty ist auch null, daher wird aufgerufen
setValues(PropertyValuesHolder.ofInt(mPropertyName, values)); Methodenaufruf. Schauen wir uns die Methode PropertyValuesHolder.ofInt an, die Klasse PropertyValuesHolder hält Attribute und Werte. In dieser Methode wird ein Objekt von IntPropertyValuesHolder erstellt und zurückgegeben.
public static PropertyValuesHolder ofInt(String propertyName, int... values) { return new IntPropertyValuesHolder(propertyName, values); }
Der Konstruktor von IntPropertyValuesHolder ist wie folgt:
public IntPropertyValuesHolder(String propertyName, int... values) { super(propertyName); setIntValues(values); }
Hier wird zunächst der Konstruktor seiner Klasse aufgerufen, gefolgt von der Aufrufung der Methode setIntValues. Im Konstruktor der übergeordneten Klasse wird nur propertyName gesetzt. Der Inhalt von setIntValues ist wie folgt:
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); } // New property/values/target should cause re-initialization prior to starting 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(); }
Firstly, it will obtain the AnimationHandler object, if it is not null, it will determine whether it is an animation in mAnimations, mPendingAnimations, or mDelayedAnims, and cancel it. Finally, it will call the parent class's start method.
ValueAnimator#start
private void start(boolean playBackwards) { if (Looper.myLooper() == null) { throw new AndroidRuntimeException("Animators may only be run on Looper threads"); } mReversing = playBackwards; mPlayingBackwards = playBackwards; if (playBackwards && mSeekFraction != -1) { if (mSeekFraction == 0 && mCurrentIteration == 0) { // Spezialfall: Rückwärts von der Suche-zu-0 sollte so funktionieren, als ob gar nicht gesucht wurde mSeekFraction = 0; } else if (mRepeatCount == INFINITE) { 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)) { // falls wir in einem umgekehrten Animator zu einer anderen Iteration gesucht wurden, // ermitteln Sie die richtige Wiedergabefolge basierend auf der Iteration if (playBackwards) { mPlayingBackwards = (mCurrentIteration % 2) == 0; } else { mPlayingBackwards = (mCurrentIteration % 2) != 0; } } int prevPlayingState = mPlayingState; mPlayingState = STOPPED; mStarted = true; mStartedDelay = false; mPaused = false; updateScaledDuration(); // falls der Skalierungsfaktor seit der Erstellung geändert wurde AnimationHandler animationHandler = getOrCreateAnimationHandler(); animationHandler.mPendingAnimations.add(this); if (mStartDelay == 0) { // Dies setzt den Anfangswert der Animation, bevor sie tatsächlich gestartet wird if (prevPlayingState != SEEKED) { setCurrentPlayTime(0); } mPlayingState = STOPPED; mRunning = true; notifyStartListeners(); } animationHandler.start(); }
In animationHandler.start wird die Methode scheduleAnimation aufgerufen, in dieser wird mChoreographerpost einen Callback verwenden, um letztendlich die Methode run von mAnimate auszuführen. mChoreographerpost bezieht sich auf VSYNC, das hier nicht weiter erläutert wird.
mAnimate#run
doAnimationFrame(mChoreographer.getFrameTime());
Hier wird doAnimationFrame verwendet, um Animationen zu setzen, wir schauen uns den Code dieser Methode an.
void doAnimationFrame(long frameTime) { mLastFrameTime = frameTime; // mPendingAnimations enthält alle Animationen, die um einen Start angefordert haben // Wir werden mPendingAnimations leeren, aber die erste Animation kann // mehr hinzuzufügen, die Warteschlange (z.B. wenn eine Animation // Startauslöser löst einen weiteren Start aus). Daher loopen wir, bis mPendingAnimations // leer ist. 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); // Wenn die Animation eine Startverzögerung hat, platzieren Sie sie in der verzögerten Liste if (anim.mStartDelay == 0) { anim.startAnimation(this); } else { mDelayedAnims.add(anim); } } } // Als nächstes verarbeiten Sie die Animationen, die derzeit in der verzögerten Warteschlange sitzen, und fügen Sie // sie den aktiven Animationen hinzufügen, wenn sie bereit sind 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(); } // Verarbeiten Sie jetzt alle aktiven Animationen. Der Rückgabewert von animationFrame() // informiert den Handler darüber, ob es jetzt beendet werden sollte 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(); } // Planen Sie den endgültigen Commit für das Frame. mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, mCommit, null); // Wenn es noch aktive oder verzögerte Animationen gibt, planen Sie einen zukünftigen Aufruf // onAnimate, um das nächste Frame der Animationen zu verarbeiten. if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) { scheduleAnimation(); } }
Die Methode ist较长, die Logik ist wie folgt: }}
Von oben können wir erkennen, dass der Schlüssel zur Ausführung der Animation die Methode doAnimationFrame ist. In dieser Methode wird die Methode animationFrame aufgerufen.
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) { // Springe zum Ende mCurrentIteration = mRepeatCount; if (!mReversing) { mPlayingBackwards = false; } } if (fraction >= 1f) { if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) { // Zeit zum Wiederholen 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; // Hinweis: Wir müssen hier den Wert von mStartTimeCommitted nicht aktualisieren // da wir gerade einen Zeitverzögerungsausgleich hinzugefügt haben. } else { done = true; fraction = Math.min(fraction, 1.0f); } } if (mPlayingBackwards) { fraction = 1f - fraction; } animateValue(fraction); break; } return done; }
Gemäß dem Prinzip der dynamischen Verteilung durch den Virtual Machine Execution Engine wird hier die Methode animateValue von ObjectAnimator aufgerufen.
ObjectAnimator#animateValue
void animateValue(float fraction) { final Object target = getTarget(); if (mTarget != null && target == null) { // Wir haben den Zielbezug verloren, abbrechen und aufräumen. cancel(); return; } super.animateValue(fraction); int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].setAnimatedValue(target); } }
Hier wurden hauptsächlich zwei Dinge getan,
Die Methode der übergeordneten Klasse ist wie folgt:
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); } } }
In this method, the current fraction is obtained through Interpolator, and calculateValue is used to calculate the current value, which will call IntPropertyValuesHolder's calculateValue
void calculateValue(float fraction) { mIntAnimatedValue = mIntKeyframes.getIntValue(fraction); }
We know that mIntKeyframes corresponds to IntKeyframeSet. In the getIntValue method of this class, the current corresponding value is calculated through TypeEvaluator. No more words.
Finally, return to animateValue. After calculating the value, setAnimatedValue is called to set the value. Let's see its implementation.
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()); } } }
Hier können Sie die Spuren der Änderung der Attributwerte sehen. Es gibt folgende vier Fälle
Zunächst wird ein Objekt durch die Konstruktion mit den Parametern String propertyName, int... values erstellt, mIntProperty ist null und mProperty ist auch null. Wie kommen die anderen beiden her? Hat sich etwas verpasst?
Warum wird in doAnimationFrame direkt startAnimation aufgerufen?63;Das ist genau hier.
startAnimation
In diesem Verfahren wird die Methode initAnimation aufgerufen. Es wird nach den Regeln der dynamischen Delegation aufgerufen, hier wird die Methode initAnimation von ObjectAnimator aufgerufen. Hier wird die Methode setupSetterAndGetter von PropertyValuesHolder aufgerufen, hier werden mSetter usw. initialisiert. Ich werde nicht weiter darauf eingehen, schauen Sie sich bitte selbst den Code an.
Nun, das war's zum Thema Property Animation in Android. Ich hoffe, dieser Artikel hilft Ihnen, Android-Entwicklern. Wenn Sie Fragen haben, hinterlassen Sie bitte Kommentare zum Austausch, danke für die Unterstützung von Rufenanleitung.
Erklärung: Der Inhalt dieses Artikels wurde aus dem Internet übernommen und gehört dem Urheberrechtlichem Inhaber. Der Inhalt wurde von Internetbenutzern freiwillig beigesteuert und hochgeladen. Diese Website besitzt keine Eigentumsrechte und hat den Inhalt nicht manuell bearbeitet. Sie übernimmt auch keine rechtlichen Haftung. Wenn Sie verdächtigen Inhalt finden, senden Sie bitte eine E-Mail an: notice#oldtoolbag.com (E-Mail senden, # durch @ ersetzen) zur Meldung von Missbrauch und zur Bereitstellung von relevanten Beweisen. Bei nachgewiesener Täuschung wird die Website sofort den verdächtigen Inhalt löschen.