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

Анализ исходного кода Android для свойств анимации

Preface

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 handle it with ease. So, today, let's start with the simplest and understand the principles of property animation.

ObjectAnimator
 .ofInt(mView,"width",100,500)
 .setDuration(1000)
 .start();

ObjectAnimator#ofInt

Here is an example of the code.

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 constructor of ObjectAnimator, the current animation object is set using the setTarget method, and the current property name is set using setPropertyName. 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);
 }
}

Сначала проверяется, не является ли mValues null, здесь это null, и mProperty также null, поэтому вызывается
Метод setValues(PropertyValuesHolder.ofInt(mPropertyName, values)); Давайте сначала посмотрим на метод PropertyValuesHolder.ofInt, класс 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) {
 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);
}

Здесь? На основе传入енных значений вычисляем ключевые кадры и в конце возвращаем 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, " Цель анимации, продолжительность: " + 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, он должен действовать, как будто поиска вовсе не было
 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 (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) {
 // Это устанавливает начальное значение анимации, до ее фактического запуска
 if (prevPlayingState != SEEKED) {
 setCurrentPlayTime(0);
 }
 mPlayingState = STOPPED;
 mRunning = true;
 notifyStartListeners();
 }
 animationHandler.start();
}
  • Сначала初始化一些 значения
  • updateScaledDuration масштабировать время, по умолчанию 1.0f
  • Получите или создайте AnimationHandler, добавьте анимацию в список mPendingAnimations
  • Если нет задержки, уведомите слушателей
  • animationHandler.start

В animationHandler.start вызывается метод scheduleAnimation, в котором используется mChoreographerpost для вызова callback, который в конечном итоге выполнит метод run анимации mAnimate. 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();
 }
 // Теперь обработайте все активные анимации. Возвратное значение из animationFrame()
 // сообщает обработчику, должен ли он сейчас быть завершен
 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();
 }
 // Запланируйте окончательное подтверждение для кадра.
 mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, mCommit, null);
 // Если есть активные или отложенные анимации, запланируйте будущий вызов:
 // onAnimate для обработки следующего кадра анимаций.
 if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
 scheduleAnimation();
 }
}

Метод较长, логика такова:

  1. Извлекаем анимацию из mPendingAnimations и на основе предварительно выбранного startAnimation добавляем её в список mDelayedAnims.
  2. Если анимации в списке mDelayedAnims готовы, добавляем их в список mReadyAnims
  3. Извлекаем анимацию, которую нужно выполнить, из списка mAnimations и добавляем её в список mTmpAnimations
  4. Выполняем анимацию кадра через метод doAnimationFrame
  5. Продолжаем выполнять scheduleAnimation

Из вышеизложенного мы можем看出, ключом к выполнению анимации является метод doAnimationFrame. В этом методе вызывается метод 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) {
 // Пропуск до конца
 mCurrentIteration = mRepeatCount;
 if (!mReversing) {
  mPlayingBackwards = false;
 }
 }
 if (fraction >= 1f) {
 if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
  // Время для повторения
  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;
 }
  • Вычисление fraction
  • Метод animateValue вызывается

Согласно принципу динамического распределения выполнения виртуального машины, здесь вызывается метод animateValue класса ObjectAnimator.

ObjectAnimator#animateValue

void animateValue(float fraction) {
 final 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);
 }
}

Здесь выполняются два основных действия:

  1. Метод animateValue родительского класса вызывается
  2. Свойства устанавливаются через setAnimatedValue

Методы родительского класса таковы:

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

Ну, до этого можно увидеть следы изменения значения свойства, есть следующие四种 situations

  1. mIntProperty не equals null
  2. mProperty не equals null
  3. mJniSetter не equals null
  4. mSetter не equals null

Сначала, мы создаем объект через String propertyName, int... values параметр, mIntProperty equals null, и mProperty также equals null. Как появились другие два? Похоже, что мы что-то забыли?

Так, в doAnimationFrame, вы就直接 вызовете startAnimation? Да, именно здесь.

startAnimation

В этом методе вызывается метод initAnimation. Как и по правилам динамического распределения, здесь вызывается метод initAnimation ObjectAnimator. Здесь вызывается метод setupSetterAndGetter PropertyValuesHolder, здесь initializes mSetter и т.д., не будем вдаваться в подробности, смотрите код сами.

Итак, вот все, что касается анимации свойств в Android. Надеюсь, что содержимое этой статьи поможет вам, уважаемые разработчики Android. Если у вас есть вопросы, пожалуйста, оставляйте комментарии для обсуждения, спасибо за поддержку呐喊 учебника.

Заявление: содержание этой статьи взято из Интернета, авторские права принадлежат соответствующему автору. Контент был предложен и загружен пользователями Интернета, этот сайт не обладает правами собственности, не был отредактирован вручную и не несет ответственности за связанные с этим法律责任. Если вы обнаружите содержимое,涉嫌侵犯版权, пожалуйста, отправьте письмо по адресу: notice#oldtoolbag.com (во время отправки письма замените # на @), и предоставьте соответствующие доказательства. Если после проверки будет установлено, что содержимое является нарушением авторских прав, сайт немедленно удалят涉嫌侵权的内容。

Давай посмотрим, что вам понравится