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);
            }
            ......
        });
    }
}
複製代碼
相關文章
相關標籤/搜索