基於UI設計稿的寬進行屏幕適配工具類,幾句代碼搞定,對原項目基本無侵入,接入成本極低

基於UI設計稿的寬進行屏幕適配工具類,在Activity的onCreate中幾句代碼搞定,對原項目基本無侵入。

 

步驟一:在全局Application裏初始化。

public class XXXApp extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        //屏幕適配方案初始化
        ScreenAdaptive.initDensity(this, new ScreenAdaptive.ILogger() {
            @Override
            public void printLog(Object msg) {
                //使用自己的日誌打印工具類
            }
        });
    }

}

步驟二:在需要適配的Activity裏實現ScreenAdaptive.IActivity接口。 

public abstract class ScreenAdaptActivity extends BaseActivity implements ScreenAdaptive.IActivity {

    /**
     * 返回false將不進行屏幕適配
     */
    @Override
    public boolean enableAdapt() {
        return true;
    }

    /**
     * 指定設計稿寬度,寬度單位爲dp,即依賴於設備的像素。
     * 一般情況下,iPhone6設計稿750px爲375dp、Android設計稿1080px爲540dp
     */
    @Override
    public int designWidthInDp() {
        return 375;
    }

}

public class XXXActivity extends ScreenAdaptActivity {

}

 

完整的工具類源碼如下:

package com.gitee.li_yu_jiang.toolkit;

import android.app.Activity;
import android.app.Application;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Point;
import android.os.Build;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.WindowManager;

/**
 * 屏幕適配方案輔助類(原理類似於今日頭條的屏幕適配方案 https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA),
 * 類似的比較胖的開源庫有 https://github.com/JessYanCoding/AndroidAutoSize
 * 參閱 https://blog.csdn.net/waplyj/article/details/86081689
 * <p>
 * Created by liyujiang on 2019/10/12
 */
@SuppressWarnings({"WeakerAccess", "unused"})
public final class ScreenAdaptive {
    private static DisplayMetrics originalAppMetrics;
    private static ILogger logger;

    private ScreenAdaptive() {
        //DON'T CREATE INSTANCE
    }

    /**
     * 本類核心代碼改自 https://blog.csdn.net/u013000152/article/details/80855315
     * 設計圖px轉dp可參閱 https://blog.csdn.net/zengd0/article/details/52464627
     * <pre>
     * PPI=(sqrt(屏幕高度像素數²+屏幕寬度像素數²)/屏幕英寸大小
     * density=PPI/160
     * dp=px/density
     * ldpi(0-120),mdpi(120-160),hdpi(160-240),xhdpi(240-320),xxhdpi(320-480),xxxhdpi(480-640)
     * 例如,設計圖是以iPhone6(4.7寸屏,分辨率750*1334px)爲基準的,計算步驟如下:
     * 1334²=1779556,750²=562500,1779556+562500=2342056,sqrt(2342056)≈1530.38,1530.38/4.7≈325.61,
     * 325.61/160≈2.03
     * 所以iPhone6的PPI是326,1dp≈2px,也就是說設計圖即375*667dp
     * </pre>
     * 直接在{@link Application#onCreate}中調用,併爲{@link Activity}實現屏幕適配接口{@link IActivity}
     *
     * @param application 全局上下文
     */
    public static void initDensity(final Application application, ILogger iLogger) {
        if (iLogger != null) {
            logger = iLogger;
        }
        if (originalAppMetrics == null) {
            //初始化的保存原始的密度
            originalAppMetrics = getDisplayMetrics(application.getResources());
        }
        logger.printLog("original density=" + originalAppMetrics.density + ", scaledDensity=" + originalAppMetrics.scaledDensity);
        application.registerComponentCallbacks(new ComponentCallbacks() {
            @Override
            public void onConfigurationChanged(Configuration newConfig) {
                //字體改變後,將原始的密度重新賦值
                if (newConfig != null && newConfig.fontScale > 0) {
                    originalAppMetrics = getDisplayMetrics(application.getResources());
                    logger.printLog("font changed, scaledDensity=" + originalAppMetrics.scaledDensity);
                }
            }

            @Override
            public void onLowMemory() {
            }
        });
        application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                if (activity instanceof IActivity) {
                    //實現了屏幕適配接口的Activity才進行適配
                    IActivity adaptiveActivity = (IActivity) activity;
                    boolean enableAdaptive = adaptiveActivity.enableAdapt();
                    logger.printLog("can adaptive screen for " + activity.getClass().getName() + ": " + enableAdaptive);
                    if (enableAdaptive) {
                        changeDensity(activity, adaptiveActivity.designWidthInDp());
                    }
                } else {
                    logger.printLog("can not adaptive screen for " + activity.getClass().getName());
                }
            }

            @Override
            public void onActivityStarted(Activity activity) {
            }

            @Override
            public void onActivityResumed(Activity activity) {
            }

            @Override
            public void onActivityPaused(Activity activity) {
            }

            @Override
            public void onActivityStopped(Activity activity) {
            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }

            @Override
            public void onActivityDestroyed(Activity activity) {
            }
        });
    }

    public static void initDensity(Application application) {
        initDensity(application, new DefaultLogger());
    }

    /**
     * 在需要適配的Activity的onCreate中調用
     *
     * @param activity      所在的的Activity
     * @param designWidthDp 設計稿寬度,單位爲dp
     */
    public static void changeDensity(Activity activity, float designWidthDp) {
        if (activity == null) {
            return;
        }
        if (originalAppMetrics == null) {
            logger.printLog(activity.getClass().getSimpleName() + " not init original density");
            return;
        }
        int screenWidthPixel = obtainScreenSize(activity).x;
        logger.printLog(activity.getClass().getSimpleName() + ", screenWidthPixel=" + screenWidthPixel + ", designWidthDp=" + designWidthDp);
        float targetDensity = screenWidthPixel / designWidthDp;
        float targetScaledDensity = targetDensity * (originalAppMetrics.scaledDensity / originalAppMetrics.density);
        int targetDensityDpi = (int) (160 * targetDensity);
        DisplayMetrics activityMetrics = getDisplayMetrics(activity.getResources());
        //基於設計圖計算出新的密度並值賦給系統參數
        activityMetrics.density = targetDensity;
        activityMetrics.scaledDensity = targetScaledDensity;
        activityMetrics.densityDpi = targetDensityDpi;
        logger.printLog(activity.getClass().getSimpleName() + " target density="
                + targetDensity + ", scaledDensity=" + targetScaledDensity);
    }

    /**
     * 在不需要適配的Activity的onCreate中調用
     *
     * @param activity 所在的的Activity
     */
    public static void resetDensity(Activity activity) {
        if (activity == null) {
            return;
        }
        DisplayMetrics activityMetrics = getDisplayMetrics(activity.getResources());
        activityMetrics.setToDefaults();//NOTICE: widthPixels and heightPixels will set to 0
        Point point = obtainScreenSize(activity);
        activityMetrics.widthPixels = point.x;
        activityMetrics.heightPixels = point.y;
        logger.printLog(activity.getClass().getSimpleName() + ", reset density="
                + activityMetrics.density + ", scaledDensity=" + activityMetrics.scaledDensity);
    }

    /**
     * Return the width/height of screen, in pixel.
     *
     * @return the width/height of screen, in pixel
     */
    public static Point obtainScreenSize(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Point point = new Point();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            //noinspection ConstantConditions
            wm.getDefaultDisplay().getRealSize(point);
        } else {
            //noinspection ConstantConditions
            wm.getDefaultDisplay().getSize(point);
        }
        return point;
    }

    /**
     * 解決 MIUI 更改框架導致的 MIUI7 + Android5.1.1 上出現的失效問題 (以及極少數基於這部分 MIUI 去掉 ART 然後置入 XPosed 的手機)
     * 來源於: https://github.com/Firedamp/Rudeness/blob/master/rudeness-sdk/src/main/java/com/bulong/rudeness/RudenessScreenHelper.java#L61:5
     *
     * @param resources {@link Resources}
     * @return {@link DisplayMetrics}
     */
    private static DisplayMetrics getDisplayMetrics(Resources resources) {
        DisplayMetrics metrics = null;
        if ("MiuiResources".equals(resources.getClass().getSimpleName()) || "XResources".equals(resources.getClass().getSimpleName())) {
            try {
                //noinspection JavaReflectionMemberAccess
                java.lang.reflect.Field field = Resources.class.getDeclaredField("mTmpMetrics");
                field.setAccessible(true);
                metrics = (DisplayMetrics) field.get(resources);
            } catch (Throwable e) {
                logger.printLog("not match MIUI or XPosed: " + e);
            }
        }
        if (metrics == null) {
            metrics = resources.getDisplayMetrics();
        }
        return metrics;
    }

    private static class DefaultLogger implements ILogger {

        @Override
        public void printLog(Object msg) {
            if (msg instanceof Throwable) {
                android.util.Log.e(ScreenAdaptive.class.getSimpleName(), "", (Throwable) msg);
            } else {
                android.util.Log.w(ScreenAdaptive.class.getSimpleName(), msg.toString());
            }
        }

    }

    /**
     * {@link Activity}屏幕適配接口,基於UI設計稿的寬進行適配
     */
    public interface IActivity {
        /**
         * 是否啓用屏幕適配
         */
        boolean enableAdapt();

        /**
         * UI設計稿寬度,建議範圍在320-560內
         */
        int designWidthInDp();
    }

    /**
     * 日誌打印接口
     */
    public interface ILogger {
        void printLog(Object msg);
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章