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

試用 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);
   }

觸控事件沒有特別實用。新型觸控 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,一律實作會傳回 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 以快速滑過方向快速移動來回應,那麼這個手勢就很合理,就如同使用者對飛輪做出推動而放慢速度一樣。

關於如何為捲動手勢加上動畫效果的說明文件,您可以詳細說明如何實作自己的骨架行為。但模擬飛輪的氛圍並非易事。需要大量物理和數學,才能確保飛輪模型正常運作。幸好,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() 會讀取目前時間並使用物理模型計算當時的「x」和「y」位置,藉此更新 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();