Android屏幕适配:DisplayMetrics与分辨率获取全解析
1. DisplayMetrics基础解析在Android开发中DisplayMetrics类是我们获取设备屏幕信息的核心工具。这个位于android.util包下的类包含了设备显示相关的关键参数开发者可以通过它获取屏幕的物理尺寸、像素密度等重要信息。1.1 核心字段详解DisplayMetrics类中最常用的几个字段包括widthPixels屏幕的绝对宽度以px为单位heightPixels屏幕的绝对高度以px为单位densityDpi屏幕的密度DPI值每英寸点数density密度比例因子相对于基准密度160dpiscaledDensity考虑字体缩放后的密度比例因子这些字段中density和densityDpi的关系特别值得注意。density实际上是densityDpi/160的结果这个比例因子在dp和px转换时非常关键。1.2 获取DisplayMetrics实例获取DisplayMetrics实例的标准方式是通过WindowManagerDisplayMetrics metrics new DisplayMetrics(); WindowManager windowManager (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); windowManager.getDefaultDisplay().getMetrics(metrics);在Android API 17版本中推荐使用getRealMetrics()替代getMetrics()因为它会包含系统装饰如状态栏的尺寸windowManager.getDefaultDisplay().getRealMetrics(metrics);2. 屏幕分辨率获取实践2.1 基础获取方法获取屏幕分辨率最直接的方式就是使用DisplayMetrics的widthPixels和heightPixelsval displayMetrics resources.displayMetrics val screenWidth displayMetrics.widthPixels val screenHeight displayMetrics.heightPixels Log.d(ScreenInfo, Resolution: ${screenWidth}x${screenHeight})但需要注意这种方法获取的是应用可用区域的尺寸不包括系统装饰如状态栏、导航栏。2.2 完整屏幕尺寸获取要获取包含系统UI的完整屏幕尺寸需要使用以下方法Point size new Point(); windowManager.getDefaultDisplay().getRealSize(size); int width size.x; int height size.y;或者在API 17上使用getRealMetrics()val realMetrics DisplayMetrics() windowManager.defaultDisplay.getRealMetrics(realMetrics) val realWidth realMetrics.widthPixels val realHeight realMetrics.heightPixels2.3 多窗口模式处理在Android 7.0的多窗口模式下屏幕分辨率获取需要特殊处理val configuration resources.configuration val screenWidthDp configuration.screenWidthDp val screenHeightDp configuration.screenHeightDp这种方式获取的是dp单位的尺寸可以更好地适应不同屏幕密度。3. 密度与单位转换3.1 密度类型与适配Android定义了多种屏幕密度类型密度类型DPI范围比例因子ldpi~120dpi0.75xmdpi~160dpi1x (基准)hdpi~240dpi1.5xxhdpi~320dpi2xxxhdpi~480dpi3xxxxhdpi~640dpi4x3.2 dp与px转换在代码中进行dp和px转换的标准方法是// dp转px fun dpToPx(dp: Float): Int { return (dp * resources.displayMetrics.density).toInt() } // px转dp fun pxToDp(px: Float): Int { return (px / resources.displayMetrics.density).toInt() }更推荐使用系统提供的TypedValue.applyDimension()方法float px TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics() );3.3 sp与px转换处理文字大小时需要考虑用户的字体缩放偏好fun spToPx(sp: Float): Float { return sp * resources.displayMetrics.scaledDensity }4. 实际应用场景4.1 响应式布局实现利用屏幕分辨率信息可以实现响应式布局LinearLayout android:layout_widthmatch_parent android:layout_heightwrap_content android:orientation{screenWidth 600 ? horizontal : vertical}或者在代码中动态调整if (displayMetrics.widthPixels 600.dpToPx()) { // 宽屏布局 } else { // 窄屏布局 }4.2 图片适配策略根据屏幕密度加载不同分辨率的图片val densityBucket when { displayMetrics.densityDpi DisplayMetrics.DENSITY_XXXHIGH - xxxhdpi displayMetrics.densityDpi DisplayMetrics.DENSITY_XXHIGH - xxhdpi displayMetrics.densityDpi DisplayMetrics.DENSITY_XHIGH - xhdpi displayMetrics.densityDpi DisplayMetrics.DENSITY_HIGH - hdpi displayMetrics.densityDpi DisplayMetrics.DENSITY_MEDIUM - mdpi else - ldpi }4.3 游戏开发中的运用在游戏开发中通常需要根据屏幕分辨率设置视口fun setupViewport() { val aspectRatio displayMetrics.widthPixels.toFloat() / displayMetrics.heightPixels if (aspectRatio 1.77f) { // 超宽屏 // 特殊处理 } else { // 标准处理 } }5. 常见问题与解决方案5.1 获取值不正确的情况问题现象在onCreate()中获取的尺寸可能不准确。解决方案在View的onSizeChanged()或onWindowFocusChanged()中获取override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (hasFocus) { val metrics DisplayMetrics() windowManager.defaultDisplay.getMetrics(metrics) // 现在可以获取准确尺寸 } }5.2 折叠屏设备适配折叠屏设备需要监听屏幕变化val callback object : DisplayManager.DisplayListener { override fun onDisplayChanged(displayId: Int) { updateLayout() } // 其他方法实现... } val displayManager getSystemService(DISPLAY_SERVICE) as DisplayManager displayManager.registerDisplayListener(callback, null)5.3 刘海屏/挖孔屏处理获取安全区域尺寸val windowInsets ViewCompat.getRootWindowInsets(window.decorView) val safeInsetLeft windowInsets?.systemWindowInsetLeft ?: 0 val safeInsetRight windowInsets?.systemWindowInsetRight ?: 0 val safeWidth displayMetrics.widthPixels - safeInsetLeft - safeInsetRight6. 性能优化建议6.1 避免频繁获取DisplayMetrics的获取虽然不昂贵但也不应在循环中频繁调用。最佳实践是在Activity创建时获取一次并缓存class MainActivity : AppCompatActivity() { private lateinit var displayMetrics: DisplayMetrics override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) displayMetrics resources.displayMetrics // 后续使用缓存的实例 } }6.2 使用ViewConfiguration预定义值对于常见的UI交互阈值使用系统预定义值val touchSlop ViewConfiguration.get(this).scaledTouchSlop val minimumFlingVelocity ViewConfiguration.get(this).scaledMinimumFlingVelocity这些值已经根据屏幕密度进行了适当缩放。6.3 矢量图形优先尽可能使用矢量图形VectorDrawable替代位图vector xmlns:androidhttp://schemas.android.com/apk/res/android android:width24dp android:height24dp android:viewportWidth24.0 android:viewportHeight24.0 path android:fillColor#FF000000 android:pathDataM19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z/ /vector7. 测试与验证7.1 多设备测试策略在Android Studio的模拟器中测试不同分辨率创建AVD时选择不同设备预设特别测试极端比例设备如超宽屏验证折叠屏的展开/折叠状态7.2 自动化测试脚本编写测试用例验证分辨率相关逻辑RunWith(AndroidJUnit4::class) class DisplayMetricsTest { Test fun testDpToPxConversion() { val context InstrumentationRegistry.getInstrumentation().targetContext val expectedPx (16 * context.resources.displayMetrics.density).toInt() assertEquals(expectedPx, dpToPx(context, 16f)) } }7.3 实际设备验证清单在真机测试时检查以下项目横竖屏切换时的布局表现多窗口模式下的尺寸变化系统字体大小改变后的影响高密度屏幕上的图像清晰度8. 高级技巧与最佳实践8.1 动态计算布局参数根据屏幕尺寸动态计算布局参数fun calculateColumns(): Int { val displayMetrics resources.displayMetrics val screenWidthDp displayMetrics.widthPixels / displayMetrics.density return maxOf(1, (screenWidthDp / 120).toInt()) // 每列至少120dp }8.2 处理屏幕方向变化正确处理配置变化activity android:name.MainActivity android:configChangesorientation|screenSize|screenLayout并在代码中处理变化override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) val newScreenWidthDp newConfig.screenWidthDp // 更新布局 }8.3 支持超大字体考虑用户可能设置了超大字体val config resources.configuration val fontScale config.fontScale val adjustedSize baseSize * fontScale9. 兼容性处理9.1 旧版本API适配对于API 16及以下版本的特殊处理Suppress(DEPRECATION) fun getRealScreenSize(context: Context): Point { val point Point() val windowManager context.getSystemService(Context.WINDOW_SERVICE) as WindowManager if (Build.VERSION.SDK_INT Build.VERSION_CODES.JELLY_BEAN_MR1) { windowManager.defaultDisplay.getRealSize(point) } else { windowManager.defaultDisplay.getSize(point) } return point }9.2 特殊设备处理某些设备可能有非标准实现fun getReliableScreenSize(context: Context): Point { return try { getRealScreenSize(context) } catch (e: Exception) { val metrics context.resources.displayMetrics Point(metrics.widthPixels, metrics.heightPixels) } }10. 工具与调试10.1 ADB命令查看通过ADB获取设备屏幕信息adb shell wm size adb shell wm density10.2 开发辅助工具创建屏幕信息显示工具类object ScreenUtils { fun logScreenInfo(context: Context) { val metrics context.resources.displayMetrics val config context.resources.configuration Log.d(ScreenInfo, Resolution: ${metrics.widthPixels}x${metrics.heightPixels} Density: ${metrics.densityDpi}dpi (${metrics.density}x) Screen size: ${config.screenWidthDp}dp x ${config.screenHeightDp}dp Orientation: ${if (config.orientation Configuration.ORIENTATION_LANDSCAPE) Landscape else Portrait} Font scale: ${config.fontScale}x .trimIndent()) } }10.3 布局边界调试在开发者选项中开启显示布局边界可以直观看到各视图的实际像素尺寸。