Android屏幕适配:DisplayMetrics核心解析与实践
1. DisplayMetrics基础解析在Android开发中DisplayMetrics类是我们获取设备屏幕物理特性的重要工具。这个类位于android.util包下提供了屏幕尺寸、密度和字体缩放等关键参数。理解这些参数对于实现适配不同设备的UI至关重要。1.1 核心参数说明DisplayMetrics包含几个关键字段widthPixels屏幕的绝对宽度像素heightPixels屏幕的绝对高度像素densityDpi屏幕密度每英寸点数density密度比例因子相对于基准密度160dpiscaledDensity字体缩放比例因子获取DisplayMetrics实例的标准方式DisplayMetrics metrics new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics);注意在Android 4.2API 17及以上版本推荐使用getRealMetrics()替代getMetrics()因为后者不包含系统装饰如导航栏占用的空间。1.2 屏幕密度详解Android将屏幕密度分为几个标准级别ldpi低密度~120dpimdpi中密度~160dpi基准密度hdpi高密度~240dpixhdpi超高密度~320dpixxhdpi超超高密度~480dpixxxhdpi超超超高密度~640dpi密度比例因子density的计算公式density densityDpi / 160f例如在240dpi的设备上density 240 / 160 1.52. 分辨率获取实践2.1 基础获取方法最直接的屏幕分辨率获取方式DisplayMetrics metrics getResources().getDisplayMetrics(); int width metrics.widthPixels; // 屏幕宽度(px) int height metrics.heightPixels; // 屏幕高度(px)2.2 考虑系统UI的影响实际可用显示区域可能小于物理分辨率Rect rect new Rect(); getWindow().getDecorView().getWindowVisibleDisplayFrame(rect); int usableWidth rect.width(); int usableHeight rect.height();2.3 多窗口模式处理在分屏模式下需要动态监听尺寸变化View rootView getWindow().getDecorView(); rootView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { int newWidth right - left; int newHeight bottom - top; // 处理尺寸变化 } });3. 像素单位转换3.1 dp与px转换Android推荐使用dp密度无关像素作为布局单位。转换方法dp转pxpublic static int dpToPx(float dp, Context context) { return (int) (dp * context.getResources().getDisplayMetrics().density 0.5f); }px转dppublic static float pxToDp(int px, Context context) { return px / context.getResources().getDisplayMetrics().density; }专业提示加0.5f是为了四舍五入避免精度损失导致的尺寸偏差。3.2 sp与px转换处理字体大小时使用sp缩放无关像素public static int spToPx(float sp, Context context) { return (int) (sp * context.getResources().getDisplayMetrics().scaledDensity 0.5f); }4. 多屏适配策略4.1 最小宽度限定符在res/values-sw dp目录中定义尺寸res/ values-sw320dp/ // 适用于最小宽度≥320dp的设备 dimens.xml values-sw600dp/ // 适用于平板 dimens.xml4.2 百分比布局使用PercentRelativeLayout或ConstraintLayout的百分比约束android.support.constraint.ConstraintLayout Button app:layout_constraintWidth_percent0.5 app:layout_constraintHeight_percent0.3/ /android.support.constraint.ConstraintLayout4.3 动态调整策略根据屏幕特性动态调整UIDisplayMetrics metrics getResources().getDisplayMetrics(); float diagonalInches Math.sqrt(Math.pow(metrics.widthPixels/metrics.xdpi, 2) Math.pow(metrics.heightPixels/metrics.ydpi, 2)); if (diagonalInches 7) { // 平板布局 } else if (diagonalInches 4.5) { // 大手机布局 } else { // 小手机布局 }5. 常见问题排查5.1 获取值不正确的情况可能原因及解决方案未考虑系统装饰使用getRealMetrics()替代getMetrics()未处理配置变更在Activity中添加configChanges属性activity android:configChangesorientation|screenSize|smallestScreenSize/多窗口模式未适配实现OnLayoutChangeListener监听尺寸变化5.2 内存带宽优化高分辨率设备上图形性能问题使用RGB_565替代ARGB_8888减少内存占用BitmapFactory.Options options new BitmapFactory.Options(); options.inPreferredConfig Bitmap.Config.RGB_565;5.3 动态图标适配根据密度动态加载图标资源String densitySuffix; switch(getResources().getDisplayMetrics().densityDpi) { case DisplayMetrics.DENSITY_XXXHIGH: densitySuffix xxxhdpi; break; // 其他case... } int resId getResources().getIdentifier( icon_ densitySuffix, drawable, getPackageName());6. 高级应用场景6.1 自定义View的尺寸处理正确处理wrap_content和精确尺寸Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode MeasureSpec.getMode(widthMeasureSpec); int widthSize MeasureSpec.getSize(widthMeasureSpec); int desiredWidth dpToPx(100, getContext()); // 默认100dp int finalWidth; if (widthMode MeasureSpec.EXACTLY) { finalWidth widthSize; } else if (widthMode MeasureSpec.AT_MOST) { finalWidth Math.min(desiredWidth, widthSize); } else { finalWidth desiredWidth; } // 类似处理高度... setMeasuredDimension(finalWidth, finalHeight); }6.2 跨进程分辨率同步在Service中获取正确分辨率WindowManager wm (WindowManager) getSystemService(Context.WINDOW_SERVICE); DisplayMetrics metrics new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(metrics);6.3 动态主题适配根据屏幕特性加载不同主题if (getResources().getConfiguration().smallestScreenWidthDp 600) { setTheme(R.style.TabletTheme); } else { setTheme(R.style.PhoneTheme); }在实际项目中我发现正确处理DisplayMetrics数据的关键在于始终考虑实际可用显示区域而非物理分辨率对dp/sp单位的转换保持一致性动态监听配置变化特别是多窗口场景高分辨率设备上要注意内存使用优化一个常见的坑是忘记处理横竖屏切换时的布局调整这会导致UI显示异常。建议在开发初期就建立完善的屏幕适配测试方案覆盖各种密度和尺寸的设备。