项目要求及采用方案:
- 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等,还会重新回调。
网友评论