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

تحليل المصدر لـ Android Property Animation

المقدمة

في التطوير اليومي،لا يمكننا الاستغناء عن الرسوم المتحركة،والمحركات الخاصية أقوى.ليس فقط علينا معرفة كيفية استخدامها،بل يجب أن نعرف أيضًا مبادئها.بهذا يمكننا استخدامها بسهولة.إذن،اليوم،من خلال الأبسط،سنفهم مبادئ الرسوم المتحركة الخاصية.

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);
 return anim;
}

في هذا الطريقة،سيتم إنشاء جسم ObjectAnimator أولاً،ثم إعداد القيم من خلال طريقة setIntValues،ثم العودة.في طريقة بناء ObjectAnimator،سيتم إعداد الجسم الحالي للرسوم المتحركة من خلال طريقة setTarget،وإعداد الاسم الحالي للخصائص من خلال طريقة setPropertyName.سنتحدث عن طريقة setIntValues بشكل رئيسي.

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));
 } آخره {
 setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
 }
 } آخره {
 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

loại này là để ghi lại khung chốt. chúng ta xem phương thức ofInt của nó.

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]);
 } آخره {
 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]);
 }
 }
 trả về new IntKeyframeSet(keyframes);
}

tại đây? tính toán khung chốt dựa trên values được truyền vào, sau đó trả về IntKeyframeSet.

trở lại ObjectAnimator, ở đây setValues được sử dụng từ lớp cha ValueAnimator

ValueAnimator#setValues

public void setValues(PropertyValuesHolder... values) {
 int numValues = values.length;
 mValues = values;
 mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
 للمسافة (i = 0; i < عدد القيم; ++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);
 إذا (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);
 إذا (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);
 إذا (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {}}
  anim.cancel();
 }
 }
 }
 }
 إذا (DBG) {
 Log.d(LOG_TAG, "هدف التحرك، استمرار: " + getTarget() + ", " + getDuration());
 للدوران (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();
}

أولاً، سيتم الحصول على Object AnimationHandler، إذا لم يكن فارغًا، سيتم التحقق مما إذا كانت التحركات هي mAnimations أو mPendingAnimations أو mDelayedAnims، وسيتعين إلغاءها. ثم يتم استدعاء طريقة start من الصنف الأبوي.

ValueAnimator#start

private void start(boolean playBackwards) {
 إذا (Looper.myLooper() == null) {
 throw new AndroidRuntimeException("يمكن تشغيل الممثلين فقط في threads من نوع Looper");
 }
 mReversing = playBackwards;
 mPlayingBackwards = playBackwards;
 إذا (playBackwards && mSeekFraction != -1) {
 إذا (mSeekFraction == 0 && mCurrentIteration == 0) {
 // حالة خاصة: العكس من البحث إلى 0 يجب أن يعمل كأنه لم يتم البحث على الإطلاق
 mSeekFraction = 0;
 } آخراً إذا (mRepeatCount == INFINITE) {
 mSeekFraction = 1 - (mSeekFraction % 1);
 } آخره {
 mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
 }
 mCurrentIteration = (int) mSeekFraction;
 mSeekFraction = mSeekFraction % 1;
 }
 إذا (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
 (mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
 // إذا كنا نبحث عن تكرار آخر في محرك العكس
 // اكتشف الاتجاه الصحيح للبدء بالعزف بناءً على التكرار
 إذا (playBackwards) {
 mPlayingBackwards = (mCurrentIteration % 2) == 0;
 } آخره {
 mPlayingBackwards = (mCurrentIteration % 2) != 0;
 }
 }
 int prevPlayingState = mPlayingState;
 mPlayingState = STOPPED;
 mStarted = true;
 mStartedDelay = false;
 mPaused = false;
 updateScaledDuration(); // في حالة تغيير معامل المقياس منذ وقت إنشاء
 AnimationHandler animationHandler = getOrCreateAnimationHandler();
 animationHandler.mPendingAnimations.add(this);
 إذا (مStartDelay == 0) {
 // هذا يضبط القيمة الابتدائية للحركة، قبل بدء تشغيلها فعليًا
 إذا كان prevPlayingState != SEEKED) {
 setCurrentPlayTime(0);
 }
 mPlayingState = STOPPED;
 mRunning = true;
 notifyStartListeners();
 }
 animationHandler.start();
}
  • البدء في تعيين بعض القيم
  • تحديث updateScaledDuration للزمن، الافتراضي هو 1.0f
  • الحصول أو إنشاء AnimationHandler، وإضافة الحركة إلى قائمة mPendingAnimations،
  • إذا لم يكن هناك تأخير، يتم إعلام المستمعين
  • animationHandler.start

في animationHandler.start، سيتم استدعاء method scheduleAnimation، في هذا النوع، سيتم إرسال callback من قبل mChoreographerpost، مما يؤدي إلى تنفيذ method run لمحرك mAnimate. mChoreographerpost يتعلق بVSYNC، لن نتحدث عنه الآن.

mAnimate#run

doAnimationFrame(mChoreographer.getFrameTime());

في هذا المكان سيتم استخدام doAnimationFrame لضبط إطارات الحركة، لنرى كود هذه الطريقة.

void doAnimationFrame(long frameTime) {
 mLastFrameTime = frameTime;
 // يحتوي mPendingAnimations على أي حركات قد طلبت بدءها
 // سنقوم بإزالة mPendingAnimations، ولكن قد تكون الحركة التي تبدأ
 // يضيف المزيد إلى قائمة الاستدعاءات المعلقة (على سبيل المثال، إذا كانت هناك حركة
 // البدء يؤدي إلى بدء آخر). لذا نعاد إلى التكرار حتى mPendingAnimations
 // is empty.
 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);
 // If the animation has a startDelay, place it on the delayed list
 if (anim.mStartDelay == 0) {
 anim.startAnimation(this);
 } آخره {
 mDelayedAnims.add(anim);
 }
 }
 }
 // Next, process animations currently sitting on the delayed queue, adding
 // them to the active animations if they are ready
 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();
 للدوران (int i = 0; i < numAnims; ++i) {
 mTmpAnimations.add(mAnimations.get(i));
 }
 للدوران (int i = 0; i < numAnims; ++i) {
 ValueAnimator anim = mTmpAnimations.get(i);
 إذا (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {
 mEndingAnims.add(anim);
 }
 }
 mTmpAnimations.clear();
 إذا (mEndingAnims.size() > 0) {
 للدوران (int i = 0; i < mEndingAnims.size(); ++i) {
 mEndingAnims.get(i).endAnimation(this);
 }
 mEndingAnims.clear();
 }
 // جدولة التزام النهاية للإطار.
 mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, mCommit, null);
 // إذا كانت هناك رسوميات نشطة أو مؤجلة، تقوم بجدولة مكالمة مستقبلية إلى
 // onAnimate لمعالجة إطار التغير التالي للرسوميات.
 إذا (!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) {
 // 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;
  نسبة = نسبة % 1f;
  مؤشر الزمن += المدة;
  // ملاحظة: لا نحتاج إلى تحديث قيمة mStartTimeCommitted هنا
  // لأننا قمنا فقط بإضافة تأخير المدة.
 } آخره {
  عدول = صحيح;
  نسبة = Math.min(nسبة, 1.0f);
 }
 }
 إذا (يشتغل العكسي) {
 نسبة = 1f - نسبة;
 }
 animateValue(nسبة);
 توقف();
 }
 عدول = done;
 }
  • حساب نسبة;
  • ندعو إلى طريقة animateValue;

حسب مبدأ التوزيع الديناميكي للتشغيل لجهاز التشغيل، سيتم استدعاء طريقة animateValue لمطور الهدف.

مطور الهدف#animateValue

بلاشك animateValue(float نسبة) {
 هدف نهائي = getTarget();
 إذا (الهدف != null && الهدف == null) {
 // فقدنا مرجع الهدف، ألغوا وازالة اللمسات.
 إلغاء();
 return;
 }
 super.animateValue(nسبة);
 عدد القيم = طول قيمي;
 للمسافة (i = 0; i < عدد القيم; ++i) {
 مقيمي [i].setAnimatedValue(الهدف);
 }
}

إليك ما قمنا به في هذا المكان:;

  1. ندعو إلى طريقة animateValue الفئة الأم;
  2. إعداد الخاصية عبر setAnimatedValue;

يوجد طريقة للاخداء من الفئة الأم:;

بلاشك animateValue(float نسبة) {
 نسبة = mInterpolator.getInterpolation(nسبة);
 مقاس الحالي = نسبة;
 عدد القيم = طول قيمي;
 للمسافة (i = 0; i < عدد القيم; ++i) {
 حساب القيمة (نسبة);
 }
 إذا (ممستمعي التحديث لا null) {
 int numListeners = mUpdateListeners.size();
 for (int i = 0; i < numListeners; ++i) {
 mUpdateListeners.get(i).onAnimationUpdate(this);
 }
 }
}

في هذه الطريقة، سيتم الحصول على fraction الحالية من خلال Interpolator، وتحديد القيمة الحالية من خلال calculateValue، هنا سيتم استدعاء calculateValue من IntPropertyValuesHolder

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

حسنًا، هنا يمكنك رؤية أثر تعديل القيمة الخاص بالخصائص، هناك أربعة حالات

  1. mIntProperty ليس null
  2. mProperty ليس null
  3. mJniSetter ليس null
  4. mSetter ليس null

أولاً، عبر إنشاء الكائن باستخدام String propertyName, int... values، mIntProperty هو null، وmProperty أيضًا null. كيف تأتي الأثنين الآخران؟ يبدو أن هناك شيء مفقود؟

هل يمكن في doAnimationFrame استدعاء startAnimation مباشرة؟ نعم، هو هنا.

startAnimation

في هذا الطريقة، تم استدعاء طريقة initAnimation. بناءً على قواعد التوزيع الديناميكي، هنا يتم استدعاء طريقة initAnimation الخاصة بObjectAnimator. هنا يتم استدعاء طريقة setupSetterAndGetter الخاصة بPropertyValuesHolder، هنا يتم تحديث mSetter وما إلى ذلك، لن أقول أكثر، يرجى مراجعة الكود الخاص بك.

حسنًا، هذا هو كل محتوى حول Animate Properties في Android، آمل أن يكون محتوى هذا المقال قد ساعدك، إذا كان لديك أي أسئلة، يمكنك ترك تعليق للتفاعل، شكرًا لدعمك لتعليمات呐喊.

البيان: محتويات هذا المقال تم جمعها من الإنترنت، ويتمتع المالك الحقيقي بالحقوق، تم جمع المحتويات من قبل المستخدمين عبر الإنترنت الذين يقدمون مساهماتهم الخاصة ويتم تحميلها بشكل مستقل، هذا الموقع لا يملك حقوق الملكية، ولا يتم تعديل المحتويات بشكل إنساني، ولا يتحمل أي مسؤولية قانونية. إذا كنت قد وجدت محتوى يشتبه في انتهاك حقوق النسخ، فيرجى إرسال بريد إلكتروني إلى: notice#oldtoolbag.com (أثناء إرسال البريد الإلكتروني، يرجى استبدال #بـ @) للإبلاغ، وتقديم الأدلة ذات الصلة، وإذا تم التحقق من ذلك، فإن هذا الموقع سيقوم بإزالة المحتوى المزعوم بسرعة.

الذوق الشخصي