You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 10:36:03