Android中的動畫和原理(屬性動畫)
1、屬性動畫
屬性動畫通過改變物件的屬性來展示的動畫效果,補間動畫只是設定當前View在區域內移動,產生的動畫效果,其實原View的還在原地,沒有發生改變。
但屬性動畫改變了物件的屬性。也就是改變了物件的顏色,位置,寬高等。
2、示例
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = (ImageView) findViewById(R.id.imageView);
ObjectAnimator translationX = new ObjectAnimator().ofFloat(imageView,"translationX",0,600f);
ObjectAnimator translationY = new ObjectAnimator().ofFloat(imageView,"translationY",0,0);
AnimatorSet animatorSet = new AnimatorSet(); //組合動畫
animatorSet.setInterpolator(new LinearInterpolator()); //設定時間插值器
animatorSet.playTogether(translationX,translationY); //設定動畫
animatorSet.setDuration(3000); //設定動畫時間
animatorSet.start(); //啟動
}
}
3、插值器和估值器
1)下圖描述了一個物件,該物件的X屬性,表示螢幕上的水平位置。動畫的持續時間設定為40毫秒,移動的距離為40畫素。每10毫秒,該物件是預設幀重新整理率,物件水平移動10畫素。在40ms結束時,動畫停止,物件在水平位置40結束。這是一個線性插值器動畫的例子,以恆定速度移動。
2)還可以指定動畫以進行非線性插值。圖2示出了在動畫開始時加速的物件,並在動畫結束時減速。物件仍然在40毫秒內移動40個畫素,但非線性。開始時,動畫加速,然後減速直到動畫結束。如圖2所示,動畫的開始和結束的距離小於中間的距離。
4、屬性動畫
屬性動畫的重要元件
1)ValueAnimator追蹤物件,比如物件的執行時間,當前的屬性值。
2)ValueAnimator封裝了TimeInterpolator,它定義了動畫的插值演算法,而且定義了一個TypeEvaluator,它計算了如何去計算屬性動畫。
3)要開始一個動畫,首先需建立一個ValueAnimator並且要定義動畫的屬性的開始和結束的值,還有動畫持續的時間。當呼叫start() 時,動畫就開始了。在動畫進行時,ValueAnimator 跟據動畫的持續時間和已經過的時間,計算出一個百分比(0和1之間),進度分數代表了動畫已進行的時間的百分比,0代表0%,1代表100%。例如,圖1中進度分數 在t = 10 ms時值為0.25,因為總時間是t = 40 ms。
4)當ValueAnimator 計算完成動畫的進度時,呼叫TimeInterpolator 去計算一個插值分數。插入分數結合所設定的時間插值把進度分數對映到一個新的分數。例如,在圖2中,因為動畫緩慢加速,在 t = 10 ms時,插值分數為0.15,小於進度分數為0.25。在圖1中,插值分數進度分數永遠相等。
5)計算插值函式時,ValueAnimator 會呼叫適當的TypeEvaluator來基於插值函式、開始值、結束值計算你在動畫的屬性的值。例如,在圖2中,插值函式值在 t = 10 ms時為0.15 ,所以些時屬性的值是6。
5、屬性動畫原理
1、start()方法動畫啟動
@Override
public void start() {
AnimationHandler.getInstance().autoCancelBasedOn(this);
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()方法。
2、start()方法
/**
* Start the animation playing. This version of start() takes a boolean flag that indicates
* whether the animation should play in reverse. The flag is usually false, but may be set
* to true if called from the reverse() method.
*
* <p>The animation started by calling this method will be run on the thread that called
* this method. This thread should have a Looper on it (a runtime exception will be thrown if
* this is not the case). Also, if the animation will animate
* properties of objects in the view hierarchy, then the calling thread should be the UI
* thread for that view hierarchy.</p>
*
* @param playBackwards Whether the ValueAnimator should start playing in reverse.
*/
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;
mAnimationEndRequested = 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();
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方法,啟動動畫
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);
}
}
}
可以看到,實際上動畫的執行,是呼叫父類的start()方法。
如果沒有動畫延遲,則開始執行動畫,並設定監聽事件。則呼叫startAnimation()開啟動畫
3)進入到startAnimation()方法
private void startAnimation() {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
System.identityHashCode(this));
}
mAnimationEndRequested = false;
initAnimation();
mRunning = true;
if (mSeekFraction >= 0) {
mOverallFraction = mSeekFraction;
} else {
mOverallFraction = 0f;
}
if (mListeners != null) {
notifyStartListeners();
}
}
很遺憾,在這它又只是做了初始化動畫方法,接著進入到initAnimation()
4)最終,進入到initAnimation()方法
/**
* This function is called immediately before processing the first animation
* frame of an animation. If there is a nonzero <code>startDelay</code>, the
* function is called after that delay ends.
* It takes care of the final initialization steps for the
* animation.
*
* <p>Overrides of this method should call the superclass method to ensure
* that internal mechanisms for the animation are set up correctly.</p>
*/
@CallSuper
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//計算每幀動畫的屬性值
mValues[i].init();
}
mInitialized = true;
}
}
上面程式碼中mValues[i].init()方法便是計算動畫的屬性值,那麼它通過什麼來計算呢?先看mValues,屬性動畫的集合
/**
* The property/value sets being animated.
*/
PropertyValuesHolder[] mValues;
也就是我們在定義屬性動畫的時候,有set方法,我們通過set方法來給動畫設定屬性,最張一步一步的完成動畫的。