響應視窗屬性動畫 | 讓您的軟鍵盤動起來 (二)

響應視窗屬性動畫 | 讓您的軟鍵盤動起來 (二)

在上一篇文章中,咱們介紹了全部關於 "邊到邊" (edge-to-edge) 的 API 改動: 讓您的軟鍵盤動起來。在這篇文章中,咱們會繼續跟進軟鍵盤動畫這一實際任務。爲了展現能夠實現的效果,您能夠查看下面這個來自同一個應用的示例,左邊的是運行在 Android 10 上,而右邊的是運行在 Android 11 上 (動畫效果是實際速度的 20%):java

如上動圖所示: 在 Android 10 以及之前版本的設備上,當用戶點擊文字輸入框來輸入回覆,軟鍵盤會帶着動畫效果移動到預期的位置,可是應用在兩個狀態間的動畫很突兀。這是一個您在設備上已經看過好久的效果,降慢速度到實際速度的 20% 使得它更爲明顯。android

您能夠在右邊看到相同的場景運行在 Android 11 上的效果。這一次,當用戶點擊文字輸入框的時候,應用跟隨着軟鍵盤一塊兒移動而且創造了一個更流暢的體驗。git

因此您如何才能在您的應用中添加這種體驗呢?這都依賴新 API 的支持...github

WindowInsetsAnimation 類

在 Android 11 中支持實現這種效果的 API 就是新的 WindowInsetsAnimation 類,它包含一個涉及視窗屬性的動畫。應用能夠經過 WindowInsetsAnimation.Callback 類監聽各類動畫事件,這個回調能夠被設置到一個視圖上:app

val cb = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
    // TODO
}

view.setWindowInsetsAnimationCallback(cb)

讓咱們來看一下這個回調類,以及它提供的方法:ide

想象一下當前軟鍵盤是關閉的,用戶剛剛點擊了 EditText。系統如今立刻要顯示軟鍵盤,因爲咱們已經設置了 WindowInsetsAnimation.Callback,咱們會按順序收到以下的調用:函數

val cb = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {

    override fun onPrepare(animation: WindowInsetsAnimation) {
        // #1: 第一,onPrepare 被調用會容許應用記錄當前佈局的任何狀態
    }

    // #2: 在 onPrepare 以後,正常的 WindowInsets 會被下發到視圖層次
    // 結構中,它包含告終束狀態。這意味着您的視圖的 
    // OnApplyWindowInsetsListener 會被調用,這會致使一個佈局傳遞
    // 以反映結束狀態

    override fun onStart(
        animation: WindowInsetsAnimation,
        bounds: WindowInsetsAnimation.Bounds
    ):  WindowInsetsAnimation.Bounds {

        // #3: 接下來是 onStart ,這個會在動畫開始的時候被調用。
        // 這容許應用記錄下視圖的目標狀態或者結束狀態
        return bounds
    }

    override fun onProgress(
      insets: WindowInsets,
      runningAnimations: List<WindowInsetsAnimation>
    ): WindowInsets {

        // #4: 接下來是一個很重要的調用:onProgress 。這個會在動畫中每次視窗屬性
        // 更改的時候被調用。在軟鍵盤的這個例子中,這個調用會發生在軟鍵盤在屏幕
        // 上滑動的時候。
        return insets
    }

    override fun onEnd (animation: WindowInsetsAnimation) {

        // #5: 最後 onEnd 在動畫已經結束的時候被調用。使用這個來
        // 清理任何舊的狀態。
    }
}

這就是回調在理論上是如何工做的,如今讓咱們在場景中實踐一下...佈局

實現示例

咱們會使用 WindowInsetsAnimation.Callback 來實如今文章開頭您看到的示例。讓咱們從實現咱們的回調函數開始:動畫

onPrepare() 方法

首先咱們要複寫 onPrepare()),而且在其餘佈局改變發生以前記錄下視圖的底部座標:google

val view = binding.conversationList

val cb = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
    var startBottom = 0
    var endBottom = 0

    override fun onPrepare(animation: WindowInsetsAnimation) {
        // #1: 首先 onPrepare 被調用,這容許應用記錄下當前佈局中的任何視圖狀態。
        // 咱們要記錄下這個視圖在視窗中的底部座標。
        startBottom = view.calculateBottomInWindow()
    }
}

屬性分發

這時候結束狀態的屬性會被分發,而咱們的 OnApplyWindowInsetsListener 會被調用,監聽器會更新容器視圖的內邊距,這會致使內容被推上去。

然而用戶不會看到這個以下圖所示的狀態。

onStart() 方法

接下來咱們實現 onStart()) 方法,這會讓咱們先記錄下這個視圖結束時候的位置。

咱們利用 translationY 在視覺上將視圖移動回初始位置,由於咱們不想如今就讓用戶看到結束狀態。因爲系統保證了任何由視窗屬性變動致使的從新佈局都會在 onStart() 的同一幀被調用,因此用戶此時不會看到閃動。

val view = binding.conversationList

val cb = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
    var startBottom = 0
    var endBottom = 0

    override fun onStart(
        animation: WindowInsetsAnimation,
        bounds: WindowInsetsAnimation.Bounds
    ):  WindowInsetsAnimation.Bounds {
        // #3: 接下來是 onStart,它會在動畫開始的時候被調用      
        // 咱們記錄下視窗中視圖的底部
        endBottom = view.calculateBottomInWindow()
        
        // 而後咱們移動視圖回到它視覺上的初始位置
        view.translationY = startBottom - endBottom
      
        // 咱們不會更改邊界,因此咱們會返回傳入的邊界值
        return bounds
    }
}

onProgress() 方法

最後咱們要複寫 onProgress()) 方法,這會讓咱們能夠在軟鍵盤滑入的時候更新咱們的視圖。

咱們會在起始和結束狀態之間插值,並再次使用 translationY 使得視圖能夠和軟鍵盤一塊兒移動。

val view = binding.conversationList

val cb = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
    var startBottom = 0
    var endBottom = 0

    override fun onProgress(
      insets: WindowInsets,
      runningAnimations: List<WindowInsetsAnimation>
    ): WindowInsets {

        // #4: 接下來是最重要的調用:onProgress
        // 它會在動畫中每次視窗屬性改變的時候被調用。

        // 從起始位置到結束位置,咱們利用線性插值的方式和動畫自己的分數
        // 來計算視圖的偏移量。
        val offset = lerp(
            startBottom - endBottom,
            0,
            animation.interpolatedFraction
        )
        // … 而後咱們再用 translationY 來設置
        view.translationY = offset

        return insets
    }
}

軟鍵盤的協同效果

使用這個方法,咱們已經實現了軟鍵盤和應用視圖的同步。若是您想查看完整的實現,請查閱 WindowInsetsAnimation 的示例: android/user-interface-samples

若是您在您的應用中添加了上述實現,請在下方評論區留言告訴咱們您的使用感覺。在下一篇文章中,咱們會繼續探索如何能讓您的應用控制軟鍵盤,好比在滾動列表的時候自動打開軟鍵盤。

視圖裁剪

若是您在您的視圖上嘗試咱們在這篇文章中介紹的方法,您可能會發現視圖在移動的過程當中被裁剪了。這是由於咱們在移動視圖的過程當中,視圖自己可能會由於 OnApplyWindowInsetsListener 致使的佈局改變而被調整大小。

咱們會在之後的文章中介紹如何解決這個問題,而目前我會推薦查看 WindowInsetsAnimation 示例,其中也包含了一個能夠避免這個問題的技巧。

相關文章
相關標籤/搜索