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

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

繪製 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,請務必實作會傳回 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;
}

如果傳遞的觸控事件無法辨識為手勢的一部分,系統會傳回 falseonTouchEvent()接著,您就可以執行自己的自訂手勢偵測程式碼。

建立符合物理原理的動作

手勢是控制觸控螢幕裝置的強大方式,但除非能產生實際可行的結果,否則手勢可能不直覺且難以記憶。

舉例來說,假設您想實作水平輕拂手勢,讓在檢視區塊中繪製的項目繞著垂直軸旋轉。如果 UI 會快速朝撥動方向移動,然後減速,就像使用者推動飛輪並使其旋轉,那麼這個手勢就很有意義。

如需如何為捲動手勢製作動畫的說明文件,請參閱詳細說明,瞭解如何實作自己的捲動行為。但模擬飛輪的感覺並非易事。要讓飛輪模型正常運作,需要大量的物理和數學知識。幸好,Android 提供輔助類別來模擬這項和其他行為。Scroller 類別是處理飛輪式快速滑動手勢的基礎。

如要啟動快速滑動,請使用起始速度和快速滑動的 xy 最小值和最大值呼叫 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() 更新 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();