Android如何使得View可交互

2018-08-02 18:13 更新

編寫:kesenhoo - 原文:http://developer.android.com/training/custom-view/make-interactive.html

繪制UI僅僅是創(chuàng)建自定義View的一部分。你還需要使得你的View能夠以模擬現(xiàn)實世界的方式來進(jìn)行反饋。對象應(yīng)該總是與現(xiàn)實情景能夠保持一致。例如,圖片不應(yīng)該突然消失又從另外一個地方出現(xiàn),因為在現(xiàn)實世界里面不會發(fā)生那樣的事情。正確的應(yīng)該是,圖片從一個地方移動到另外一個地方。

用戶應(yīng)該可以感受到UI上的微小變化,并對模仿現(xiàn)實世界的細(xì)微之處反應(yīng)強烈。例如,當(dāng)用戶fling(迅速滑動)一個對象時,應(yīng)該在開始時感到摩擦帶來的阻力,在結(jié)束時感到fling帶動的動力。應(yīng)該在滑動開始與結(jié)束的時候給用戶一定的反饋。

這節(jié)課會演示如何使用Android framework的功能來為自定義的View添加那些現(xiàn)實世界中的行為。

處理輸入的手勢

像許多其他UI框架一樣,Android提供一個輸入事件模型。用戶的動作會轉(zhuǎn)換成觸發(fā)一些回調(diào)函數(shù)的事件,你可以重寫這些回調(diào)方法來定制你的程序應(yīng)該如何響應(yīng)用戶的輸入事件。在Android中最常用的輸入事件是touch,它會觸發(fā)onTouchEvent(android.view.MotionEvent))的回調(diào)。重寫這個方法來處理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òu)建一個GestureDetector。如果你只是想要處理幾種gestures(手勢操作)你可以繼承GestureDetector.SimpleOnGestureListener,而不用實現(xiàn)GestureDetector.OnGestureListener接口。例如,下面的代碼創(chuàng)建一個繼承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, 你必須總是實現(xiàn)onDown()方法,并返回true。這一步是必須的,因為所有的gestures都是從onDown()開始的。如果你在onDown()里面返回false,系統(tǒng)會認(rèn)為你想要忽略后續(xù)的gesture,那么GestureDetector.OnGestureListener的其他回調(diào)方法就不會被執(zhí)行到了。一旦你實現(xiàn)了GestureDetector.OnGestureListener并且創(chuàng)建了GestureDetector的實例, 你可以使用你的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)你傳遞一個touch事件到onTouchEvent()時,若這個事件沒有被辨認(rèn)出是何種gesture,它會返回false。你可以執(zhí)行自定義的gesture-decection代碼。

創(chuàng)建基本合理的物理運動

Gestures是控制觸摸設(shè)備的一種強有力的方式,但是除非你能夠產(chǎn)出一個合理的觸摸反饋,否則將是違反用戶直覺的。一個很好的例子是fling手勢,用戶迅速的在屏幕上移動手指然后抬手離開屏幕。這個手勢應(yīng)該使得UI迅速的按照fling的方向進(jìn)行滑動,然后慢慢停下來,就像是用戶旋轉(zhuǎn)一個飛輪一樣。

但是模擬這個飛輪的感覺并不簡單,要想得到正確的飛輪模型,需要大量的物理,數(shù)學(xué)知識。幸運的是,Android有提供幫助類來模擬這些物理行為。Scroller是控制飛輪式的fling的基類。

要啟動一個fling,需調(diào)用fling(),并傳入啟動速率、x、y的最小值和最大值,對于啟動速度值,可以使用GestureDetector計算得出。

@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來計算的,許多開發(fā)者感覺使用這個值使得fling動畫太快。通常把x與y設(shè)置為4到8倍的關(guān)系。

調(diào)用fling())時會為fling手勢設(shè)置物理模型。然后,通過調(diào)用定期調(diào)用 Scroller.computeScrollOffset())來更新Scroller。computeScrollOffset())通過讀取當(dāng)前時間和使用物理模型來計算x和y的位置更新Scroller對象的內(nèi)部狀態(tài)。調(diào)用getCurrX())和getCurrY())來獲取這些值。

大多數(shù)view通過Scroller對象的x,y的位置直接到scrollTo()),PieChart例子稍有不同,它使用當(dāng)前滾動y的位置設(shè)置圖表的旋轉(zhuǎn)角度。

if (!mScroller.isFinished()) {
    mScroller.computeScrollOffset();
    setPieRotation(mScroller.getCurrY());
}

Scroller 類會為你計算滾動位置,但是他不會自動把哪些位置運用到你的view上面。你有責(zé)任確保View獲取并運用到新的坐標(biāo)。你有兩種方法來實現(xiàn)這件事情:

  • 在調(diào)用fling()之后執(zhí)行postInvalidate(), 這是為了確保能強制進(jìn)行重畫。這個技術(shù)需要每次在onDraw里面計算過scroll offsets(滾動偏移量)之后調(diào)用postInvalidate()。
  • 使用ValueAnimator在fling是展現(xiàn)動畫,并且通過調(diào)用addUpdateListener()增加對fling過程的監(jiān)聽。

這個PieChart 的例子使用了第二種方法。這個方法使用起來會稍微復(fù)雜一點,但是它更有效率并且避免了不必要的重畫的view進(jìn)行重繪。缺點是ValueAnimator是從API Level 11才有的。因此他不能運用到3.0的系統(tǒng)之前的版本上。

Note: ValueAnimator雖然是API 11才有的,但是你還是可以在最低版本低于3.0的系統(tǒng)上使用它,做法是在運行時判斷當(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();
         }
     }
 });

使過渡平滑

用戶期待一個UI之間的切換是能夠平滑過渡的。UI元素需要做到漸入淡出來取代突然出現(xiàn)與消失。Android從3.0開始有提供property animation framework,用來使得平滑過渡變得更加容易。

使用這套動畫系統(tǒng)時,任何時候?qū)傩缘母淖兌紩绊懙侥愕囊晥D,所以不要直接改變屬性的值。而是使用ValueAnimator來實現(xiàn)改變。在下面的例子中,在PieChart 中更改選擇的部分將導(dǎo)致整個圖表的旋轉(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 ,它能夠同時執(zhí)行多個屬性的動畫。

animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號