基于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);
    }

}

 

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