將自訂檢視畫面設為互動式

嘗試 Compose 方法
Jetpack Compose 是 Android 推薦的 UI 工具包。瞭解如何在 Compose 中處理版面配置。

繪製 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,請一律實作會傳回 trueonDown() 方法。由於所有手勢都開頭為 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(),指定起始速度以及快速滑過速度的最小和最大 xy 值。對於速率值,您可以使用 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() 以更新 ScrollercomputeScrollOffset() 會讀取目前時間並使用物理模型計算當時的 xy 位置,藉此更新 Scroller 物件的內部狀態。呼叫 getCurrX()getCurrY() 擷取這些值。

大多數檢視畫面會將 Scroller 物件的 xy 位置直接傳遞至 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();