1. 程式人生 > 其它 >專案應用篇-RecyclerView巢狀滑動置頂效果實踐~

專案應用篇-RecyclerView巢狀滑動置頂效果實踐~

都2021了,RecyclerView巢狀滑動置頂應該已經被說爛了吧,但是如果專案中真的需要一個這樣的結構應用到首頁,想找到一個成熟的方案並不容易。這篇文章給出的是已穩定執行大半年的巢狀滑動程式碼。程式碼地址:

https://github.com/youlookwhat/ByRecyclerView/tree/master/app/src/main/java/me/jingbin/byrecyclerview/stickyrv

專案來源

半年前接到的任務需要將首頁改為天貓或京東的一樣,現在似乎滑動置頂都是標配了,之前在網上看到那麼多這類似的文章,找找應該不難,結果我幾乎找遍了所有的文章與專案基本都不能使用,有卡頓的bug,問作者有沒有應用到專案中也沒有迴應。

本來想像以前的滑動置頂使用CoordinatorLayout+TabLayout+RecyclerView的形式處理,但是感覺欠妥,用uiautomatorviewer分析了天貓/京東/網易考拉所有App的首頁都是使用的RecyclerView-ViewPager-RecyclerView的形式,然後繼續尋找時發現了一個最接近的專案xmuSistone/PersistentRecyclerView。這應該是我找過的最完善的程式碼,然後應用到實際專案中發現還是有問題:

  • 1.在華為裝置上滑動子RecyclerView時會有跳動
  • 2.父RecyclerView下拉重新整理使用的是SmartRefreshLayout有issues反應有卡頓
  • 3.子RecyclerView載入更多需要處理
  • 4.子RecyclerView巢狀橫向的RecyclerView滑動衝突問題
  • 5.Android4.4慣性滑動崩潰問題 當然輪子不可能完美貼合專案的需求,於是在上面修改了部分程式碼。

完善

1.在華為裝置上滑動子RecyclerView時會有跳動

這個問題我在好幾個作者寫的滑動置頂程式碼那裡都發現了,其他手機都是沒問題的,原因是華為裝置靈敏度很高,在手指放在ChildRecyclerView時很容易觸發parent.requestDisallowInterceptTouchEvent(false)將事件丟給ParentRecyclerView,然後導致卡頓。處理方式是在dispatchTouchEven

t裡如果垂直滑動的距離超過24f才丟給ParentRecyclerView。具體程式碼:

override fun dispatchTouchEvent(e: MotionEvent): Boolean {
    val x = e.rawX
    val y = e.rawY
    when (e.action) {
        MotionEvent.ACTION_DOWN -> {
            //將按下時的座標儲存
            downX = x
            downY = y
            // true 表示讓ParentRecyclerView不要攔截
            parent.requestDisallowInterceptTouchEvent(true)
        }
        MotionEvent.ACTION_MOVE -> {
            //獲取到距離差
            val dx: Float = x - downX
            val dy: Float = y - downY
            // 通過距離差判斷方向
            val orientation = getOrientation(dx, dy)
            val location = intArrayOf(0, 0)
            getLocationOnScreen(location)
            when (orientation) {
                "d" -> if (canScrollVertically(-1)) {
                    // 可以向下滑動時讓ParentRecyclerView不要攔截
                    parent.requestDisallowInterceptTouchEvent(true)
                } else { //內層RecyclerView下拉到最頂部時
                    if(dy < 24f){
                        // 如果滑動的距離小於這個值依然讓Parent不攔截
                        parent.requestDisallowInterceptTouchEvent(true)
                    }else{
                        // 將滑動事件拋給Parent,這樣可以隨著Parent一起滑動
                        parent.requestDisallowInterceptTouchEvent(false)
                    }
                }
                "u" -> {
                    // 向上滑動時,始終由ChildRecyclerView處理
                    parent.requestDisallowInterceptTouchEvent(true)
                }
            }
        }
    }
    return super.dispatchTouchEvent(e)
}

private fun getOrientation(dx: Float, dy: Float): String {
    return if (Math.abs(dx) > Math.abs(dy)) {
        //X軸移動
        if (dx > 0) "r" else "l" //右,左
    } else {
        //Y軸移動
        if (dy > 0) "d" else "u" //下//上
    }
}
2.解決下拉重新整理/上拉載入問題(問題2/3)

由於之前花了一番功夫寫了ByRecyclerView,支援下拉重新整理和上拉載入,其本質上就是Adapter上加一個特殊的viewType來處理,所帶來的相容性也好很多,於是將BaseRecyclerView繼承ByRecyclerView後就達到了要求,給ParentRecyclerViewChildRecyclerView分別加下拉重新整理和上拉載入的監聽就好了:

parentRecyclerView.setOnRefreshListener { }
childRecyclerView.setOnLoadMoreListener { }
3.子RecyclerView裡的item巢狀橫向的RecyclerView滑動衝突問題

這部分相對於巢狀置頂的處理要簡單多了:

override fun dispatchTouchEvent(e: MotionEvent): Boolean {
    val x = e.rawX
    val y = e.rawY
    when (e.action) {
        MotionEvent.ACTION_DOWN -> {
            downX = x
            downY = y
        }
        MotionEvent.ACTION_MOVE -> {
            // 獲取到距離差
            val dx: Float = x - downX
            val dy: Float = y - downY
            // 通過距離差判斷方向
            val orientation = getOrientation(dx, dy)
            val location = intArrayOf(0, 0)
            getLocationOnScreen(location)
            when (orientation) {
                // 上下滑動時拋給ChildRecyclerView處理
                "d" -> parent.requestDisallowInterceptTouchEvent(false)
                "u" -> parent.requestDisallowInterceptTouchEvent(false)
                "r" -> {
                    if (canScrollVertically(-1)) {
                        // 可以向右滑動時,自己處理,可以內部左右滑
                        parent.requestDisallowInterceptTouchEvent(true)
                    } else {
                        // 右滑動到頂時,交給parent處理,使其可以滑到ViewPager下一個的position
                        parent.requestDisallowInterceptTouchEvent(false)
                    }
                }
                "l" -> {
                    if (canScrollVertically(-1)) {
                        parent.requestDisallowInterceptTouchEvent(true)
                    } else {
                        parent.requestDisallowInterceptTouchEvent(false)
                    }
                }
            }
        }
    }
    return super.dispatchTouchEvent(e)
}
4.Android 4.4慣性滑動崩潰問題

上到線上後發現,使用Android 4.4手機慣性滑動停止後會必現崩潰,具體原因是onNestedScrollAccepted()was added in API 21. You can't use it in lower API levels.,因為使用此版本的人數很少,所以粗略處理了一下:

override fun onNestedPreFling(target: View, velocityX: Float, velocityY: Float): Boolean {
        try {
            if (android.os.Build.VERSION.SDK_INT <= 19) {
                return true
            }
            return super.onNestedPreFling(target, velocityX, velocityY)
        } catch (e: Exception) {
            return true
        }
    }

總結

此專案是在PersistentRecyclerView中發展修改而來,其中解決了部分卡頓相容問題和加了下拉重新整理上拉載入功能,且經過一段時間的大範圍使用,基本趨於穩定,使用起來更加方便了。專案程式碼放在了:

https://github.com/youlookwhat/ByRecyclerView/tree/master/app/src/main/java/me/jingbin/byrecyclerview/stickyrv

Android高階開發系統進階筆記、最新面試複習筆記PDF,我的GitHub

文末

您的點贊收藏就是對我最大的鼓勵!
歡迎關注我,分享Android乾貨,交流Android技術。
對文章有何見解,或者有何技術問題,歡迎在評論區一起留言討論!