繪製 UI 只是建立自訂檢視區塊的其中一個環節。此外,您還需要讓檢視畫面回應使用者輸入內容,方式要與您模擬的實際動作非常相似。
讓應用程式中的物件像真實物件一樣運作。舉例來說,應用程式中的圖片不應突然消失,然後出現在其他位置,因為現實世界中的物件不會這樣。請改為將圖片從一個位置移至另一個位置。
使用者會感受到介面中細微的行為或感覺,並對模仿現實世界的細微之處做出最佳反應。舉例來說,當使用者甩動 UI 物件時,一開始應讓他們感覺到慣性,延遲動作。在動作結束時,讓他們感覺物體會繼續移動,不會在甩動結束時停止。
本頁說明如何使用 Android 架構的功能,將這些實際行為新增至自訂檢視區塊。
如需其他相關資訊,請參閱「輸入事件總覽」和「屬性動畫總覽」。
處理輸入手勢
如同許多其他 UI 架構,Android 支援輸入事件模型。使用者動作會變成觸發回呼的事件,您可以覆寫回呼,自訂應用程式回應使用者的方式。Android 系統中最常見的輸入事件是「觸控」,會觸發 onTouchEvent(android.view.MotionEvent)。覆寫這個方法來處理事件,如下所示:
Kotlin
override fun onTouchEvent(event: MotionEvent): Boolean { return super.onTouchEvent(event) }
Java
@Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
觸控事件本身並不是特別實用,新式觸控式 UI 會根據手勢定義互動,例如輕觸、拉動、推動、甩動和縮放。如要將原始觸控事件轉換為手勢,Android 會提供 GestureDetector。
傳遞導入 GestureDetector.OnGestureListener 的類別執行個體,即可建構 GestureDetector。如果只想處理幾種手勢,可以擴充 GestureDetector.SimpleOnGestureListener,而非實作 GestureDetector.OnGestureListener 介面。舉例來說,以下程式碼會建立擴充 GestureDetector.SimpleOnGestureListener 的類別,並覆寫 onDown(MotionEvent)。
Kotlin
private val myListener = object : GestureDetector.SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean { return true } } private val detector: GestureDetector = GestureDetector(context, myListener)
Java
class MyListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { return true; } } detector = new GestureDetector(getContext(), new MyListener());
無論是否使用 GestureDetector.SimpleOnGestureListener,請務必實作會傳回 true 的 onDown() 方法。這是必要步驟,因為所有手勢都會以 onDown() 訊息開頭。如果您從 onDown() 傳回 false (如 GestureDetector.SimpleOnGestureListener 所示),系統會假設您要忽略手勢的其餘部分,且不會呼叫 GestureDetector.OnGestureListener 的其他方法。如要忽略整個手勢,請只從 onDown() 傳回 false。
導入 GestureDetector.OnGestureListener 並建立 GestureDetector 的執行個體後,您可以使用 GestureDetector 解讀在 onTouchEvent() 中收到的觸控事件。
Kotlin
override fun onTouchEvent(event: MotionEvent): Boolean { return detector.onTouchEvent(event).let { result -> if (!result) { if (event.action == MotionEvent.ACTION_UP) { stopScrolling() true } else false } else true } }
Java
@Override public boolean onTouchEvent(MotionEvent event) { boolean result = detector.onTouchEvent(event); if (!result) { if (event.getAction() == MotionEvent.ACTION_UP) { stopScrolling(); result = true; } } return result; }
如果傳遞的觸控事件無法辨識為手勢的一部分,系統會傳回 false。onTouchEvent()接著,您就可以執行自己的自訂手勢偵測程式碼。
建立符合物理原理的動作
手勢是控制觸控螢幕裝置的強大方式,但除非能產生實際可行的結果,否則手勢可能不直覺且難以記憶。
舉例來說,假設您想實作水平輕拂手勢,讓在檢視區塊中繪製的項目繞著垂直軸旋轉。如果 UI 會快速朝撥動方向移動,然後減速,就像使用者推動飛輪並使其旋轉,那麼這個手勢就很有意義。
如需如何為捲動手勢製作動畫的說明文件,請參閱詳細說明,瞭解如何實作自己的捲動行為。但模擬飛輪的感覺並非易事。要讓飛輪模型正常運作,需要大量的物理和數學知識。幸好,Android 提供輔助類別來模擬這項和其他行為。Scroller 類別是處理飛輪式快速滑動手勢的基礎。
如要啟動快速滑動,請使用起始速度和快速滑動的 x 和 y 最小值和最大值呼叫 fling()。如要取得速度值,可以使用 GestureDetector 計算的值。
Kotlin
fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { scroller.fling( currentX, currentY, (velocityX / SCALE).toInt(), (velocityY / SCALE).toInt(), minX, minY, maxX, maxY ) postInvalidate() return true }
Java
@Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { scroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY); postInvalidate(); return true; }
呼叫 fling() 會設定甩動手勢的物理模型。之後,請定期呼叫 Scroller.computeScrollOffset() 更新 Scroller。computeScrollOffset() 會讀取目前時間,並使用物理模型計算該時間的 x 和 y 位置,藉此更新 Scroller 物件的內部狀態。呼叫 getCurrX() 和 getCurrY() 即可擷取這些值。
大多數檢視區塊會將 Scroller 物件的 x 和 y 位置直接傳遞至 scrollTo()。這個範例稍有不同:它會使用目前的捲動 x 位置,設定檢視區塊的旋轉角度。
Kotlin
scroller.apply { if (!isFinished) { computeScrollOffset() setItemRotation(currX) } }
Java
if (!scroller.isFinished()) { scroller.computeScrollOffset(); setItemRotation(scroller.getCurrX()); }
Scroller 類別會為您計算捲動位置,但不會自動將這些位置套用至檢視區塊。請經常套用新座標,讓捲動動畫看起來流暢。方法有兩種:
- 呼叫
fling()後,呼叫postInvalidate()強制重新繪製。這項技術需要您在onDraw()中計算捲動偏移,並在每次捲動偏移變更時呼叫postInvalidate()。 - 設定
ValueAnimator,在快速滑過期間製作動畫,並新增事件監聽器,藉由呼叫addUpdateListener()處理動畫更新。這項技術可讓您為View的屬性製作動畫。
讓轉移過程順利進行
使用者期望新式 UI 能在狀態之間順暢轉換:UI 元素淡入和淡出,而不是出現和消失;動作順暢開始和結束,而不是突然開始和停止。Android 的屬性動畫架構可簡化流暢轉場效果的實作程序。
如要使用動畫系統,每當屬性變更會影響檢視區塊外觀時,請勿直接變更屬性。請改用 ValueAnimator 進行變更。在下列範例中,修改檢視畫面中選取的子項元件,會使整個算繪檢視畫面旋轉,讓選取指標置中。ValueAnimator 會在數百毫秒內變更旋轉角度,而不是立即設定新的旋轉值。
Kotlin
autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0).apply { setIntValues(targetAngle) duration = AUTOCENTER_ANIM_DURATION start() }
Java
autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0); autoCenterAnimator.setIntValues(targetAngle); autoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION); autoCenterAnimator.start();
如果要變更的值是其中一個基本 View 屬性,動畫製作會更加簡單,因為檢視區塊內建 ViewPropertyAnimator,可同時為多個屬性製作動畫,如以下範例所示:
Kotlin
animate() .rotation(targetAngle) .duration = ANIM_DURATION .start()
Java
animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();