English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Análisis del código fuente de Android: explicación detallada de la animación de propiedades

Contenido

Introducción

En el desarrollo diario, no podemos prescindir de las animaciones, las animaciones de propiedades son más poderosas, no solo debemos saber cómo usarlas, sino también saber su principio. De esta manera, podremos manejarlas con destreza. Entonces, hoy, comencemos con lo más simple, conozcamos el principio de las animaciones de propiedades.
 ObjectAnimator10.ofInt(mView,"width",5.setDuration(
 0,10.setDuration(
 00)

.start();

ObjectAnimator#ofInt

Con este ejemplo, el código es el siguiente.
 public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
 anim.setIntValues(values);
 devolver anim;
}

En este método, primero se crea un objeto ObjectAnimator, luego se establecen los valores mediante el método setIntValues y luego se devuelve. En el constructor de ObjectAnimator, se establece el objeto de la animación actual mediante el método setTarget y el nombre de la propiedad actual mediante el método setPropertyName. Nos centraremos en el método setIntValues.

public void setIntValues(int... values) {
 if (mValues == null || mValues.length == 0) {
 // No hay valores aún - este animador se está construyendo por partes. Inicialice los valores con
 // sin importar cuál sea el propertyName actual
 if (mProperty != null) {
 setValues(PropertyValuesHolder.ofInt(mProperty, values));
 } else {
 setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
 }
 } else {
 super.setIntValues(values);
 }
}

Primero se evaluará si mValues es null, aquí es null, y también mProperty es null, por lo que se llamará
El método setValues(PropertyValuesHolder.ofInt(mPropertyName, values)); Vamos a ver el método PropertyValuesHolder.ofInt, la clase PropertyValuesHolder almacena atributos y valores, en este método se construye un objeto IntPropertyValuesHolder y se devuelve.

public static PropertyValuesHolder ofInt(String propertyName, int... values) {
 return new IntPropertyValuesHolder(propertyName, values);
}

El método constructor de IntPropertyValuesHolder es el siguiente:

public IntPropertyValuesHolder(String propertyName, int... values) {
 super(propertyName);
 setIntValues(values);
}

Aquí, primero se llama al método constructor de su clase de clasificación, luego se llama al método setIntValues. En el método constructor de su clase padre, solo se configura el propertyName. El contenido de setIntValues es el siguiente:

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];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);
 }
 // Nueva propiedad/valores/el objetivo debe causar re-inicialización antes de comenzar
 mInitialized = false;
}

Aquí la operación es sencilla, se colocan PropertyValuesHolder en mValuesMap.

ObjectAnimator#start

Este método es el punto de partida de la animación.

public void start() {
 // Ver si alguno de los activos actuales/los animadores pendientes deben ser cancelados
 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, "Objetivo de animación, duración: " + getTarget() + ", " + getDuration());
 for (int i = 0; i < mValues.length; ++i) {
 PropertyValuesHolder pvh = mValues[i];
 Log.d(LOG_TAG, " Valores[" + i + "]: " +
 pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
 pvh.mKeyframes.getValue(1));
 }
 }
 super.start();
}

Primero, se obtiene el objeto AnimationHandler, si no es nulo, se determina si es una animación de mAnimations, mPendingAnimations o mDelayedAnims y se cancela. Finalmente, se llama al método start de la clase padre.

ValueAnimator#start

private void start(boolean playBackwards) {
 if (Looper.myLooper() == null) {
 throw new AndroidRuntimeException("Los animadores solo pueden ejecutarse en hilos Looper");
 }
 mReversing = playBackwards;
 mPlayingBackwards = playBackwards;
 if (playBackwards && mSeekFraction != -1) {
 if (mSeekFraction == 0 && mCurrentIteration == 0) {
 // caso especial: invirtiendo desde buscar-a-0 debe actuar como si no se hubiera buscado en absoluto
 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)) {
 // si fuimos buscando a alguna otra iteración en un animador que invierte
 // determine la dirección correcta para comenzar a reproducir basado en la iteración
 if (playBackwards) {
 mPlayingBackwards = (mCurrentIteration % 2) == 0;
 } else {
 mPlayingBackwards = (mCurrentIteration % 2) != 0;
 }
 }
 int prevPlayingState = mPlayingState;
 mPlayingState = STOPPED;
 mStarted = true;
 mStartedDelay = false;
 mPaused = false;
 updateScaledDuration(); // en caso de que el factor de escala haya cambiado desde el momento de la creación
 AnimationHandler animationHandler = getOrCreateAnimationHandler();
 animationHandler.mPendingAnimations.add(this);
 if (mStartDelay == 0) {
 // Esto establece el valor inicial de la animación, antes de comenzar realmente a ejecutarla
 if (prevPlayingState != SEEKED) {
 setCurrentPlayTime(0);
 }
 mPlayingState = STOPPED;
 mRunning = true;
 notifyStartListeners();
 }
 animationHandler.start();
}
  • Inicializar algunos valores primero
  • updateScaledDuration ajustar el tiempo, por defecto es1.0f
  • Obtener o crear AnimationHandler, agregar la animación a la lista mPendingAnimations,
  • Si no hay retraso, notificar al oyente
  • animationHandler.start

En animationHandler.start, se llama al método scheduleAnimation, en este caso, se usará mChoreographerpost para llamar a un callback, que finalmente ejecutará el método run de mAnimate. mChoreographerpost implica VSYNC, aquí no se detallará.

mAnimate#run

doAnimationFrame(mChoreographer.getFrameTime());

Aquí se utilizará doAnimationFrame para configurar los cuadros de animación, veamos el código de este método.

void doAnimationFrame(long frameTime) {
 mLastFrameTime = frameTime;
 // mPendingAnimations contiene cualquier animación que haya solicitado comenzar
 // Vamos a limpiar mPendingAnimations, pero la animación de inicio puede
 // agregar más a la lista pendiente (por ejemplo, si una animación
 // inicia desencadena otro inicio). Por lo tanto, seguimos en el bucle hasta que mPendingAnimations
 // está vacío.
 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);
 // Si la animación tiene un startDelay, colócala en la lista retrasada
 if (anim.mStartDelay == 0) {
 anim.startAnimation(this);
 } else {
 mDelayedAnims.add(anim);
 }
 }
 }
 // A continuación, procesa las animaciones que actualmente se encuentran en la cola retrasada, añadiendo
 // los animaciones activas si están listas
 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();
 }
 // Ahora procesa todas las animaciones activas. El valor de retorno de animationFrame()
 // informa al gestor si debe finalizar ahora
 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();
 }
 // Programa el compromiso final para el cuadro.
 mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, mCommit, null);
 // Si aún hay animaciones activas o diferidas, programa una llamada futura a
 // onAnimate para procesar el siguiente cuadro de las animaciones.
 if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
 scheduleAnimation();
 }
}

El método es largo, la lógica es la siguiente:

  1. Tomar la animación de la lista mPendingAnimations y, según la elección previa, comenzar a animar o agregarla a la lista mDelayedAnims.
  2. Si las animaciones de la lista mDelayedAnims están listas, se agregan a la lista mReadyAnims
  3. Tomar la animación que se debe ejecutar de la lista mAnimations y agregarla a la lista mTmpAnimations
  4. Ejecutar el cuadro de animación a través del método doAnimationFrame
  5. Continuar ejecutando scheduleAnimation

Desde lo anterior podemos ver que la clave para ejecutar la animación es el método doAnimationFrame. En este método, se llama al método 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) {
 // Saltar al final
 mCurrentIteration = mRepeatCount;
 if (!mReversing) {
  mPlayingBackwards = false;
 }
 }
 if (fraction >= 1f) {
 if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
  // El tiempo para repetir
  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: No necesitamos actualizar el valor de mStartTimeCommitted aquí
  // ya que acabamos de agregar un desplazamiento de duración.
 } else {
  done = true;
  fraction = Math.min(fraction, 1.0f);
 }
 }
 if (mPlayingBackwards) {
 fraction = 1f - fraction;
 }
 animateValue(fraction);
 break;
 }
 return done;
 }
  • Calcular fraction
  • Llamar al método animateValue

De acuerdo con el principio de dinámica de asignación de métodos del motor de ejecución del virtual machine, aquí se llamará al método animateValue de ObjectAnimator.

ObjectAnimator#animateValue

void animateValue(float fraction) {
 final Object target = getTarget();
 if (mTarget != null && target == null) {
 // Perdimos la referencia al objetivo, cancelar y limpiar.
 cancel();
 return;
 }
 super.animateValue(fraction);
 int numValues = mValues.length;
 for (int i = 0; i < numValues; ++i) {
 mValues[i].setAnimatedValue(target);
 }
}

Aquí se hacen dos cosas principales,

  1. Llamar al método animateValue de la clase padre
  2. A través de setAnimatedValue configurar el atributo

Su método en la clase padre es el siguiente:

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, here IntPropertyValuesHolder's calculateValue is called

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());
 }
 }
}

Sí, aquí podemos ver la huella de la modificación del valor de la propiedad, hay las siguientes cuatro situaciones

  1. mIntProperty no es null
  2. mProperty no es null
  3. mJniSetter no es null
  4. mSetter no es null

Primero, a través de los objetos construidos con los parámetros String propertyName, int... values, mIntProperty es null, y mProperty también es null. ¿Y de dónde vienen los otros dos? Parece que falta algo?

¿Por qué no llamar directamente startAnimation en doAnimationFrame?63;Sí, es aquí.

startAnimation

En este método se llama al método initAnimation. Aún según las reglas de asignación dinámica, aquí se llama al método initAnimation de ObjectAnimator. Aquí se llama al método setupSetterAndGetter de PropertyValuesHolder, aquí se inicializa mSetter, etc., no mencionaré más, los demás pueden ver el código por su cuenta.

Bueno, esto es todo sobre la animación de propiedades en Android, espero que el contenido de este artículo pueda ayudar a los desarrolladores de Android, si tienen alguna pregunta, pueden dejar comentarios para intercambiar, gracias por el apoyo a la教程 de alarido.

Declaración: El contenido de este artículo se obtiene de la red, es propiedad del autor original, el contenido se contribuye y carga espontáneamente por los usuarios de Internet, este sitio no posee los derechos de propiedad, no se ha realizado una edición humana y no asume ninguna responsabilidad legal relacionada. Si encuentra contenido sospechoso de infracción de derechos de autor, por favor envíe un correo electrónico a: notice#oldtoolbag.com (al enviar un correo electrónico, reemplace # con @) para denunciar y proporcionar evidencia relevante. Una vez verificada, este sitio eliminará inmediatamente el contenido sospechoso de infracción.

Te gustaría que te gustara