項目要求及採用方案:
- APP界面橫屏且分區域顯示,左邊豎向三個Tab Icon,右邊一個內容區域,內容區域分左右兩部分,點擊左邊菜單彈出右邊具體內容《==》採用
DrawerLayout
配合Navigation。- 可手勢滑動關閉,但不能滑動打開 《==》動態設置
setDrawerLockMode
。- 抽屜顯示時內容界面可以選擇不要變暗《==》
setScrimColor
方法。- 抽屜打開時內容界面可以選擇能被操作《==》重寫
onInterceptTouchEvent
方法- APP全屏顯示,隱藏狀態欄和導航欄
- 抽屜寬度始終爲父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
- 方法一
(getChildAt(1).layoutParams as LayoutParams).width = measuredWidth/2`
發現無論放onMeasure
和onLayout
都能實現這個需求。
之前看扔物線凱哥自定義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性能。
- 方法二
看完視頻,寫個自定義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等,還會重新回調。