自定義ViewGroup(DrawerLayout)異常分析

項目要求及採用方案:

  1. APP界面橫屏且分區域顯示,左邊豎向三個Tab Icon,右邊一個內容區域,內容區域分左右兩部分,點擊左邊菜單彈出右邊具體內容《==》採用DrawerLayout配合Navigation。
  2. 可手勢滑動關閉,但不能滑動打開 《==》動態設置setDrawerLockMode
  3. 抽屜顯示時內容界面可以選擇不要變暗《==》setScrimColor方法。
  4. 抽屜打開時內容界面可以選擇能被操作《==》重寫onInterceptTouchEvent方法
  5. APP全屏顯示,隱藏狀態欄和導航欄
  6. 抽屜寬度始終爲父view的一半《==》自定義DrawerLayout重寫onMeasure方法。

最終效果(以設置頁面爲參考)

設置頁面

具體實現

1. 很簡單就在DrawerLayout外面套一個LinearLayout就可以,然後設置抽屜在右邊打開android:layout_gravity="end"就行。

2. 是否可以手勢操作抽屜,設置一個監聽事件就可以

// 默認設置爲“保持關閉”,手勢不可劃出
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
drawer.addDrawerListener(object : DrawerLayout.DrawerListener {
    override fun onDrawerClosed(drawerView: View) {
         //  關閉時設置不允許手勢操作
         drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
    }
    override fun onDrawerOpened(drawerView: View) {
        // 打開時允許手勢操作
        drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
    }
    override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
    }
    override fun onDrawerStateChanged(newState: Int) {
    }
})

3. 設置是否顯示內容界面的遮罩

 if (touchOutSide) setScrimColor(Color.TRANSPARENT)
 else setScrimColor(0x99000000.toInt())

4. 設置抽屜打開時內容界面能否操作

參考abfo12老師的思路,做了些優化。

override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
    if(ev!=null) {
        if (ev.x < measuredWidth / 2 && canTouchOutside && getChildAt(1).measuredWidth < measuredWidth) return false
        val currentX = ev.x
        val currentY = ev.y
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                lastX = ev.x
                lastY = ev.y
            }
            MotionEvent.ACTION_MOVE -> {
                val diffX = abs(currentX - lastX)
                val diffY = abs(currentY - lastY)
                // 滑動角度小於30度判斷爲橫向滑動,攔截後執行自帶的滑動事件
                return diffX > 0 && diffX > diffY * sqrt(3f)
            }
        }
    }
    // 調super方法完成滑動
    return super.onInterceptTouchEvent(ev)
}

5. APP全屏顯示,隱藏狀態欄和導航欄

安卓谷歌官方文檔設計即可,有多種模式,此處是進入隱藏,邊緣滑動顯示狀態欄和導航欄,幾秒後無操作自動隱藏。

window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY

6. 寬度修改,重寫onMeasure

onMeasure《=》M
onLayout《=》L
onSizeChanged《=》S

  1. 方法一
(getChildAt(1).layoutParams as LayoutParams).width = measuredWidth/2`

發現無論放onMeasureonLayout都能實現這個需求。
之前看扔物線凱哥自定義ViewGroup的視頻,問了下凱哥
他的回覆
是的,放在onLayout也能生效是因爲界面生成過程中會進行多次測量、佈局,總的來說什麼方法做什麼事是正確的。

  • 一些BUG:
    APP需要全屏顯示,幾臺測試機中,華爲、錘子、小米無論全屏手勢還是虛擬導航鍵都沒問題,三星s8用全屏手勢也沒問題,設置虛擬導航鍵的話,返回桌面再進入頁面抽屜寬度會出現問題。
  • 生命週期:
    用手勢進入APP,生命週期無回調任何SML,打開抽屜也是MML,但關閉抽屜SML步驟異常多,MML+6ML
    用虛擬導航鍵進入APP,生命週期會回調MSLMMSL,第一次打開抽屜,抽屜位置異常,界面不能觸摸,滑動無效果,向右偏移了狀態欄高度的距離,猜測是三星橫豎屏切換過程中,狀態欄沒有及時隱藏的結果,解決方法就是在onSizeChanged中加入如下代碼。
if(Build.BRAND.contains("samsung",true)) {
    postDelayed({
        getChildAt(1).requestLayout()
    }, 200)
}

後來發現還是不穩定…其餘機型也偶有發生,並且這個延時受配置影響不可控,並且會一定程度影響APP性能。

  1. 方法二
    看完視頻,寫個自定義ViewGroup,有點感覺以後,發現這麼寫更好。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    // 獲取DrawerLayout的可用空間
    val desiredWidth = MeasureSpec.getSize(widthMeasureSpec)
    // 將抽屜寬度設置爲可用空間的一半大小
    (getChildAt(1).layoutParams as LayoutParams).width = desiredWidth/2
    // 繼續測量
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
  • BUG:沒有。
  • 生命週期:
    用手勢進入APP,生命週期統一都沒有回調MLS
    用虛擬導航鍵進入APP,生命週期會回調MSLMSL
    打開或者關閉抽屜會回調MML,當然,具體視佈局而定,比如我抽屜有網絡請求的數據加載,或者RecyclerView等,還會重新回調。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章