繪製 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); }
觸控事件沒有特別實用。新型觸控 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; }
如果傳遞 onTouchEvent()
的觸控事件不是手勢的一部分,就會傳回 false
。這樣一來,您就可以執行自訂的手勢偵測程式碼。
製作實際可行的動態效果
手勢是控制觸控螢幕裝置的有效方式,但若不實際產生實際結果,則可能不符合直覺且難以記住。
舉例來說,假設您想實作水平快速滑過手勢,可設定在垂直軸周圍旋轉的檢視畫面所繪製的項目。如果 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();