編寫:kesenhoo - 原文:http://developer.android.com/training/custom-view/make-interactive.html
繪制UI僅僅是創(chuàng)建自定義View的一部分。你還需要使得你的View能夠以模擬現(xiàn)實(shí)世界的方式來進(jìn)行反饋。對(duì)象應(yīng)該總是與現(xiàn)實(shí)情景能夠保持一致。例如,圖片不應(yīng)該突然消失又從另外一個(gè)地方出現(xiàn),因?yàn)樵诂F(xiàn)實(shí)世界里面不會(huì)發(fā)生那樣的事情。正確的應(yīng)該是,圖片從一個(gè)地方移動(dòng)到另外一個(gè)地方。
用戶應(yīng)該可以感受到UI上的微小變化,并對(duì)模仿現(xiàn)實(shí)世界的細(xì)微之處反應(yīng)強(qiáng)烈。例如,當(dāng)用戶fling(迅速滑動(dòng))一個(gè)對(duì)象時(shí),應(yīng)該在開始時(shí)感到摩擦帶來的阻力,在結(jié)束時(shí)感到fling帶動(dòng)的動(dòng)力。應(yīng)該在滑動(dòng)開始與結(jié)束的時(shí)候給用戶一定的反饋。
這節(jié)課會(huì)演示如何使用Android framework的功能來為自定義的View添加那些現(xiàn)實(shí)世界中的行為。
像許多其他UI框架一樣,Android提供一個(gè)輸入事件模型。用戶的動(dòng)作會(huì)轉(zhuǎn)換成觸發(fā)一些回調(diào)函數(shù)的事件,你可以重寫這些回調(diào)方法來定制你的程序應(yīng)該如何響應(yīng)用戶的輸入事件。在Android中最常用的輸入事件是touch,它會(huì)觸發(fā)onTouchEvent(android.view.MotionEvent)的回調(diào)。重寫這個(gè)方法來處理touch事件:
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
Touch事件本身并不是特別有用。如今的touch UI定義了touch事件之間的相互作用,叫做gestures。例如tapping,pulling,flinging與zooming。為了把那些touch的源事件轉(zhuǎn)換成gestures, Android提供了GestureDetector。
通過傳入GestureDetector.OnGestureListener的一個(gè)實(shí)例構(gòu)建一個(gè)GestureDetector。如果你只是想要處理幾種gestures(手勢(shì)操作)你可以繼承GestureDetector.SimpleOnGestureListener,而不用實(shí)現(xiàn)GestureDetector.OnGestureListener接口。例如,下面的代碼創(chuàng)建一個(gè)繼承GestureDetector.SimpleOnGestureListener的類,并重寫onDown(MotionEvent)。
class mListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
return true;
}
}
mDetector = new GestureDetector(PieChart.this.getContext(), new mListener());
不管你是否使用GestureDetector.SimpleOnGestureListener, 你必須總是實(shí)現(xiàn)onDown()方法,并返回true。這一步是必須的,因?yàn)樗械膅estures都是從onDown()開始的。如果你在onDown()里面返回false,系統(tǒng)會(huì)認(rèn)為你想要忽略后續(xù)的gesture,那么GestureDetector.OnGestureListener的其他回調(diào)方法就不會(huì)被執(zhí)行到了。一旦你實(shí)現(xiàn)了GestureDetector.OnGestureListener并且創(chuàng)建了GestureDetector的實(shí)例, 你可以使用你的GestureDetector來中止你在onTouchEvent里面收到的touch事件。
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = mDetector.onTouchEvent(event);
if (!result) {
if (event.getAction() == MotionEvent.ACTION_UP) {
stopScrolling();
result = true;
}
}
return result;
}
當(dāng)你傳遞一個(gè)touch事件到onTouchEvent()時(shí),若這個(gè)事件沒有被辨認(rèn)出是何種gesture,它會(huì)返回false。你可以執(zhí)行自定義的gesture-decection代碼。
Gestures是控制觸摸設(shè)備的一種強(qiáng)有力的方式,但是除非你能夠產(chǎn)出一個(gè)合理的觸摸反饋,否則將是違反用戶直覺的。一個(gè)很好的例子是fling手勢(shì),用戶迅速的在屏幕上移動(dòng)手指然后抬手離開屏幕。這個(gè)手勢(shì)應(yīng)該使得UI迅速的按照fling的方向進(jìn)行滑動(dòng),然后慢慢停下來,就像是用戶旋轉(zhuǎn)一個(gè)飛輪一樣。
但是模擬這個(gè)飛輪的感覺并不簡(jiǎn)單,要想得到正確的飛輪模型,需要大量的物理,數(shù)學(xué)知識(shí)。幸運(yùn)的是,Android有提供幫助類來模擬這些物理行為。Scroller是控制飛輪式的fling的基類。
要啟動(dòng)一個(gè)fling,需調(diào)用fling(),并傳入啟動(dòng)速率、x、y的最小值和最大值,對(duì)于啟動(dòng)速度值,可以使用GestureDetector計(jì)算得出。
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mScroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY);
postInvalidate();
}
Note: 盡管速率是通過GestureDetector來計(jì)算的,許多開發(fā)者感覺使用這個(gè)值使得fling動(dòng)畫太快。通常把x與y設(shè)置為4到8倍的關(guān)系。
調(diào)用[fling()](http://developer.android.com/reference/android/widget/Scroller.html#fling(int, int, int, int, int, int, int, int))時(shí)會(huì)為fling手勢(shì)設(shè)置物理模型。然后,通過調(diào)用定期調(diào)用 [Scroller.computeScrollOffset()](http://developer.android.com/reference/android/widget/Scroller.html#computeScrollOffset())來更新Scroller。[computeScrollOffset()](http://developer.android.com/reference/android/widget/Scroller.html#computeScrollOffset())通過讀取當(dāng)前時(shí)間和使用物理模型來計(jì)算x和y的位置更新Scroller對(duì)象的內(nèi)部狀態(tài)。調(diào)用[getCurrX()](http://developer.android.com/reference/android/widget/Scroller.html#getCurrX())和[getCurrY()](http://developer.android.com/reference/android/widget/Scroller.html#getCurrY())來獲取這些值。
大多數(shù)view通過Scroller對(duì)象的x,y的位置直接到[scrollTo()](http://developer.android.com/reference/android/view/View.html#scrollTo(int, int)),PieChart例子稍有不同,它使用當(dāng)前滾動(dòng)y的位置設(shè)置圖表的旋轉(zhuǎn)角度。
if (!mScroller.isFinished()) {
mScroller.computeScrollOffset();
setPieRotation(mScroller.getCurrY());
}
Scroller 類會(huì)為你計(jì)算滾動(dòng)位置,但是他不會(huì)自動(dòng)把哪些位置運(yùn)用到你的view上面。你有責(zé)任確保View獲取并運(yùn)用到新的坐標(biāo)。你有兩種方法來實(shí)現(xiàn)這件事情:
這個(gè)PieChart 的例子使用了第二種方法。這個(gè)方法使用起來會(huì)稍微復(fù)雜一點(diǎn),但是它更有效率并且避免了不必要的重畫的view進(jìn)行重繪。缺點(diǎn)是ValueAnimator是從API Level 11才有的。因此他不能運(yùn)用到3.0的系統(tǒng)之前的版本上。
Note: ValueAnimator雖然是API 11才有的,但是你還是可以在最低版本低于3.0的系統(tǒng)上使用它,做法是在運(yùn)行時(shí)判斷當(dāng)前的API Level,如果低于11則跳過。
mScroller = new Scroller(getContext(), null, true);
mScrollAnimator = ValueAnimator.ofFloat(0,1);
mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
if (!mScroller.isFinished()) {
mScroller.computeScrollOffset();
setPieRotation(mScroller.getCurrY());
} else {
mScrollAnimator.cancel();
onScrollFinished();
}
}
});
用戶期待一個(gè)UI之間的切換是能夠平滑過渡的。UI元素需要做到漸入淡出來取代突然出現(xiàn)與消失。Android從3.0開始有提供property animation framework,用來使得平滑過渡變得更加容易。
使用這套動(dòng)畫系統(tǒng)時(shí),任何時(shí)候?qū)傩缘母淖兌紩?huì)影響到你的視圖,所以不要直接改變屬性的值。而是使用ValueAnimator來實(shí)現(xiàn)改變。在下面的例子中,在PieChart 中更改選擇的部分將導(dǎo)致整個(gè)圖表的旋轉(zhuǎn),以至選擇的進(jìn)入選擇區(qū)內(nèi)。ValueAnimator在數(shù)百毫秒內(nèi)改變旋轉(zhuǎn)量,而不是突然地設(shè)置新的旋轉(zhuǎn)值。
mAutoCenterAnimator = ObjectAnimator.ofInt(PieChart.this, "PieRotation", 0);
mAutoCenterAnimator.setIntValues(targetAngle);
mAutoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);
mAutoCenterAnimator.start();
如果你想改變的是view的某些基礎(chǔ)屬性,你可以使用ViewPropertyAnimator ,它能夠同時(shí)執(zhí)行多個(gè)屬性的動(dòng)畫。
animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();