最近在做控件,一个底部的弹出菜单,因为是dialog,所以弹出时虚拟按键和状态栏显得很不理想,产品要求隐藏掉系统的虚拟按键和状态栏,踩了些坑,记录下。
底部弹出时进入全屏隐藏状态栏、虚拟按键
这个在dialog创建后更改window的flag标志位,同时将最外层的view的fitsSystemWindows置为false即可,也就是在onShow之后,或者fragment的onActivityCreated之后,同时这时候还可以设置window的透明度,或者背景颜色以及window的进入动效。
dialog!!.window?.apply { //make window transparent setDimAmount(0f) //setBackgroundDrawableResource(android.R.color.transparent) //动画可自行处理 //setWindowAnimations(R.style.Animation_Dialog_FadeInOut) makeFullScreenBottomSheet() } val frameLayout = dialog!!.findViewById<FrameLayout>(R.id.design_bottom_sheet) (frameLayout.parent.parent as View).fitsSystemWindows = false
进入全屏的扩展函数:
fun Window.makeFullScreenBottomSheet() { val uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION.or(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) .or(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) .or(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) decorView.systemUiVisibility = uiOptions addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) }
锁屏后再开启屏幕虚拟按键会自己弹出来
这个问题是生命周期问题,锁屏后也就是关屏后实际上dialog已经重新走了一遍流程。解决办法也简单,就是在fragment的onResume中再处理一次。只是这次使用的是setOnSystemUiVisibilityChangeListener方法。
override fun onResume() {
super.onResume()
view?.setOnSystemUiVisibilityChangeListener {
dialog?.window?.apply {
makeFullScreenBottomSheet()
}
}
}
拦截点击外部自动消失事件
先上源码:
//wrapInBottomSheet method coordinator.findViewById(id.touch_outside).setOnClickListener(new OnClickListener() { public void onClick(View view) { if (BottomSheetDialog.this.cancelable && BottomSheetDialog.this.isShowing() && BottomSheetDialog.this.shouldWindowCloseOnTouchOutside()) { BottomSheetDialog.this.cancel(); } } });
看源码会发现,实际上这个点击外部消失和以往的那种实现并不同,新版的实现选择使用一个view的点击事件来替代,id为:R.id.touch_outside,在kotlin中直接用即可,java中可能需要加上很长的一段包名。
首先关闭dialog本身的点击外侧消失功能:
dialog.setCanceledOnTouchOutside(false)
然后自己在dialog创建后设置监听,可以拿到点击的位置坐标,逻辑自行实现:
val outside = dialog!!.findViewById<View>(R.id.touch_outside) outside.setOnTouchListener { v, event -> if (shouldDismiss(event.rawX.toInt(), event.rawY.toInt())) { Log.d(TAG, "dismiss") dismiss() true } else { Log.d(TAG, "no dismiss") false } }
监听BottomSheetDialog控件在上拉下拉时的进度变化
因为涉及不同屏幕尺寸,相关的计算还请自行修正,我这里提供的只是一个在全屏状态下的计算,如果有状态栏或者虚拟按键而又没屏蔽,可能会有问题。
也是在中进行处理:
/** * 计算实际偏移量比例 */var heightBiasOffset: Float = 1f override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) //---- val frameLayout = dialog!!.findViewById<FrameLayout>(R.id.design_bottom_sheet) val screenHeight = getScreenHeight(activity ?: return) Log.d(TAG, "click outside screenHeight:${screenHeight}") frameLayout.viewTreeObserver.addOnPreDrawListener { val alpha = (screenHeight - frameLayout.top) * 1.0 / (frameLayout.height * heightBiasOffset) val float = BigDecimal(alpha).setScale(2, BigDecimal.ROUND_HALF_UP).toFloat() if (heightBiasOffset == 1f) { heightBiasOffset = float } Log.d( TAG, "alpha:${float} ${heightBiasOffset} frameLayout.height:${frameLayout.height * heightBiasOffset} ${(screenHeight - frameLayout.top)} " ) true } //----float是当前弹窗位置的比例,完全弹出时为0.99近1,往下拉会逐渐减少到0,可用于透明度处理等需求 } /** * 获取屏幕高度(px) * * @param context * @return */fun getScreenHeight(context: Context): Int { // 获取屏幕分辨率, 计算方格大小 val wm = getSystemService(Context.WINDOW_SERVICE) as WindowManager val displaySize = Point() wm.defaultDisplay.getRealSize(displaySize) return displaySize.y }
需要注意的一点是,这里不要使用addOnDrawListener,因为在部分安卓sdk上如果设置得太早不会触发该方法的调用,原因看源码,相关文章推荐:https://kymjs.com/note/2018/09/20/01/
dialog和fragment所在activity的背景冲突导致看不到圆角问题
这个主要还是需要通过style解决,在DialogFragment加上初始化代码:
init {
setStyle(DialogFragment.STYLE_NO_FRAME, R.style.Theme_Dialog_NoFrame)
}
style:
<style name="Theme.Dialog.NoFrame" parent="Theme.AppCompat.Light.Dialog"> <item name="windowNoTitle">true</item> <item name="windowActionModeOverlay">false</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowNoTitle">true</item> <item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item> <item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item> <item name="android:windowActionBarOverlay">false</item> <item name="android:windowActionModeOverlay">false</item> </style>
然后在BottomSheetDialog创建时使用带theme的构造方法
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val dialog = BottomSheetDialog(context!!, theme) dialog.setOnShowListener { onShowDialog(it as BottomSheetDialog) } return dialog }
实例
以上遇到的坑差不多说完了。上完整代码,避免以后我自己也看不清,base类:
open class BaseBottomSheetDialog : AppCompatDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = BottomSheetDialog(context!!, theme)
dialog.setOnShowListener {
onShowDialog(it as BottomSheetDialog)
}
//关闭点击外侧消失
dialog.setCanceledOnTouchOutside(false)
return dialog
}
protected open fun onShowDialog(dialog: BottomSheetDialog) {
}
override fun onResume() {
super.onResume()
//解锁屏后虚拟按键屏蔽失效问题
view?.setOnSystemUiVisibilityChangeListener {
dialog?.window?.apply {
makeFullScreenBottomSheet()
}
}
}
}
然后写一个扩展类继承下:
class BottomSheetDialog : BaseBottomSheetDialog() { init { setStyle(DialogFragment.STYLE_NO_FRAME, R.style.Theme_Dialog_NoFrame) } companion object { const val TAG = "BottomSheetDialog" fun create(context: Context): BottomSheetDialog { return instantiate(context, BottomSheetDialog::class.java.name) as BottomSheetDialog } /** * 获取屏幕高度(px) * * @param context * @return */ fun getScreenHeight(context: Context): Int { // 获取屏幕分辨率, 计算方格大小 val wm = getSystemService(Context.WINDOW_SERVICE) as WindowManager val displaySize = Point() wm.defaultDisplay.getRealSize(displaySize) return displaySize.y } /** * 计算实际偏差比例尺寸 */ var heightBiasOffset: Float = 1f override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) dialog!!.window?.apply { //make window transparent setDimAmount(0f) //setBackgroundDrawableResource(android.R.color.transparent) //动画可自行处理 //setWindowAnimations(R.style.Animation_Dialog_FadeInOut) makeFullScreenBottomSheet() } val frameLayout = dialog!!.findViewById<FrameLayout>(R.id.design_bottom_sheet) (frameLayout.parent.parent as View).fitsSystemWindows = false val outside = dialog!!.findViewById<View>(R.id.touch_outside) outside.setOnTouchListener { v, event -> if (shouldDismiss(event.rawX.toInt(), event.rawY.toInt())) { Log.d(TAG, "dismiss") dismiss() true } else { Log.d(TAG, "no dismiss") false } } val screenHeight = getScreenHeight(activity ?: return) Log.d(TAG, "click outside screenHeight:${screenHeight}") frameLayout.viewTreeObserver.addOnPreDrawListener { val alpha = (screenHeight - frameLayout.top) * 1.0 / (frameLayout.height * heightBiasOffset) val float = BigDecimal(alpha).setScale(2, BigDecimal.ROUND_HALF_UP).toFloat() if (heightBiasOffset == 1f) { heightBiasOffset = float } Log.d( TAG, "alpha:${float} ${heightBiasOffset} frameLayout.height:${frameLayout.height * heightBiasOffset} ${(screenHeight - frameLayout.top)} " ) true } } private fun shouldDismiss(x: Int, y: Int): Boolean { Log.d(TAG, "click outside x:${x} y:${y}") return false } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { //自行定义view return inflater.inflate(R.layout.bottom_sheet_fragment, container, false) } }
用的时候:
BottomSheetDialog.create(this).show(supportFragmentManager,“tag”)
实际上就是一个fragment中填充了一个dialog,然后show实际上不是真正的show,只是一个全屏的fragment。相关的逻辑可在源码中找到。而DialogFragment本身的优点,这里就不再说明。
本站由以下主机服务商提供服务支持:
0条评论