Android高级开发工程师-屏幕适配解决方案

屏幕适配常见方式

布局适配

  • 避免写死控件尺寸,使用match_parent、wrap_content
  • LinearLayout的layout_weight和RelativeLayout
  • ConstraintLayout 性能优于RelativeLayout
  • Percent-support-lib layout_widthPercent

限定符适配

  • 分辨率限定符 drawable-xhdpi drawable-hdpi
  • 尺寸限定符 layout-small layout-large
  • 最小宽度限定符 value-sw360dp value-sw480dp
  • 屏幕方向限定符 layout-land layout-port

自定义View适配

根据UI设计标注的屏幕尺寸做为参考,在View的加载过程,根据当前设备的实际像素换算成目标像素,再做用到控件上。php

public int getAdapterWidth(int width) {
        return width * mDisplayWidth / DEFAULT_WIDTH;
    }
    public int getAdapterHeight(int height) {
        return height * mDisplayHeight / DEFAULT_HEIGHT;
    }
复制代码
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (!isAdapted) {
            int childCnt = getChildCount();
            for (int i = 0; i < childCnt; i++) {
                LayoutParams params = (LayoutParams) getChildAt(i).getLayoutParams();
                params.width = ScreenUtils.getInstance(getContext()).getAdapterWidth(params.width);
                params.height = ScreenUtils.getInstance(getContext()).getAdapterWidth(params.height);
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
复制代码

百分比布局

原理和上面的自定义View的像素布局相似,经过自定义属性来计算View的尺寸。java

Google已经支持百分比布局android

implementation 'com.android.support:percent:29.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="match_parent">
    <androidx.percentlayout.widget.PercentRelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1">
        <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_widthPercent="50%" app:layout_heightPercent="50%" android:background="@android:color/holo_red_light"/>

    </androidx.percentlayout.widget.PercentRelativeLayout>

    <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1">
        <TextView android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" android:text="Hello World!" app:layout_constraintWidth_percent=".5" app:layout_constraintHeight_percent=".5" android:background="@android:color/holo_red_light"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
复制代码

注意:ConstraintLayout支持百分比布局,从1.1.X开始bash

修改系统density、scaleDensity、densityDpi

适用于APP已经有Pad或Phone版本,不想从新开发,适配Phone或Pad场景。

  • px像素只说明了一个屏幕包含的点数有多少,可是点的大小不是肯定的,一样是480*800,多是手掌那么大,也多是电影院屏幕那么大。
  • densityDpi 屏幕的像素密度,即屏幕每英寸的像素点。例如,屏幕横向2英寸,480px,那么横向像素密度为480px/2=240dpi。
  • density 逻辑上的屏幕密度,density = densityDpi/160。
  • scaleDensity 字体的缩放密度,默认等于density。若是在手机设置中更改了字体大小,则再也不等于density。
  • dp 独立像素密度。dp = px/density + 0.5
  • sp 缩放像素。随手机设置的字体大小而更改。sp = px/scaleDensity + 0.5

参考源码

/** * Container for a dynamically typed data value. Primarily used with * {@link android.content.res.Resources} for holding resource values. */
public class TypedValue {
    /** * Converts an unpacked complex data value holding a dimension to its final floating * point value. The two parameters <var>unit</var> and <var>value</var> * are as in {@link #TYPE_DIMENSION}. * * @param unit The unit to convert from. * @param value The value to apply the unit to. * @param metrics Current display metrics to use in the conversion -- * supplies display density and scaling information. * * @return The complex floating point value multiplied by the appropriate * metrics depending on its unit. */
    public static float applyDimension(int unit, float value, DisplayMetrics metrics) {
        switch (unit) {
        case COMPLEX_UNIT_PX:
            return value;
        case COMPLEX_UNIT_DIP:
            return value * metrics.density;
        case COMPLEX_UNIT_SP:
            return value * metrics.scaledDensity;
        case COMPLEX_UNIT_PT:
            return value * metrics.xdpi * (1.0f/72);
        case COMPLEX_UNIT_IN:
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM:
            return value * metrics.xdpi * (1.0f/25.4f);
        }
        return 0;
    }
}
复制代码

示例代码app

public class DensityUtil {
    /** * 设计师标注的标准宽度,单位是DP */
    private static final int STANDARD_WIDTH = 640;

    private static float mDensity;
    private static float mScaleDensity;

    private static float mScreenWidth;

    private DensityUtil() {
    }

    public static void setDensity(Activity activity) {
        if (mDensity == 0) {
            final Application application = activity.getApplication();
            DisplayMetrics appDm = application.getResources().getDisplayMetrics();
            mDensity = appDm.density;
            mScaleDensity = appDm.scaledDensity;

            // appDm.widthPixels始终都是短的边,不管是否设置了横竖屏
            // 与经过WindowManager获取的不同
            mScreenWidth = appDm.widthPixels;

            /** * 监听设置中字体大小的更改 */
            application.registerComponentCallbacks(new ComponentCallbacks() {
                @Override
                public void onConfigurationChanged(@NonNull Configuration configuration) {
                    if (configuration != null && configuration.fontScale > 0) {
                        // 从新获取字体的缩放
                        mScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
                        // 会从新执行Activity的onCreate方法,即从新执行后面的设置代码
                    }
                }
                @Override
                public void onLowMemory() {
                }
            });
        }

        // 经过标准屏幕宽dp和实际屏幕宽度px,计算将实际屏幕px换算成标准dp,对应的density值
        float targetDensity = mScreenWidth / STANDARD_WIDTH;
        // 经过新的density计算新的scaleDensity
        float targetScaleDensity = mScaleDensity * (targetDensity / mDensity);
        float densityDpi = targetDensity * 160;

        DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();
        activityDm.density = targetDensity;
        activityDm.scaledDensity = targetScaleDensity;
        activityDm.densityDpi = (int) densityDpi;
    }
}
复制代码
public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle) {
                DensityUtil.setDensity(activity);
            }
            ......
        });
    }
}
复制代码
相关文章
相关标签/搜索