Android自定义Dialog打开时抖动偏移且内容显示不全问题求助
问题:自定义Dialog在固定密度适配下的抖动与布局显示异常
场景与现有实现
需开发一款不受用户显示偏好影响、固定显示尺寸的Android应用,通过重写attachBaseContext实现全局密度适配,同时自定义全屏Dialog。
1. 全局密度适配代码
override fun attachBaseContext(newBase: Context?) { val newOverride = Configuration(newBase?.resources?.configuration) var screenWidth = newBase?.resources?.displayMetrics?.widthPixels var screenHeight = newBase?.resources?.displayMetrics?.heightPixels if (screenHeight!! < screenWidth!!) { val newScreenHeight = screenWidth screenWidth = screenHeight screenHeight = newScreenHeight } val targetDensityDpi = if ((screenHeight.toDouble()/screenWidth.toDouble()) < MINIMUM_ASPECT_RATIO) { (screenHeight / 4f).roundToInt() } else { (screenWidth / 2.25f).roundToInt() } newOverride.fontScale = 1.0f newOverride.densityDpi = targetDensityDpi applyOverrideConfiguration(newOverride) super.attachBaseContext(newBase) } const val MINIMUM_ASPECT_RATIO = 1.66
逻辑说明:根据屏幕宽高比设置目标密度,保证宽高比小于1.66时长边为640dp,否则短边为360dp,强制字体缩放比例为1。
2. 全屏CustomDialog实现
private class CustomDialog(context: Context): Dialog(context) { override fun onStart() { super.onStart() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val controller = WindowCompat.getInsetsController(window!!, window!!.decorView) controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE ViewCompat.setOnApplyWindowInsetsListener(window!!.decorView) { v, insets -> if (insets.isVisible(WindowInsetsCompat.Type.navigationBars()) || insets.isVisible(WindowInsetsCompat.Type.statusBars())) { controller.hide(WindowInsetsCompat.Type.systemBars()) } ViewCompat.onApplyWindowInsets(v, insets) } } else { @Suppress("DEPRECATION") window?.decorView?.setOnSystemUiVisibilityChangeListener { @Suppress("DEPRECATION") window?.decorView?.systemUiVisibility = ( View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ) } } } override fun show() { window?.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { window?.setDecorFitsSystemWindows(false) val controller = WindowCompat.getInsetsController(window!!, window!!.decorView) controller.hide(WindowInsetsCompat.Type.systemBars()) controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } else { @Suppress("DEPRECATION") window?.decorView?.systemUiVisibility = ( View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ) } super.show() window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) } }
3. Dialog调用代码
val dialog = CustomDialog(this) dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) dialog.setContentView(R.layout.privacy_options_dialog) dialog.window?.setDimAmount(0.3f) dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) dialog.show()
4. Dialog布局文件
<?xml version="1.0" encoding="utf-8"?> <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" app:cardBackgroundColor="@color/darkgray" app:cardCornerRadius="24dp" android:layout_marginHorizontal="4dp" android:layout_marginVertical="0dp"> <!-- Views --> </androidx.cardview.widget.CardView>
问题现象
- 用户系统密度与应用设置的密度一致时,Dialog显示正常
- 密度不一致时,Dialog打开时出现剧烈抖动
- 布局内容较多时,靠近屏幕边缘的视图无法完整显示
解决方案
核心问题是Dialog的Context未应用与Activity一致的密度配置,导致布局计算使用系统原生密度,与应用适配逻辑冲突。
1. 给Dialog传入适配后的Context
修改CustomDialog构造函数,手动同步应用的密度配置到Dialog的Context:
private class CustomDialog(context: Context) : Dialog(createAdaptedContext(context)) { companion object { private fun createAdaptedContext(baseContext: Context): Context { val appConfig = baseContext.resources.configuration val adaptedConfig = Configuration(appConfig).apply { densityDpi = appConfig.densityDpi fontScale = 1.0f // 和全局配置保持一致 } return baseContext.createConfigurationContext(adaptedConfig) } } // 原有的onStart和show方法保持不变 }
2. 强制Dialog窗口使用适配后的密度计算
在Dialog的show方法中添加窗口尺寸适配逻辑:
override fun show() { window?.let { window -> val metrics = context.resources.displayMetrics // 强制窗口宽度匹配屏幕(基于适配后的密度) window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) // 确保窗口密度与应用一致 window.attributes.density = metrics.density } // 原有的全屏逻辑代码... super.show() window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) }
3. 优化布局边缘显示
修改Dialog布局的根CardView,添加边缘适配属性:
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" app:cardBackgroundColor="@color/darkgray" app:cardCornerRadius="24dp" android:layout_marginHorizontal="4dp" android:layout_marginVertical="8dp" android:clipToPadding="false" android:padding="8dp"> <!-- Views --> </androidx.cardview.widget.CardView>
原理说明
- Dialog默认Context未继承Activity的自定义Configuration,导致密度计算与应用主体不一致,引发布局抖动和显示异常
- 通过创建适配后的Context,确保Dialog的资源加载、布局计算都使用应用统一的密度配置
- 强制窗口属性使用适配后的密度,避免系统自动调整窗口尺寸时出现冲突
内容的提问来源于stack exchange,提问作者Carl Koh Wei Hao
相关产品推荐
相关产品推荐

