English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
المقدمة
في التطوير اليومي،لا يمكننا الاستغناء عن الرسوم المتحركة،والمحركات الخاصية أقوى.ليس فقط علينا معرفة كيفية استخدامها،بل يجب أن نعرف أيضًا مبادئها.بهذا يمكننا استخدامها بسهولة.إذن،اليوم،من خلال الأبسط،سنفهم مبادئ الرسوم المتحركة الخاصية.
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(); }
في 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(); } }
الطريقة طويلة، منطقها كالتالي:
من خلال ما سبق يمكننا ملاحظة أن تنفيذ الرسوم المتحركة يعتمد على طريقة 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(float نسبة) { هدف نهائي = getTarget(); إذا (الهدف != null && الهدف == null) { // فقدنا مرجع الهدف، ألغوا وازالة اللمسات. إلغاء(); return; } super.animateValue(nسبة); عدد القيم = طول قيمي; للمسافة (i = 0; i < عدد القيم; ++i) { مقيمي [i].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()); } } }
حسنًا، هنا يمكنك رؤية أثر تعديل القيمة الخاص بالخصائص، هناك أربعة حالات
أولاً، عبر إنشاء الكائن باستخدام String propertyName, int... values، mIntProperty هو null، وmProperty أيضًا null. كيف تأتي الأثنين الآخران؟ يبدو أن هناك شيء مفقود؟
هل يمكن في doAnimationFrame استدعاء startAnimation مباشرة؟ نعم، هو هنا.
startAnimation
في هذا الطريقة، تم استدعاء طريقة initAnimation. بناءً على قواعد التوزيع الديناميكي، هنا يتم استدعاء طريقة initAnimation الخاصة بObjectAnimator. هنا يتم استدعاء طريقة setupSetterAndGetter الخاصة بPropertyValuesHolder، هنا يتم تحديث mSetter وما إلى ذلك، لن أقول أكثر، يرجى مراجعة الكود الخاص بك.
حسنًا، هذا هو كل محتوى حول Animate Properties في Android، آمل أن يكون محتوى هذا المقال قد ساعدك، إذا كان لديك أي أسئلة، يمكنك ترك تعليق للتفاعل، شكرًا لدعمك لتعليمات呐喊.
البيان: محتويات هذا المقال تم جمعها من الإنترنت، ويتمتع المالك الحقيقي بالحقوق، تم جمع المحتويات من قبل المستخدمين عبر الإنترنت الذين يقدمون مساهماتهم الخاصة ويتم تحميلها بشكل مستقل، هذا الموقع لا يملك حقوق الملكية، ولا يتم تعديل المحتويات بشكل إنساني، ولا يتحمل أي مسؤولية قانونية. إذا كنت قد وجدت محتوى يشتبه في انتهاك حقوق النسخ، فيرجى إرسال بريد إلكتروني إلى: notice#oldtoolbag.com (أثناء إرسال البريد الإلكتروني، يرجى استبدال #بـ @) للإبلاغ، وتقديم الأدلة ذات الصلة، وإذا تم التحقق من ذلك، فإن هذا الموقع سيقوم بإزالة المحتوى المزعوم بسرعة.