속성 애니메이션(ObjectAnimator) 소스 분석
24098 단어 기술 지식
Paste_Image.png
속성 애니메이션 사용 코드 보기
ObjectAnimator animator=ObjectAnimator.ofFloat(image,"rotationX",0f,360f);
animator.setDuration(2000);//
animator.setInterpolator(new LinearInterpolator());//
animator.setRepeatCount(-1);//-1
animator.start();
ObjectAnimator 따라가기.ofFloat(image,"rotationX",0f,360f)
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
계속 anim에 들어갑니다.setFloatValues(values);
@Override
public void setFloatValues(float... 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.ofFloat(mProperty, values));
} else {
setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
}
} else {
super.setFloatValues(values);
}
}
PropertyValuesHolder를 시작합니다.ofFloat(mPropertyName, values)
public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
return new FloatPropertyValuesHolder(propertyName, values);
}
여기 Property Values Holder가 나왔는데 이런 종류는 뭘까요?
PropertyValuesHolder: 말 그대로 속성 값 소유자입니다. 이것은 애니메이션 과정에서 필요한 동작의 속성과 대응하는 값을 저장합니다. ofFloat(Object target, String property Name, float...values)로 구성된 애니메이션을 저장하고 ofFloat()의 내부 구현은 전송된 매개 변수를 PropertyValuesHolder 실례로 봉하여 애니메이션 상태를 저장합니다.PropertyValuesHolder 실례로 봉인된 후에도 다음 작업은PropertyValuesHolder를 위주로 한다
new Float Property Values Holder (property Name,values) 로 돌아가는 구조 방법을 보았습니다.
public FloatPropertyValuesHolder(String propertyName, float... values) {
super(propertyName);
setFloatValues(values);
}
// super(propertyName) PropertyValuesHolder
private PropertyValuesHolder(String propertyName) {
mPropertyName = propertyName;
}
setFloatValues(values)로 들어가기
@Override
public void setFloatValues(float... values) {
super.setFloatValues(values);
mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}
// super.setFloatValues(values);
public void setFloatValues(float... values) {
mValueType = float.class;
mKeyframes = KeyframeSet.ofFloat(values);
}
KeyframeSet.ofFloat(values); 키프레임: 키프레임이라는 뜻으로 키프레임을 설정한 후에 애니메이션은 각 키프레임 사이를 매끄럽게 전환할 수 있다. 키프레임은 두 개의 원소를 포함해야 한다. 첫 번째 시간, 두 번째 위치. 즉, 이 키프레임은 어떤 물체가 어느 시간에 어느 위치에 있어야 하는지를 나타낸다.fraction은 현재 진도를 표시하고value는 현재 위치를 표시합니다.
우리는 Key frame Set으로 들어간다.ofFloat(values)
public static KeyframeSet ofFloat(float... values) {
// values :
//ObjectAnimator.ofFloat(image,"rotationX",0f,360f); 0f ,360f
boolean badValue = false;
int numKeyframes = values.length;
// 2
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
//
if (numKeyframes == 1) {
// 0f
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
if (Float.isNaN(values[0])) {
badValue = true;
}
} else {
//
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
//
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] =
(FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
if (Float.isNaN(values[i])) {
badValue = true;
}
}
}
if (badValue) {
Log.w("Animator", "Bad value (NaN) in float animator");
}
// List
return new FloatKeyframeSet(keyframes);
}
이어서 new Float Key 프레임워크 세트(key 프레임워크)로 들어가기;구조 함수
public FloatKeyframeSet(FloatKeyframe... keyframes) {
super(keyframes);
}
// super(keyframes) KeyframeSet
class KeyframeSet implements Keyframes {
int mNumKeyframes;
Keyframe mFirstKeyframe;
Keyframe mLastKeyframe;
TimeInterpolator mInterpolator; // only used in the 2-keyframe case
List mKeyframes; // only used when there are not 2 keyframes
TypeEvaluator mEvaluator;
public KeyframeSet(Keyframe... keyframes) {
mNumKeyframes = keyframes.length;
// mKeyframes
mKeyframes = Arrays.asList(keyframes);
mFirstKeyframe = keyframes[0];
mLastKeyframe = keyframes[mNumKeyframes - 1];
mInterpolator = mLastKeyframe.getInterpolator();
}
위에서 보듯이 PropertyValuesHolder는 애니메이션 과정에서 필요한 조작의 속성과 대응하는 값을 저장하고 KeyframeSet을 가지고 있다.ofFloat(values)는 애니메이션의 프레임마다 집합됩니다
제가 속성 애니메이션의 다른 속성 설정을 다시 가볼게요.
//
@Override
@NonNull
public ObjectAnimator setDuration(long duration) {
super.setDuration(duration);
return this;
}
// super.setDuration(duration);
@Override
public ValueAnimator setDuration(long duration) {
if (duration < 0) {
throw new IllegalArgumentException("Animators cannot have negative duration: " +
duration);
}
mUnscaledDuration = duration;
updateScaledDuration();
return this;
}
// sDurationScale=1.0f
private void updateScaledDuration() {
mDuration = (long)(mUnscaledDuration * sDurationScale);
}
//
@Override
public void setInterpolator(TimeInterpolator value) {
if (value != null) {
mInterpolator = value;
} else {
//
mInterpolator = new LinearInterpolator();
}
}
마지막으로 포인트 start () 방법에 주목해 보도록 하겠습니다.
@Override
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, "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();
}
**super.start();** start()
@Override
public void start() {
start(false);
}
//
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
// Special case: reversing from seek-to-0 should act as if not seeked at all.
if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
if (mRepeatCount == INFINITE) {
// Calculate the fraction of the current iteration.
float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
mSeekFraction = 1 - fraction;
} else {
mSeekFraction = 1 + mRepeatCount - mSeekFraction;
}
}
mStarted = true;
mPaused = false;
mRunning = false;
// Resets mLastFrameTime when start() is called, so that if the animation was running,
// calling start() would put the animation in the
// started-but-not-yet-reached-the-first-frame phase.
mLastFrameTime = 0;
AnimationHandler animationHandler = AnimationHandler.getInstance();
// 16 this
animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
if (mStartDelay == 0 || mSeekFraction >= 0) {
// If there's no start delay, init the animation and notify start listeners right away
// to be consistent with the previous behavior. Otherwise, postpone this until the first
// frame after the start delay.
startAnimation();
if (mSeekFraction == -1) {
// No seek, start at play time 0. Note that the reason we are not using fraction 0
// is because for animations with 0 duration, we want to be consistent with pre-N
// behavior: skip to the final value immediately.
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
Paste_Image.png
위에서 보듯이ddAnimationFrameCallback()에 있는this설명은ValueAnimator도 이 인터페이스를 실현하고 이 추상적인 리셋 방법을 순환적으로 호출하는 것이다.
Paste_Image.png
과연 이 인터페이스가 이루어졌네요. 이 인터페이스에 어떤 추상적인 방법이 있는지 한번 보도록 하겠습니다.
// 2
interface AnimationFrameCallback {
void doAnimationFrame(long frameTime);
void commitAnimationFrame(long frameTime);
}
//
* Processes a frame of the animation, adjusting the start time if needed.
*
* @param frameTime The frame time.
* @return true if the animation has ended.
* @hide
*/
// ( 16 ) Vsync
public final void doAnimationFrame(long frameTime) {
AnimationHandler handler = AnimationHandler.getInstance();
if (mLastFrameTime == 0) {
// First frame
handler.addOneShotCommitCallback(this);
if (mStartDelay > 0) {
startAnimation();
}
if (mSeekFraction < 0) {
mStartTime = frameTime;
} else {
long seekTime = (long) (getScaledDuration() * mSeekFraction);
mStartTime = frameTime - seekTime;
mSeekFraction = -1;
}
mStartTimeCommitted = false; // allow start time to be compensated for jank
}
mLastFrameTime = frameTime;
if (mPaused) {
mPauseTime = frameTime;
handler.removeCallback(this);
return;
} else if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
// Offset by the duration that the animation was paused
mStartTime += (frameTime - mPauseTime);
mStartTimeCommitted = false; // allow start time to be compensated for jank
}
handler.addOneShotCommitCallback(this);
}
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);
if (finished) {
endAnimation();
}
}
boolean finished = animateBasedOnTime(currentTime);상대 시간을 절대 시간으로 변환하는 백분율
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
final long scaledDuration = getScaledDuration();
//( - )/
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
final float lastFraction = mOverallFraction;
final boolean newIteration = (int) fraction > (int) lastFraction;
final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
(mRepeatCount != INFINITE);
if (scaledDuration == 0) {
// 0 duration animator, ignore the repeat count and skip to the end
done = true;
} else if (newIteration && !lastIterationFinished) {
// Time to repeat
if (mListeners != null) {
//mListeners
int numListeners = mListeners.size();
for (int i = 0; i < numListeners; ++i) {
//
mListeners.get(i).onAnimationRepeat(this);
}
}
} else if (lastIterationFinished) {
done = true;
}
mOverallFraction = clampFraction(fraction);
float currentIterationFraction = getCurrentIterationFraction(mOverallFraction);
// ObjectAnimator animateValue()
animateValue(currentIterationFraction);
}
return done;
}
ObjectAnimator 하위 클래스로 들어가는 animateValue()
@CallSuper
@Override
// (0-1)
void animateValue(float fraction) {
final Object target = getTarget();
if (mTarget != null && target == null) {
// We lost the target reference, cancel and clean up.
cancel();
return;
}
// animateValue()
super.animateValue(fraction);
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
// View target
mValues[i].setAnimatedValue(target);
}
}
// animateValue();
@CallSuper
void animateValue(float fraction) {
// ( )
fraction = mInterpolator.getInterpolation(fraction);
//
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
// PropertyValuesHolder calculateValue();
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
//
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
//
public interface TimeInterpolator {
float getInterpolation(float input);
}
//PropertyValuesHolder calculateValue
void calculateValue(float fraction) {
Object value = mKeyframes.getValue(fraction);
//
mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}
// mAnimatedValue ObjectAnimator animateValue()
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
// View target
mValues[i].setAnimatedValue(target);
}
// mValues[i].setAnimatedValue(target);
void setAnimatedValue(Object target) {
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
try {
mTmpValueArray[0] = getAnimatedValue();
//mSetter startAnimator()
//invoke view
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
// mSetter ObjectAnimator
@CallSuper
@Override
void initAnimation() {
if (!mInitialized) {
// mValueType may change due to setter/getter setup; do this before calling super.init(),
// which uses mValueType to set up the default type evaluator.
final Object target = getTarget();
if (target != null) {
final int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setupSetterAndGetter(target);
}
}
super.initAnimation();
}
}
// mValues[i].setupSetterAndGetter(target); PropertyValuesHolder
void setupSetterAndGetter(Object target) {
mKeyframes.invalidateCache();
if (mProperty != null) {
// check to make sure that mProperty is on the class of target
try {
Object testValue = null;
List keyframes = mKeyframes.getKeyframes();
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++) {
Keyframe kf = keyframes.get(i);
if (!kf.hasValue() || kf.valueWasSetOnStart()) {
if (testValue == null) {
testValue = convertBack(mProperty.get(target));
}
kf.setValue(testValue);
kf.setValueWasSetOnStart(true);
}
}
return;
} catch (ClassCastException e) {
Log.w("PropertyValuesHolder","No such property (" + mProperty.getName() +
") on target object " + target + ". Trying reflection instead");
mProperty = null;
}
}
// We can't just say 'else' here because the catch statement sets mProperty to null.
if (mProperty == null) {
Class targetClass = target.getClass();
if (mSetter == null) {
setupSetter(targetClass);
}
List keyframes = mKeyframes.getKeyframes();
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++) {
Keyframe kf = keyframes.get(i);
if (!kf.hasValue() || kf.valueWasSetOnStart()) {
if (mGetter == null) {
setupGetter(targetClass);
if (mGetter == null) {
// Already logged the error - just return to avoid NPE
return;
}
}
try {
Object value = convertBack(mGetter.invoke(target));
kf.setValue(value);
kf.setValueWasSetOnStart(true);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
}
}
: if (mSetter == null) {setupSetter(targetClass);}
private void setupGetter(Class targetClass) {
mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null);
}
//
private Method setupSetterOrGetter(Class targetClass,
HashMap> propertyMapMap,
String prefix, Class valueType) {
Method setterOrGetter = null;
synchronized(propertyMapMap) {
// Have to lock property map prior to reading it, to guard against
// another thread putting something in there after we've checked it
// but before we've added an entry to it
HashMap propertyMap = propertyMapMap.get(targetClass);
boolean wasInMap = false;
if (propertyMap != null) {
wasInMap = propertyMap.containsKey(mPropertyName);
if (wasInMap) {
setterOrGetter = propertyMap.get(mPropertyName);
}
}
if (!wasInMap) {
setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
if (propertyMap == null) {
propertyMap = new HashMap();
propertyMapMap.put(targetClass, propertyMap);
}
propertyMap.put(mPropertyName, setterOrGetter);
}
}
return setterOrGetter;
}
// : setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
// TODO: faster implementation...
Method returnVal = null;
String methodName = getMethodName(prefix, mPropertyName);
Class args[] = null;
if (valueType == null) {
try {
//
returnVal = targetClass.getMethod(methodName, args);
} catch (NoSuchMethodException e) {
// Swallow the error, log it later
}
} else {
args = new Class[1];
Class typeVariants[];
if (valueType.equals(Float.class)) {
typeVariants = FLOAT_VARIANTS;
} else if (valueType.equals(Integer.class)) {
typeVariants = INTEGER_VARIANTS;
} else if (valueType.equals(Double.class)) {
typeVariants = DOUBLE_VARIANTS;
} else {
typeVariants = new Class[1];
typeVariants[0] = valueType;
}
for (Class typeVariant : typeVariants) {
args[0] = typeVariant;
try {
returnVal = targetClass.getMethod(methodName, args);
if (mConverter == null) {
// change the value type to suit
mValueType = typeVariant;
}
return returnVal;
} catch (NoSuchMethodException e) {
// Swallow the error and keep trying other variants
}
}
// If we got here, then no appropriate function was found
}
if (returnVal == null) {
Log.w("PropertyValuesHolder", "Method " +
getMethodName(prefix, mPropertyName) + "() with type " + valueType +
" not found on target class " + targetClass);
}
return returnVal;
}
view.setScrollX() view.setScrollY()
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Android 개발 에서 자주 사용 하 는 디자인 모델 - 동적 에이전트 모델그 중에서 Square 회사 의 OkHttp 는 완벽 한 네트워크 요청 라 이브 러 리 이 고 그 위 에 Retrofit 라 이브 러 리 를 포장 하여 Restful Api 를 편리 하고 신속하게 호출 하 는 데 지...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.