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); anim; }
이 메서드에서는 먼저 ObjectAnimator 객체를 new하여 setIntValues 메서드를 통해 값을 설정하고 반환합니다. ObjectAnimator의 생성자에서는 setTarget 메서드를 통해 현재 애니메이션의 객체를 설정하고, setPropertyName을 통해 현재의 속성 이름을 설정합니다. 여기서 setIntValues 메서드에 대해 자세히 설명드리겠습니다.
public void setIntValues(int... values) {}} if (mValues == null || mValues.length == 0) { // 아직 값이 없습니다 - 이 애니메이터는 조각조각으로 생성되고 있습니다. 값을 초기화합니다: // 현재 propertyName이 무엇인지 상관없이 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라는 클래스는 속성과 값을 저장합니다. 이 메서드에서는 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); }
여기서, 첫 번째로 그의 分类의 생성자 메서드를 호출하고, 그 부모 클래스의 생성자 메서드에서는 propertyName을 설정하는 것만 수행됩니다. setIntValues의 내용은 다음과 같습니다:
public void setIntValues(int... values) {}} super.setIntValues(values); mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes; }
부모 클래스의 setIntValues 메서드에서, mValueType을 int.class, mKeyframes를 KeyframeSet.ofInt(values)로 초기화합니다. 그런 다음 KeyframeSet을 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] = (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); }
그곳에서는 어떻게 되는지, 입력된 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); } // 새로운 프로퍼티/값/타겟이 재-시작 전 초기화 mInitialized = false; }
여기서의 작업은 간단합니다. PropertyValuesHolder를 mValuesMap에 넣는 것입니다.
ObjectAnimator#start
이 메서드는 애니메이션이 시작하는 곳입니다.
public void start() { // 현재 활성화된 중인 애니메이션 중에 어떤 것이 있는지 확인해 보세요/대기 중인 애니메이션을 취소해야 합니다 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(); }
먼저 AnimationHandler 객체를 가져옵니다. 비어 있지 않다면, mAnimations, mPendingAnimations, mDelayedAnims 중의 애니메이션을 확인하고 취소합니다. 그리고 부모 클래스의 start 메서드를 호출합니다.
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) { // 특별한 경우: 이동에서 반복-to-0은 전혀 이동하지 않은 것처럼 작동해야 합니다 mSeekFraction = 0; } 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(); }
animationHandler.start에서 scheduleAnimation 메서드를 호출합니다. 이 경우, mChoreographer를 통해 콜백을 설정하여 마지막으로 mAnimate의 run 메서드를 실행합니다. mChoreographerpost는 VSYNC와 관련이 있지만, 이곳에서는 자세히 설명하지 않습니다.
mAnimate#run
doAnimationFrame(mChoreographer.getFrameTime());
여기서 doAnimationFrame를 사용하여 애니메이션 프레임을 설정하겠습니다. 이 메서드의 코드를 보겠습니다.
void doAnimationFrame(long frameTime) { mLastFrameTime = frameTime; // mPendingAnimations는 시작을 요청한 모든 애니메이션을 유지합니다 // 우리는 mPendingAnimations를 비우려고 합니다만, 시작 애니메이션은 // 지연된 목록에 더 많은 항목을 추가합니다 (예를 들어, 하나의 애니메이션 // 시작이 다시 시작을 trigge르면. 따라서 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); // 애니메이션이 시작 지연이 있으면, 지연 목록에 추가합니다 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()의 반환 값 // handler에 현재 종료되어야 하는지 알립니다 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(); } }
메서드가 길지만, 로직은 다음과 같습니다:
위에서 볼 수 있듯이, 애니메이션을 실행하는 핵심은 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의 값을 업데이트할 필요가 없습니다 // 우리가 just duration offset를 추가했기 때문에. } else { done = true; fraction = Math.min(fraction, 1.0f); } } if (mPlayingBackwards) { fraction = 1f - fraction; } animateValue(fraction); break; } return done; }
가상 기계 실행 엔진 동적 분배 원칙에 따라 여기서 ObjectAnimator의 animateValue 메서드를 호출합니다.
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); } }
이곳에서 주로 두 가지 일을 합니다,
그의 상위 클래스의 메서드는 다음과 같습니다:
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()); } } }
네, 여기서 수정된 속성 값을 볼 수 있습니다. 다음과 같은 네 가지 경우가 있습니다.
String propertyName, int... values 매개변수를 통해 생성된 객체에서 mIntProperty가 null이고 mProperty도 null입니다. 그렇다면 다른 두 가지는 어디서 왔을까요? 무엇이 빠졌는지 의심스럽습니다.
doAnimationFrame에서 startAnimation을 직접 호출하는 것은 아닙니다.63; 맞습니다, 바로 여기입니다.
startAnimation
이 메서드에서 initAnimation 메서드를 호출했습니다. 여전히 동적 분할 규칙에 따라, 여기서 ObjectAnimator의 initAnimation 메서드를 호출했습니다. 여기서 PropertyValuesHolder의 setupSetterAndGetter 메서드를 호출하고, 여기서 mSetter 등을 초기화했습니다. 더 이상 설명하지 않겠습니다. 코드를 직접 보세요.
좋습니다. 이제 Android에서의 속성 애니메이션에 대한 모든 내용이 끝났습니다. 이 문서의 내용이 Android 개발자 여러분에게 도움이 되길 바랍니다. 의문이 있으면 댓글을 남겨 주세요. 감사합니다.呐喊 교본에 대한 지지에 감사합니다.
선언: 이 문서의 내용은 인터넷에서 가져왔으며, 저작권은 원저자에게 있으며, 인터넷 사용자가 자발적으로 기여하고 업로드한 내용입니다. 이 사이트는 소유권을 가지지 않으며, 인공 편집을 하지 않았으며, 관련 법적 책임을 부담하지 않습니다. 저작권 침해가 의심되는 내용이 있으면 메일을 보내 주시기 바랍니다: notice#oldtoolbag.com(메일을 보내는 경우, #을 @으로 변경하십시오. 신고를 하고 관련 증거를 제공하시면, 사실이 확인되면 이 사이트는 즉시 저작권 침해 내용을 삭제합니다.