English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Introduction
In daily development, animation is indispensable, and property animation is even more powerful. We not only need to know how to use it, but also understand its principles. Only in this way can we be proficient. So today, let's start with the simplest to understand the principles of property animation.
ObjectAnimator .ofInt(mView,"width",100,500) .setDuration(1000) .start();
ObjectAnimator#ofInt
For example, the code is as follows.
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) { ObjectAnimator anim = new ObjectAnimator(target, propertyName); anim.setIntValues(values); return anim; }
In this method, we first create an ObjectAnimator object, then set the values using the setIntValues method, and then return. In the ObjectAnimator constructor, the current animation object is set through the setTarget method, and the current property name is set through the setPropertyName method. We will focus on the setIntValues method.
public void setIntValues(int... values) { if (mValues == null || mValues.length == 0) { // No values yet - this animator is being constructed piecemeal. Init the values with // whatever the current propertyName is if (mProperty != null) { setValues(PropertyValuesHolder.ofInt(mProperty, values)); } else { setValues(PropertyValuesHolder.ofInt(mPropertyName, values)); } } else { super.setIntValues(values); } }
Prima di tutto, si verifica se mValues è null, qui è null e anche mProperty è null, quindi viene chiamato
Il metodo setValues(PropertyValuesHolder.ofInt(mPropertyName, values)). Vediamo prima il metodo PropertyValuesHolder.ofInt, la classe PropertyValuesHolder mantiene attributi e valori, in questo metodo viene costruito un oggetto IntPropertyValuesHolder e restituito.
public static PropertyValuesHolder ofInt(String propertyName, int... values) { return new IntPropertyValuesHolder(propertyName, values); }
Il costruttore di IntPropertyValuesHolder è il seguente:
public IntPropertyValuesHolder(String propertyName, int... values) { super(propertyName); setIntValues(values); }
Qui, prima di tutto, viene chiamato il costruttore della sua classe di appartenenza, quindi viene chiamato il metodo setIntValues, nel costruttore della superclasse, viene solo impostato il propertyName. Il contenuto di setIntValues è come segue:
public void setIntValues(int... values) { super.setIntValues(values); mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes; }
Nel metodo setIntValues della superclasse, viene inizializzato mValueType come int.class e mKeyframes come KeyframeSet.ofInt(values). KeyframeSet rappresenta la raccolta di keyframe. Poi assegna mKeyframes a mIntKeyframes.
KeyframeSet
Questa classe registra i frame chiave. Vediamo il suo metodo ofInt.
public static KeyframeSet ofInt(int... values) { int numKeyframes = values.length; 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); }
E qui? Calcola i frame chiave in base ai valori immessi e alla fine restituisce IntKeyframeSet.
Torna nell'ObjectAnimator, qui setValues viene utilizzato dal padre 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); } // Una nuova proprietà/valore/destinazione dovrebbe causare una nuova inizializzazione prima di iniziare mInitialized = false; }
L'operazione qui è semplice, ovvero inserire PropertyValuesHolder in mValuesMap.
ObjectAnimator#start
Questo metodo è il punto di partenza dell'animazione.
public void start() { // Verifica se qualcuno degli animatori attivi/pendenti deve essere annullato 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(); }
Prima di tutto, viene ottenuto l'oggetto AnimationHandler, se non è vuoto, viene verificato se l'animazione è tra mAnimations, mPendingAnimations, mDelayedAnims e viene annullata. Infine, viene chiamato il metodo start della superclasse.
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) { // special case: reversing from seek-to-0 should act as if not seeked at all 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)) { // if we were seeked to some other iteration in a reversing animator, // figure out the correct direction to start playing based on the iteration if (playBackwards) { mPlayingBackwards = (mCurrentIteration % 2) == 0; } else { mPlayingBackwards = (mCurrentIteration % 2) != 0; } } int prevPlayingState = mPlayingState; mPlayingState = STOPPED; mStarted = true; mStartedDelay = false; mPaused = false; updateScaledDuration(); // in case the scale factor has changed since creation time AnimationHandler animationHandler = getOrCreateAnimationHandler(); animationHandler.mPendingAnimations.add(this); if (mStartDelay == 0) { // Questo imposta il valore iniziale dell'animazione, prima di avviare effettivamente la sua esecuzione if (prevPlayingState != SEEKED) { setCurrentPlayTime(0); } mPlayingState = STOPPED; mRunning = true; notifyStartListeners(); } animationHandler.start(); }
In animationHandler.start, viene chiamato il metodo scheduleAnimation, in questo caso, viene eseguito un callback mChoreographerpost, che alla fine esegue il metodo run di mAnimate. mChoreographerpost riguarda VSYNC, non entreremo nei dettagli.
mAnimate#run
doAnimationFrame(mChoreographer.getFrameTime());
Qui utilizzeremo doAnimationFrame per impostare i fotogrammi dell'animazione, diamo un'occhiata al codice di questo metodo.
void doAnimationFrame(long frameTime) { mLastFrameTime = frameTime; // mPendingAnimations contiene qualsiasi animazione che ha richiesto di essere avviata // Stiamo per svuotare mPendingAnimations, ma l'avvio dell'animazione potrebbe // aggiunge altri elementi alla lista in attesa (ad esempio, se una animazione // avvia un altro avvio). Così ripetiamo fino a mPendingAnimations // è vuoto. 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); // Se l'animazione ha un startDelay, posizionala nella lista di attesa if (anim.mStartDelay == 0) { anim.startAnimation(this); } else { mDelayedAnims.add(anim); } } } // Poi, elabora le animazioni attualmente in coda di attesa, aggiungendo // le aggiungono alle animazioni attive se sono pronte 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(); } // Ora elabora tutte le animazioni attive. Il valore di ritorno da animationFrame() // informa l'handler se deve essere ora terminato 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(); } // Pianifica il commit finale per il frame. mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, mCommit, null); // Se ci sono ancora animazioni attive o differite, pianifica una chiamata futura a // onAnimate per elaborare il prossimo frame delle animazioni. if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) { scheduleAnimation(); } }
Il metodo è lungo, la logica è la seguente:
Da qui possiamo vedere che la chiave dell'esecuzione dell'animazione è il metodo doAnimationFrame. In questo metodo, viene chiamato il metodo animationFrame.
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; // Nota: Non dobbiamo aggiornare il valore di mStartTimeCommitted qui // poiché abbiamo appena aggiunto un offset di durata. } else { done = true; fraction = Math.min(fraction, 1.0f); } } if (mPlayingBackwards) { fraction = 1f - fraction; } animateValue(fraction); break; } return done; }
Secondo il principio di dinamiche di dispatching dell'esecutore virtuale, qui verrà chiamato il metodo animateValue di ObjectAnimator.
ObjectAnimator#animateValue
void animateValue(float fraction) { final Object target = getTarget(); if (mTarget != null && target == null) { // Abbiamo perso il riferimento al target, annulla e pulisci. cancel(); return; } super.animateValue(fraction); int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].setAnimatedValue(target); } }
Qui si fanno principalmente due cose:
Il metodo della sua superclasse è il seguente:
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 questo metodo, viene ottenuto il valore corrente di fraction tramite Interpolator e calcolato il valore corrispondente tramite calculateValue, chiamando calculateValue di IntPropertyValuesHolder
void calculateValue(float fraction) { mIntAnimatedValue = mIntKeyframes.getIntValue(fraction); }
Sappiamo che mIntKeyframes corrisponde a IntKeyframeSet. Nel metodo getIntValue di questa classe, viene calcolato il valore corrispondente tramite TypeEvaluator. Non c'è molto da dire.
Infine, torniamo a animateValue. Dopo aver calcolato i valori, chiamiamo setAnimatedValue per impostare i valori. Vediamo l'implementazione.
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吗?没错,就是这里。
startAnimation
在这个方法中调用了initAnimation方法。还是根据动态分派规则,这里调用ObjectAnimator的initAnimation方法。在这里调用PropertyValuesHolder的setupSetterAndGetter方法,在这里对mSetter等进行了初始化,这里就不多说了,大家自己看代码吧。
好了,以上就是关于Android中属性动画的全部内容,希望本文的内容对各位Android开发者有所帮助,如果有疑问大家可以留言交流,感谢大家对呐喊教程的支持。
声明:本文内容来自网络,版权属于原作者,内容由互联网用户自发贡献并自行上传,本网站不拥有所有权,未进行人工编辑处理,也不承担相关法律责任。如果您发现涉嫌版权的内容,请发送邮件至:notice#oldtoolbag.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立即删除涉嫌侵权内容。