最近在做控件,一个底部的弹出菜单,因为是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本身的优点,这里就不再说明。
本站广告由 Google AdSense 提供
0条评论