繪製 UI 只是建立自訂檢視區塊的一環,您還需要讓檢視畫面回應使用者輸入內容,呈現方式必須與您模仿的實際動作極為相似。
讓應用程式中的物件像實際物件一樣運作。舉例來說,請不要讓應用程式中的圖片從存在中顯現而再次顯示在其他地方,因為實際環境中的物件並不會這麼做。而是應將圖片從一個位置移至其他位置。
使用者甚至能感測介面上的細微行為或感受,並對於模仿真實世界的細微差異做出改進。舉例來說,當使用者快速滑過 UI 物件時,請在一開始就讓他們知道會延遲動作的慣性。在運動的結尾,讓玩家感受到將物體運送到快速滑過之外的動能。
本頁面說明如何使用 Android 架構的功能,將這些實際行為新增至自訂檢視區塊。
如需其他相關資訊,請參閱「輸入事件總覽」和「屬性動畫總覽」。
處理輸入手勢
如同許多其他 UI 架構,Android 也支援輸入事件模型。使用者動作會變成觸發回呼的事件,您可以覆寫回呼,自訂應用程式回應使用者的方式。Android 系統中最常見的輸入事件是「Touch」,觸發 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); }
觸控事件本身沒有特別實用。新型觸控使用者介面定義了手勢的互動,例如輕觸、提取、推送、快速滑過和縮放。如要將原始觸控事件轉換為手勢,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; }
如果您向 onTouchEvent()
傳遞觸控事件,且該事件無法辨識為手勢的一部分,則會傳回 false
。然後您就可以執行自己的自訂手勢偵測程式碼。
創造出實際可行的動作
手勢是控制觸控螢幕裝置的強大方式,但由於手勢不易理解,除非產生實際可行的結果,否則這種手勢可能會違反直覺,而且難以記住。
例如,假設您想要實作水平快速滑過手勢,設定在檢視畫面中繪製的項目在垂直軸上旋轉。這個手勢適合 UI 回應時,判斷 UI 是沿著快速滑過方向快速移動,接著減慢速度,就像使用者按下飛輪再轉動一樣。
請參閱有關如何為捲動手勢建立動畫的說明文件,詳細說明如何實作自己的美術拼貼行為。但模擬飛輪的感受並不容易。因此需要大量的物理和數學運算
飛輪模型才能正常運作幸運的是,Android 提供輔助類別來模擬這種和其他行為。Scroller
類別是處理飛輪式快速滑過手勢的基礎。
如要開始快速滑過,請呼叫 fling()
,指定起始速度以及快速滑過速度的最小和最大 x 和 y 值。對於速率值,您可以使用 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();