Android屏幕适配-第三篇(今日头条适配方案)

今天记录的笔记是今日头条的适配方案原理以及开源库”AndroidAutoSize“的实现剖析!(备注:此文仅供自己参考,全文全是文字,如果读者读起来比较生涩难懂,建议阅读引入文章)

此篇文章是通过阅读《骚年你的屏幕适配方式该升级了!-今日头条适配方案》理解的整理。感谢作者JessYan

适配的前提是你需要知道安卓适配的基础知识和基本方法 《Android屏幕适配-第一篇(基础知识)》、《Android屏幕适配-第二篇(你要知道的适配基本操作)》

你需要知道的关键字:density、dip、px、dp(dpi)

今日头条适配原理:density=(当前屏幕分辨率宽或高(单位px))/(ui设计稿宽或高(单位dp))

                                 这个表示的是1dp在当前分辨率下的所对于的像素

真正的view大小: realWidth=(ui设计稿上的宽度)*density

如何使用,例子:如设置一个view大小为100dpx100dp,通过上面的公式可得到宽为100*density高为100*density

                            假设当前分辨率为720x1280,设计搞的尺寸为360x640,当前density=2

                            假设当前分辨率为480x800,设计搞的尺寸为360x640,当前density=1.333

                            分辨率为720x1280下 view宽度=100*2 px,占整体宽度的比例为0.278(约等于)

                            分辨率为480x800下 view宽度=100*1.33 px,占整体宽度的比例为0.278(约等于)

                            比例近乎相同             

AndroidAutoSize分析      

 设置设计稿 宽或高 

        
        
        

 初始化配置,通过启动一个ContenProvider来启动默认配置


/**
 * ================================================
 * 通过声明 {@link ContentProvider} 自动完成初始化
 * Created by JessYan on 2018/8/19 11:55
 * Contact me
 * Follow me
 * ================================================
 */
public class InitProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        AutoSizeConfig.getInstance()
                .setLog(true)
                .init((Application) getContext().getApplicationContext())
                .setUseDeviceSize(false);
        return true;
    }
    //其他代码忽略...
}

然后会调用此方法


    AutoSizeConfig init(final Application application, boolean isBaseOnWidth, AutoAdaptStrategy strategy) {
        Preconditions.checkArgument(mInitDensity == -1, "AutoSizeConfig#init() can only be called once");
        Preconditions.checkNotNull(application, "application == null");
        this.mApplication = application;
        this.isBaseOnWidth = isBaseOnWidth;
        final DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();

        getMetaData(application);
        isVertical = application.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
        int[] screenSize = ScreenUtils.getScreenSize(application);
        mScreenWidth = screenSize[0];
        mScreenHeight = screenSize[1];
        mStatusBarHeight = ScreenUtils.getStatusBarHeight();
        LogUtils.d("designWidthInDp = " + mDesignWidthInDp + ", designHeightInDp = " + mDesignHeightInDp + ", screenWidth = " + mScreenWidth + ", screenHeight = " + mScreenHeight);

        mInitDensity = displayMetrics.density;
        mInitDensityDpi = displayMetrics.densityDpi;
        mInitScaledDensity = displayMetrics.scaledDensity;
        mInitXdpi = displayMetrics.xdpi;
        application.registerComponentCallbacks(new ComponentCallbacks() {
            @Override
            public void onConfigurationChanged(Configuration newConfig) {
                if (newConfig != null) {
                    if (newConfig.fontScale > 0) {
                        mInitScaledDensity =
                                Resources.getSystem().getDisplayMetrics().scaledDensity;
                        LogUtils.d("initScaledDensity = " + mInitScaledDensity + " on ConfigurationChanged");
                    }
                    isVertical = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT;
                    int[] screenSize = ScreenUtils.getScreenSize(application);
                    mScreenWidth = screenSize[0];
                    mScreenHeight = screenSize[1];
                }
            }

            @Override
            public void onLowMemory() {

            }
        });
        ....
        //注意看这句代码是        
        mActivityLifecycleCallbacks = new ActivityLifecycleCallbacksImpl(strategy == null ? new WrapperAutoAdaptStrategy(new DefaultAutoAdaptStrategy()) : strategy);
        application.registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
        ....
        return this;
    }

  ActivityLifecycleCallbacksImpl这里面有很多核心代码

/**
 * ================================================
 * {@link ActivityLifecycleCallbacksImpl} 可用来代替在 BaseActivity 中加入适配代码的传统方式
 * {@link ActivityLifecycleCallbacksImpl} 这种方案类似于 AOP, 面向接口, 侵入性低, 方便统一管理, 扩展性强, 并且也支持适配三方库的 {@link Activity}
 * 

* Created by JessYan on 2018/8/8 14:32 * Contact me * Follow me * ================================================ */ public class ActivityLifecycleCallbacksImpl implements Application.ActivityLifecycleCallbacks { /** * 屏幕适配逻辑策略类 */ private AutoAdaptStrategy mAutoAdaptStrategy; /** * 让 {@link Fragment} 支持自定义适配参数 */ private FragmentLifecycleCallbacksImpl mFragmentLifecycleCallbacks; public ActivityLifecycleCallbacksImpl(AutoAdaptStrategy autoAdaptStrategy) { mFragmentLifecycleCallbacks = new FragmentLifecycleCallbacksImpl(autoAdaptStrategy); mAutoAdaptStrategy = autoAdaptStrategy; } @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { if (AutoSizeConfig.getInstance().isCustomFragment()) { if (activity instanceof FragmentActivity) { ((FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(mFragmentLifecycleCallbacks, true); } } //Activity 中的 setContentView(View) 一定要在 super.onCreate(Bundle); 之后执行 if (mAutoAdaptStrategy != null) { mAutoAdaptStrategy.applyAdapt(activity, activity); } } @Override public void onActivityStarted(Activity activity) { if (mAutoAdaptStrategy != null) { mAutoAdaptStrategy.applyAdapt(activity, activity); } } ... /** * 设置屏幕适配逻辑策略类 * * @param autoAdaptStrategy {@link AutoAdaptStrategy} */ public void setAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy) { mAutoAdaptStrategy = autoAdaptStrategy; mFragmentLifecycleCallbacks.setAutoAdaptStrategy(autoAdaptStrategy); } }

 DefaultAutoAdaptStrategy里面干了什么事情

/**
 * ================================================
 * 屏幕适配逻辑策略默认实现类, 可通过 {@link AutoSizeConfig#init(Application, boolean, AutoAdaptStrategy)}
 * 和 {@link AutoSizeConfig#setAutoAdaptStrategy(AutoAdaptStrategy)} 切换策略
 *
 * @see AutoAdaptStrategy
 * Created by JessYan on 2018/8/9 15:57
 * Contact me
 * Follow me
 * ================================================
 */
public class DefaultAutoAdaptStrategy implements AutoAdaptStrategy {
    @Override
    public void applyAdapt(Object target, Activity activity) {

        //检查是否开启了外部三方库的适配模式, 只要不主动调用 ExternalAdaptManager 的方法, 下面的代码就不会执行
        if (AutoSizeConfig.getInstance().getExternalAdaptManager().isRun()) {
            if (AutoSizeConfig.getInstance().getExternalAdaptManager().isCancelAdapt(target.getClass())) {
                LogUtils.w(String.format(Locale.ENGLISH, "%s canceled the adaptation!", target.getClass().getName()));
                AutoSize.cancelAdapt(activity);
                return;
            } else {
                ExternalAdaptInfo info = AutoSizeConfig.getInstance().getExternalAdaptManager()
                        .getExternalAdaptInfoOfActivity(target.getClass());
                if (info != null) {
                    LogUtils.d(String.format(Locale.ENGLISH, "%s used %s for adaptation!", target.getClass().getName(), ExternalAdaptInfo.class.getName()));
                    AutoSize.autoConvertDensityOfExternalAdaptInfo(activity, info);
                    return;
                }
            }
        }

        //如果 target 实现 CancelAdapt 接口表示放弃适配, 所有的适配效果都将失效
        if (target instanceof CancelAdapt) {
            LogUtils.w(String.format(Locale.ENGLISH, "%s canceled the adaptation!", target.getClass().getName()));
            AutoSize.cancelAdapt(activity);
            return;
        }

        //如果 target 实现 CustomAdapt 接口表示该 target 想自定义一些用于适配的参数, 从而改变最终的适配效果
        if (target instanceof CustomAdapt) {
            LogUtils.d(String.format(Locale.ENGLISH, "%s implemented by %s!", target.getClass().getName(), CustomAdapt.class.getName()));
            AutoSize.autoConvertDensityOfCustomAdapt(activity, (CustomAdapt) target);
        } else {
            LogUtils.d(String.format(Locale.ENGLISH, "%s used the global configuration.", target.getClass().getName()));
            AutoSize.autoConvertDensityOfGlobal(activity);
        }
    }
}

  AutoSize 

    public static void autoConvertDensityOfCustomAdapt(Activity activity, CustomAdapt customAdapt) {
        Preconditions.checkNotNull(customAdapt, "customAdapt == null");
        float sizeInDp = customAdapt.getSizeInDp();

        //如果 CustomAdapt#getSizeInDp() 返回 0, 则使用在 AndroidManifest 上填写的设计图尺寸
        if (sizeInDp <= 0) {
            if (customAdapt.isBaseOnWidth()) {
                sizeInDp = AutoSizeConfig.getInstance().getDesignWidthInDp();
            } else {
                sizeInDp = AutoSizeConfig.getInstance().getDesignHeightInDp();
            }
        }
        autoConvertDensity(activity, sizeInDp, customAdapt.isBaseOnWidth());
    }

       /**
     * 这里是今日头条适配方案的核心代码, 核心在于根据当前设备的实际情况做自动计算并转换 {@link DisplayMetrics#density}、
     * {@link DisplayMetrics#scaledDensity}、{@link DisplayMetrics#densityDpi} 这三个值, 额外增加 {@link DisplayMetrics#xdpi}
     * 以支持单位 {@code pt}、{@code in}、{@code mm}
     *
     * @param activity      {@link Activity}
     * @param sizeInDp      设计图上的设计尺寸, 单位 dp, 如果 {@param isBaseOnWidth} 设置为 {@code true},
     *                      {@param sizeInDp} 则应该填写设计图的总宽度, 如果 {@param isBaseOnWidth} 设置为 {@code false},
     *                      {@param sizeInDp} 则应该填写设计图的总高度
     * @param isBaseOnWidth 是否按照宽度进行等比例适配, {@code true} 为以宽度进行等比例适配, {@code false} 为以高度进行等比例适配
     * @see 今日头条官方适配方案
     */
    public static void autoConvertDensity(Activity activity, float sizeInDp, boolean isBaseOnWidth) {
        Preconditions.checkNotNull(activity, "activity == null");

        float subunitsDesignSize = isBaseOnWidth ? AutoSizeConfig.getInstance().getUnitsManager().getDesignWidth()
                : AutoSizeConfig.getInstance().getUnitsManager().getDesignHeight();
        subunitsDesignSize = subunitsDesignSize > 0 ? subunitsDesignSize : sizeInDp;

        int screenSize = isBaseOnWidth ? AutoSizeConfig.getInstance().getScreenWidth()
                : AutoSizeConfig.getInstance().getScreenHeight();
        String key = sizeInDp + "|" + subunitsDesignSize + "|" + isBaseOnWidth + "|"
                + AutoSizeConfig.getInstance().isUseDeviceSize() + "|"
                + AutoSizeConfig.getInstance().getInitScaledDensity() + "|"
                + screenSize;

        DisplayMetricsInfo displayMetricsInfo = mCache.get(key);

        float targetDensity = 0;
        int targetDensityDpi = 0;
        float targetScaledDensity = 0;
        float targetXdpi = 0;

        if (displayMetricsInfo == null) {
            if (isBaseOnWidth) {
                targetDensity = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / sizeInDp;
            } else {
                targetDensity = AutoSizeConfig.getInstance().getScreenHeight() * 1.0f / sizeInDp;
            }
            float scale = AutoSizeConfig.getInstance().isExcludeFontScale() ? 1 : AutoSizeConfig.getInstance().
                    getInitScaledDensity() * 1.0f / AutoSizeConfig.getInstance().getInitDensity();
            targetScaledDensity = targetDensity * scale;
            targetDensityDpi = (int) (targetDensity * 160);

            if (isBaseOnWidth) {
                targetXdpi = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / subunitsDesignSize;
            } else {
                targetXdpi = AutoSizeConfig.getInstance().getScreenHeight() * 1.0f / subunitsDesignSize;
            }

            mCache.put(key, new DisplayMetricsInfo(targetDensity, targetDensityDpi, targetScaledDensity, targetXdpi));
        } else {
            targetDensity = displayMetricsInfo.getDensity();
            targetDensityDpi = displayMetricsInfo.getDensityDpi();
            targetScaledDensity = displayMetricsInfo.getScaledDensity();
            targetXdpi = displayMetricsInfo.getXdpi();
        }

        setDensity(activity, targetDensity, targetDensityDpi, targetScaledDensity, targetXdpi);

        LogUtils.d(String.format(Locale.ENGLISH, "The %s has been adapted! \n%s Info: isBaseOnWidth = %s, %s = %f, %s = %f, targetDensity = %f, targetScaledDensity = %f, targetDensityDpi = %d, targetXdpi = %f"
                , activity.getClass().getName(), activity.getClass().getSimpleName(), isBaseOnWidth, isBaseOnWidth ? "designWidthInDp"
                        : "designHeightInDp", sizeInDp, isBaseOnWidth ? "designWidthInSubunits" : "designHeightInSubunits", subunitsDesignSize
                , targetDensity, targetScaledDensity, targetDensityDpi, targetXdpi));
    }

 由此可见autoConvertDensity()方法是核心代码

 这里还匹配了pt in mm 为单位的适配

    private static void setDensity(DisplayMetrics displayMetrics, float density, int densityDpi, float scaledDensity, float xdpi) {
        if (AutoSizeConfig.getInstance().getUnitsManager().isSupportDP()) {
            displayMetrics.density = density;
            displayMetrics.densityDpi = densityDpi;
        }
        if (AutoSizeConfig.getInstance().getUnitsManager().isSupportSP()) {
            displayMetrics.scaledDensity = scaledDensity;
        }
        switch (AutoSizeConfig.getInstance().getUnitsManager().getSupportSubunits()) {
            case NONE:
                break;
            case PT:
                displayMetrics.xdpi = xdpi * 72f;
                break;
            case IN:
                displayMetrics.xdpi = xdpi;
                break;
            case MM:
                displayMetrics.xdpi = xdpi * 25.4f;
                break;
            default:
        }
    }

      

你可能感兴趣的:(android,学习笔记,屏幕适配)