diff --git a/autosize/.gitignore b/autosize/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/autosize/.gitignore @@ -0,0 +1 @@ +/build diff --git a/autosize/bintray.gradle b/autosize/bintray.gradle new file mode 100644 index 0000000..4eb690d --- /dev/null +++ b/autosize/bintray.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.novoda.bintray-release' + +allprojects { + repositories { + jcenter() + } + tasks.withType(Javadoc) { + options{ + encoding "UTF-8" + charSet 'UTF-8' + links "http://docs.oracle.com/javase/7/docs/api" + } + options.addStringOption('Xdoclint:none', '-quiet') + } +} + + +def siteUrl = 'https://github.com/JessYanCoding/AndroidAutoSize' // 项目的主页 + +publish { + userOrg = 'jessyancoding' //bintray注册的用户名 + groupId = 'me.jessyan' //compile引用时的第1部分groupId + artifactId = 'autosize' //compile引用时的第2部分项目名 + publishVersion = rootProject.versionName //compile引用时的第3部分版本号 + desc = '一个极低成本的 Android 屏幕适配方案' + website = siteUrl +} + + + + + + diff --git a/autosize/build.gradle b/autosize/build.gradle new file mode 100644 index 0000000..1c50faf --- /dev/null +++ b/autosize/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 29 + versionCode 1 + versionName "1.0.1" + consumerProguardFiles 'proguard-rules.pro' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + lintOptions { + abortOnError false + warning 'InvalidPackage' + } +} + +dependencies { + implementation 'androidx.annotation:annotation:1.0.0' + implementation 'androidx.fragment:fragment:1.1.0' + +} + diff --git a/autosize/proguard-rules.pro b/autosize/proguard-rules.pro new file mode 100644 index 0000000..b5b46a9 --- /dev/null +++ b/autosize/proguard-rules.pro @@ -0,0 +1,24 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +-keep class me.jessyan.autosize.** { *; } +-keep interface me.jessyan.autosize.** { *; } \ No newline at end of file diff --git a/autosize/src/main/AndroidManifest.xml b/autosize/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7dacba0 --- /dev/null +++ b/autosize/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/autosize/src/main/java/me/jessyan/autosize/ActivityLifecycleCallbacksImpl.java b/autosize/src/main/java/me/jessyan/autosize/ActivityLifecycleCallbacksImpl.java new file mode 100644 index 0000000..e6afe4c --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/ActivityLifecycleCallbacksImpl.java @@ -0,0 +1,122 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize; + +import android.app.Activity; +import android.app.Application; +import android.os.Build; +import android.os.Bundle; + +import androidx.annotation.RequiresApi; +import androidx.fragment.app.FragmentActivity; + +import static me.jessyan.autosize.AutoSizeConfig.DEPENDENCY_ANDROIDX; +import static me.jessyan.autosize.AutoSizeConfig.DEPENDENCY_SUPPORT; + +/** + * ================================================ + * {@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; + /** + * 让 Fragment 支持自定义适配参数 + */ + private FragmentLifecycleCallbacksImpl mFragmentLifecycleCallbacks; + private FragmentLifecycleCallbacksImplToAndroidx mFragmentLifecycleCallbacksToAndroidx; + + @RequiresApi(api = Build.VERSION_CODES.O) + public ActivityLifecycleCallbacksImpl(AutoAdaptStrategy autoAdaptStrategy) { + if (DEPENDENCY_ANDROIDX) { + mFragmentLifecycleCallbacksToAndroidx = new FragmentLifecycleCallbacksImplToAndroidx(autoAdaptStrategy); + } else if (DEPENDENCY_SUPPORT){ + mFragmentLifecycleCallbacks = new FragmentLifecycleCallbacksImpl(autoAdaptStrategy); + } + mAutoAdaptStrategy = autoAdaptStrategy; + } + + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + if (AutoSizeConfig.getInstance().isCustomFragment()) { + if (mFragmentLifecycleCallbacksToAndroidx != null && activity instanceof FragmentActivity) { + ((FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(mFragmentLifecycleCallbacksToAndroidx, true); + } else if (mFragmentLifecycleCallbacks != null && 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); + } + } + + @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) { + + } + + /** + * 设置屏幕适配逻辑策略类 + * + * @param autoAdaptStrategy {@link AutoAdaptStrategy} + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public void setAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy) { + mAutoAdaptStrategy = autoAdaptStrategy; + if (mFragmentLifecycleCallbacksToAndroidx != null) { + mFragmentLifecycleCallbacksToAndroidx.setAutoAdaptStrategy(autoAdaptStrategy); + } else if (mFragmentLifecycleCallbacks != null) { + mFragmentLifecycleCallbacks.setAutoAdaptStrategy(autoAdaptStrategy); + } + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/AutoAdaptStrategy.java b/autosize/src/main/java/me/jessyan/autosize/AutoAdaptStrategy.java new file mode 100644 index 0000000..bf284c7 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/AutoAdaptStrategy.java @@ -0,0 +1,42 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize; + +import android.app.Activity; +import android.app.Application; +import android.util.DisplayMetrics; + +/** + * ================================================ + * 屏幕适配逻辑策略类, 可通过 {@link AutoSizeConfig#init(Application, boolean, AutoAdaptStrategy)} + * 和 {@link AutoSizeConfig#setAutoAdaptStrategy(AutoAdaptStrategy)} 切换策略 + * + * @see DefaultAutoAdaptStrategy + * Created by JessYan on 2018/8/9 15:13 + * Contact me + * Follow me + * ================================================ + */ +public interface AutoAdaptStrategy { + + /** + * 开始执行屏幕适配逻辑 + * + * @param target 需要屏幕适配的对象 (可能是 {@link Activity} 或者 Fragment) + * @param activity 需要拿到当前的 {@link Activity} 才能修改 {@link DisplayMetrics#density} + */ + void applyAdapt(Object target, Activity activity); +} diff --git a/autosize/src/main/java/me/jessyan/autosize/AutoSize.java b/autosize/src/main/java/me/jessyan/autosize/AutoSize.java new file mode 100644 index 0000000..4a105b7 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/AutoSize.java @@ -0,0 +1,385 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize; + +import android.app.Activity; +import android.app.Application; +import android.app.Dialog; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.net.Uri; +import android.util.DisplayMetrics; +import android.util.SparseArray; +import android.view.View; + +import java.util.Locale; + +import me.jessyan.autosize.external.ExternalAdaptInfo; +import me.jessyan.autosize.external.ExternalAdaptManager; +import me.jessyan.autosize.internal.CancelAdapt; +import me.jessyan.autosize.internal.CustomAdapt; +import me.jessyan.autosize.utils.AutoSizeLog; +import me.jessyan.autosize.utils.Preconditions; + +/** + * ================================================ + * AndroidAutoSize 用于屏幕适配的核心方法都在这里, 核心原理来自于 今日头条官方适配方案 + * 此方案只要应用到 {@link Activity} 上, 这个 {@link Activity} 下的所有 Fragment、{@link Dialog}、 + * 自定义 {@link View} 都会达到适配的效果, 如果某个页面不想使用适配请让该 {@link Activity} 实现 {@link CancelAdapt} + *

+ * 任何方案都不可能完美, 在成本和收益中做出取舍, 选择出最适合自己的方案即可, 在没有更好的方案出来之前, 只有继续忍耐它的不完美, 或者自己作出改变 + * 既然选择, 就不要抱怨, 感谢 今日头条技术团队 和 张鸿洋 等人对 Android 屏幕适配领域的的贡献 + *

+ * Created by JessYan on 2018/8/8 19:20 + * Contact me + * Follow me + * ================================================ + */ +public final class AutoSize { + private static SparseArray mCache = new SparseArray<>(); + private static final int MODE_SHIFT = 30; + private static final int MODE_MASK = 0x3 << MODE_SHIFT; + private static final int MODE_ON_WIDTH = 1 << MODE_SHIFT; + private static final int MODE_DEVICE_SIZE = 2 << MODE_SHIFT; + + private AutoSize() { + throw new IllegalStateException("you can't instantiate me!"); + } + + /** + * 检查 AndroidAutoSize 是否已经初始化 + * + * @return {@code false} 表示 AndroidAutoSize 还未初始化, {@code true} 表示 AndroidAutoSize 已经初始化 + */ + public static boolean checkInit() { + return AutoSizeConfig.getInstance().getInitDensity() != -1; + } + + /** + * 由于 AndroidAutoSize 会通过 {@link InitProvider} 的实例化而自动完成初始化, 并且 {@link AutoSizeConfig#init(Application)} + * 只允许被调用一次, 否则会报错, 所以 {@link AutoSizeConfig#init(Application)} 的调用权限并没有设为 public, 不允许外部使用者调用 + * 但由于某些 issues 反应, 可能会在某些特殊情况下出现 {@link InitProvider} 未能正常实例化的情况, 导致 AndroidAutoSize 未能完成初始化 + * 所以提供此静态方法用于让外部使用者在异常情况下也可以初始化 AndroidAutoSize, 在 {@link Application#onCreate()} 中调用即可 + * + * @param application {@link Application} + */ + public static void checkAndInit(Application application) { + if (!checkInit()) { + AutoSizeConfig.getInstance() + .setLog(true) + .init(application) + .setUseDeviceSize(false); + } + } + + /** + * 使用 AndroidAutoSize 初始化时设置的默认适配参数进行适配 (AndroidManifest 的 Meta 属性) + * + * @param activity {@link Activity} + */ + public static void autoConvertDensityOfGlobal(Activity activity) { + if (AutoSizeConfig.getInstance().isBaseOnWidth()) { + autoConvertDensityBaseOnWidth(activity, AutoSizeConfig.getInstance().getDesignWidthInDp()); + } else { + autoConvertDensityBaseOnHeight(activity, AutoSizeConfig.getInstance().getDesignHeightInDp()); + } + } + + /** + * 使用 {@link Activity} 或 Fragment 的自定义参数进行适配 + * + * @param activity {@link Activity} + * @param customAdapt {@link Activity} 或 Fragment 需实现 {@link CustomAdapt} + */ + 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 Activity} 或 Fragment 的自定义适配参数进行适配 + * + * @param activity {@link Activity} + * @param externalAdaptInfo 三方库的 {@link Activity} 或 Fragment 提供的适配参数, 需要配合 {@link ExternalAdaptManager#addExternalAdaptInfoOfActivity(Class, ExternalAdaptInfo)} + */ + public static void autoConvertDensityOfExternalAdaptInfo(Activity activity, ExternalAdaptInfo externalAdaptInfo) { + Preconditions.checkNotNull(externalAdaptInfo, "externalAdaptInfo == null"); + float sizeInDp = externalAdaptInfo.getSizeInDp(); + + //如果 ExternalAdaptInfo#getSizeInDp() 返回 0, 则使用在 AndroidManifest 上填写的设计图尺寸 + if (sizeInDp <= 0) { + if (externalAdaptInfo.isBaseOnWidth()) { + sizeInDp = AutoSizeConfig.getInstance().getDesignWidthInDp(); + } else { + sizeInDp = AutoSizeConfig.getInstance().getDesignHeightInDp(); + } + } + autoConvertDensity(activity, sizeInDp, externalAdaptInfo.isBaseOnWidth()); + } + + /** + * 以宽度为基准进行适配 + * + * @param activity {@link Activity} + * @param designWidthInDp 设计图的总宽度 + */ + public static void autoConvertDensityBaseOnWidth(Activity activity, float designWidthInDp) { + autoConvertDensity(activity, designWidthInDp, true); + } + + /** + * 以高度为基准进行适配 + * + * @param activity {@link Activity} + * @param designHeightInDp 设计图的总高度 + */ + public static void autoConvertDensityBaseOnHeight(Activity activity, float designHeightInDp) { + autoConvertDensity(activity, designHeightInDp, false); + } + + /** + * 这里是今日头条适配方案的核心代码, 核心在于根据当前设备的实际情况做自动计算并转换 {@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"); + Preconditions.checkMainThread(); + + 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(); + + int key = Math.round((sizeInDp + subunitsDesignSize + screenSize) * AutoSizeConfig.getInstance().getInitScaledDensity()) & ~MODE_MASK; + key = isBaseOnWidth ? (key | MODE_ON_WIDTH) : (key & ~MODE_ON_WIDTH); + key = AutoSizeConfig.getInstance().isUseDeviceSize() ? (key | MODE_DEVICE_SIZE) : (key & ~MODE_DEVICE_SIZE); + + DisplayMetricsInfo displayMetricsInfo = mCache.get(key); + + float targetDensity = 0; + int targetDensityDpi = 0; + float targetScaledDensity = 0; + float targetXdpi = 0; + int targetScreenWidthDp; + int targetScreenHeightDp; + + if (displayMetricsInfo == null) { + if (isBaseOnWidth) { + targetDensity = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / sizeInDp; + } else { + targetDensity = AutoSizeConfig.getInstance().getScreenHeight() * 1.0f / sizeInDp; + } + if (AutoSizeConfig.getInstance().getPrivateFontScale() > 0) { + targetScaledDensity = targetDensity * AutoSizeConfig.getInstance().getPrivateFontScale(); + } else { + float systemFontScale = AutoSizeConfig.getInstance().isExcludeFontScale() ? 1 : AutoSizeConfig.getInstance(). + getInitScaledDensity() * 1.0f / AutoSizeConfig.getInstance().getInitDensity(); + targetScaledDensity = targetDensity * systemFontScale; + } + targetDensityDpi = (int) (targetDensity * 160); + + targetScreenWidthDp = (int) (AutoSizeConfig.getInstance().getScreenWidth() / targetDensity); + targetScreenHeightDp = (int) (AutoSizeConfig.getInstance().getScreenHeight() / targetDensity); + + 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, targetScreenWidthDp, targetScreenHeightDp)); + } else { + targetDensity = displayMetricsInfo.getDensity(); + targetDensityDpi = displayMetricsInfo.getDensityDpi(); + targetScaledDensity = displayMetricsInfo.getScaledDensity(); + targetXdpi = displayMetricsInfo.getXdpi(); + targetScreenWidthDp = displayMetricsInfo.getScreenWidthDp(); + targetScreenHeightDp = displayMetricsInfo.getScreenHeightDp(); + } + + setDensity(activity, targetDensity, targetDensityDpi, targetScaledDensity, targetXdpi); + setScreenSizeDp(activity, targetScreenWidthDp, targetScreenHeightDp); + + AutoSizeLog.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, targetScreenWidthDp = %d, targetScreenHeightDp = %d" + , activity.getClass().getName(), activity.getClass().getSimpleName(), isBaseOnWidth, isBaseOnWidth ? "designWidthInDp" + : "designHeightInDp", sizeInDp, isBaseOnWidth ? "designWidthInSubunits" : "designHeightInSubunits", subunitsDesignSize + , targetDensity, targetScaledDensity, targetDensityDpi, targetXdpi, targetScreenWidthDp, targetScreenHeightDp)); + } + + /** + * 取消适配 + * + * @param activity {@link Activity} + */ + public static void cancelAdapt(Activity activity) { + Preconditions.checkMainThread(); + float initXdpi = AutoSizeConfig.getInstance().getInitXdpi(); + switch (AutoSizeConfig.getInstance().getUnitsManager().getSupportSubunits()) { + case PT: + initXdpi = initXdpi / 72f; + break; + case MM: + initXdpi = initXdpi / 25.4f; + break; + default: + } + setDensity(activity, AutoSizeConfig.getInstance().getInitDensity() + , AutoSizeConfig.getInstance().getInitDensityDpi() + , AutoSizeConfig.getInstance().getInitScaledDensity() + , initXdpi); + setScreenSizeDp(activity + , AutoSizeConfig.getInstance().getInitScreenWidthDp() + , AutoSizeConfig.getInstance().getInitScreenHeightDp()); + } + + /** + * 当 App 中出现多进程,并且您需要适配所有的进程,就需要在 App 初始化时调用 {@link #initCompatMultiProcess} + * 建议实现自定义 {@link Application} 并在 {@link Application#onCreate()} 中调用 {@link #initCompatMultiProcess} + * + * @param context {@link Context} + */ + public static void initCompatMultiProcess(Context context) { + context.getContentResolver().query(Uri.parse("content://" + context.getPackageName() + ".autosize-init-provider"), null, null, null, null); + } + + /** + * 给几大 {@link DisplayMetrics} 赋值 + * + * @param activity {@link Activity} + * @param density {@link DisplayMetrics#density} + * @param densityDpi {@link DisplayMetrics#densityDpi} + * @param scaledDensity {@link DisplayMetrics#scaledDensity} + * @param xdpi {@link DisplayMetrics#xdpi} + */ + private static void setDensity(Activity activity, float density, int densityDpi, float scaledDensity, float xdpi) { + DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics(); + setDensity(activityDisplayMetrics, density, densityDpi, scaledDensity, xdpi); + DisplayMetrics appDisplayMetrics = AutoSizeConfig.getInstance().getApplication().getResources().getDisplayMetrics(); + setDensity(appDisplayMetrics, density, densityDpi, scaledDensity, xdpi); + + //兼容 MIUI + DisplayMetrics activityDisplayMetricsOnMIUI = getMetricsOnMiui(activity.getResources()); + DisplayMetrics appDisplayMetricsOnMIUI = getMetricsOnMiui(AutoSizeConfig.getInstance().getApplication().getResources()); + + if (activityDisplayMetricsOnMIUI != null) { + setDensity(activityDisplayMetricsOnMIUI, density, densityDpi, scaledDensity, xdpi); + } + if (appDisplayMetricsOnMIUI != null) { + setDensity(appDisplayMetricsOnMIUI, density, densityDpi, scaledDensity, xdpi); + } + } + + /** + * 赋值 + * + * @param displayMetrics {@link DisplayMetrics} + * @param density {@link DisplayMetrics#density} + * @param densityDpi {@link DisplayMetrics#densityDpi} + * @param scaledDensity {@link DisplayMetrics#scaledDensity} + * @param xdpi {@link DisplayMetrics#xdpi} + */ + 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: + } + } + + /** + * 给 {@link Configuration} 赋值 + * + * @param activity {@link Activity} + * @param screenWidthDp {@link Configuration#screenWidthDp} + * @param screenHeightDp {@link Configuration#screenHeightDp} + */ + private static void setScreenSizeDp(Activity activity, int screenWidthDp, int screenHeightDp) { + if (AutoSizeConfig.getInstance().getUnitsManager().isSupportDP() && AutoSizeConfig.getInstance().getUnitsManager().isSupportScreenSizeDP()) { + Configuration activityConfiguration = activity.getResources().getConfiguration(); + setScreenSizeDp(activityConfiguration, screenWidthDp, screenHeightDp); + + Configuration appConfiguration = AutoSizeConfig.getInstance().getApplication().getResources().getConfiguration(); + setScreenSizeDp(appConfiguration, screenWidthDp, screenHeightDp); + } + } + + /** + * Configuration赋值 + * + * @param configuration {@link Configuration} + * @param screenWidthDp {@link Configuration#screenWidthDp} + * @param screenHeightDp {@link Configuration#screenHeightDp} + */ + private static void setScreenSizeDp(Configuration configuration, int screenWidthDp, int screenHeightDp) { + configuration.screenWidthDp = screenWidthDp; + configuration.screenHeightDp = screenHeightDp; + } + + /** + * 解决 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}, 可能为 {@code null} + */ + private static DisplayMetrics getMetricsOnMiui(Resources resources) { + if (AutoSizeConfig.getInstance().isMiui() && AutoSizeConfig.getInstance().getTmpMetricsField() != null) { + try { + return (DisplayMetrics) AutoSizeConfig.getInstance().getTmpMetricsField().get(resources); + } catch (Exception e) { + return null; + } + } + return null; + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/AutoSizeCompat.java b/autosize/src/main/java/me/jessyan/autosize/AutoSizeCompat.java new file mode 100644 index 0000000..710a46a --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/AutoSizeCompat.java @@ -0,0 +1,331 @@ +/* + * Copyright 2019 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize; + +import android.app.Activity; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.util.DisplayMetrics; +import android.util.SparseArray; + +import me.jessyan.autosize.external.ExternalAdaptInfo; +import me.jessyan.autosize.external.ExternalAdaptManager; +import me.jessyan.autosize.internal.CustomAdapt; +import me.jessyan.autosize.utils.Preconditions; + +/** + * ================================================ + * 当遇到本来适配正常的布局突然出现适配失效,适配异常等问题, 重写当前 {@link Activity} 的 {@link Activity#getResources()} 并调用 + * {@link AutoSizeCompat} 的对应方法即可解决问题 + *

+ * Created by JessYan on 2018/8/8 19:20 + * Contact me + * Follow me + * ================================================ + */ +public final class AutoSizeCompat { + private static SparseArray mCache = new SparseArray<>(); + private static final int MODE_SHIFT = 30; + private static final int MODE_MASK = 0x3 << MODE_SHIFT; + private static final int MODE_ON_WIDTH = 1 << MODE_SHIFT; + private static final int MODE_DEVICE_SIZE = 2 << MODE_SHIFT; + + private AutoSizeCompat() { + throw new IllegalStateException("you can't instantiate me!"); + } + + /** + * 使用 AndroidAutoSize 初始化时设置的默认适配参数进行适配 (AndroidManifest 的 Meta 属性) + * + * @param resources {@link Resources} + */ + public static void autoConvertDensityOfGlobal(Resources resources) { + if (AutoSizeConfig.getInstance().isBaseOnWidth()) { + autoConvertDensityBaseOnWidth(resources, AutoSizeConfig.getInstance().getDesignWidthInDp()); + } else { + autoConvertDensityBaseOnHeight(resources, AutoSizeConfig.getInstance().getDesignHeightInDp()); + } + } + + /** + * 使用 {@link Activity} 或 Fragment 的自定义参数进行适配 + * + * @param resources {@link Resources} + * @param customAdapt {@link Activity} 或 Fragment 需实现 {@link CustomAdapt} + */ + public static void autoConvertDensityOfCustomAdapt(Resources resources, 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(resources, sizeInDp, customAdapt.isBaseOnWidth()); + } + + /** + * 使用外部三方库的 {@link Activity} 或 Fragment 的自定义适配参数进行适配 + * + * @param resources {@link Resources} + * @param externalAdaptInfo 三方库的 {@link Activity} 或 Fragment 提供的适配参数, 需要配合 {@link ExternalAdaptManager#addExternalAdaptInfoOfActivity(Class, ExternalAdaptInfo)} + */ + public static void autoConvertDensityOfExternalAdaptInfo(Resources resources, ExternalAdaptInfo externalAdaptInfo) { + Preconditions.checkNotNull(externalAdaptInfo, "externalAdaptInfo == null"); + float sizeInDp = externalAdaptInfo.getSizeInDp(); + + //如果 ExternalAdaptInfo#getSizeInDp() 返回 0, 则使用在 AndroidManifest 上填写的设计图尺寸 + if (sizeInDp <= 0) { + if (externalAdaptInfo.isBaseOnWidth()) { + sizeInDp = AutoSizeConfig.getInstance().getDesignWidthInDp(); + } else { + sizeInDp = AutoSizeConfig.getInstance().getDesignHeightInDp(); + } + } + autoConvertDensity(resources, sizeInDp, externalAdaptInfo.isBaseOnWidth()); + } + + /** + * 以宽度为基准进行适配 + * + * @param resources {@link Resources} + * @param designWidthInDp 设计图的总宽度 + */ + public static void autoConvertDensityBaseOnWidth(Resources resources, float designWidthInDp) { + autoConvertDensity(resources, designWidthInDp, true); + } + + /** + * 以高度为基准进行适配 + * + * @param resources {@link Resources} + * @param designHeightInDp 设计图的总高度 + */ + public static void autoConvertDensityBaseOnHeight(Resources resources, float designHeightInDp) { + autoConvertDensity(resources, designHeightInDp, false); + } + + /** + * 这里是今日头条适配方案的核心代码, 核心在于根据当前设备的实际情况做自动计算并转换 {@link DisplayMetrics#density}、 + * {@link DisplayMetrics#scaledDensity}、{@link DisplayMetrics#densityDpi} 这三个值, 额外增加 {@link DisplayMetrics#xdpi} + * 以支持单位 {@code pt}、{@code in}、{@code mm} + * + * @param resources {@link Resources} + * @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(Resources resources, float sizeInDp, boolean isBaseOnWidth) { + Preconditions.checkNotNull(resources, "resources == null"); + Preconditions.checkMainThread(); + + 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(); + + int key = Math.round((sizeInDp + subunitsDesignSize + screenSize) * AutoSizeConfig.getInstance().getInitScaledDensity()) & ~MODE_MASK; + key = isBaseOnWidth ? (key | MODE_ON_WIDTH) : (key & ~MODE_ON_WIDTH); + key = AutoSizeConfig.getInstance().isUseDeviceSize() ? (key | MODE_DEVICE_SIZE) : (key & ~MODE_DEVICE_SIZE); + + DisplayMetricsInfo displayMetricsInfo = mCache.get(key); + + float targetDensity = 0; + int targetDensityDpi = 0; + float targetScaledDensity = 0; + float targetXdpi = 0; + int targetScreenWidthDp; + int targetScreenHeightDp; + + if (displayMetricsInfo == null) { + if (isBaseOnWidth) { + targetDensity = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / sizeInDp; + } else { + targetDensity = AutoSizeConfig.getInstance().getScreenHeight() * 1.0f / sizeInDp; + } + if (AutoSizeConfig.getInstance().getPrivateFontScale() > 0) { + targetScaledDensity = targetDensity * AutoSizeConfig.getInstance().getPrivateFontScale(); + } else { + float systemFontScale = AutoSizeConfig.getInstance().isExcludeFontScale() ? 1 : AutoSizeConfig.getInstance(). + getInitScaledDensity() * 1.0f / AutoSizeConfig.getInstance().getInitDensity(); + targetScaledDensity = targetDensity * systemFontScale; + } + targetDensityDpi = (int) (targetDensity * 160); + + targetScreenWidthDp = (int) (AutoSizeConfig.getInstance().getScreenWidth() / targetDensity); + targetScreenHeightDp = (int) (AutoSizeConfig.getInstance().getScreenHeight() / targetDensity); + + 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, targetScreenWidthDp, targetScreenHeightDp)); + } else { + targetDensity = displayMetricsInfo.getDensity(); + targetDensityDpi = displayMetricsInfo.getDensityDpi(); + targetScaledDensity = displayMetricsInfo.getScaledDensity(); + targetXdpi = displayMetricsInfo.getXdpi(); + targetScreenWidthDp = displayMetricsInfo.getScreenWidthDp(); + targetScreenHeightDp = displayMetricsInfo.getScreenHeightDp(); + } + + setDensity(resources, targetDensity, targetDensityDpi, targetScaledDensity, targetXdpi); + setScreenSizeDp(resources, targetScreenWidthDp, targetScreenHeightDp); + } + + /** + * 取消适配 + * + * @param resources {@link Resources} + */ + public static void cancelAdapt(Resources resources) { + Preconditions.checkMainThread(); + float initXdpi = AutoSizeConfig.getInstance().getInitXdpi(); + switch (AutoSizeConfig.getInstance().getUnitsManager().getSupportSubunits()) { + case PT: + initXdpi = initXdpi / 72f; + break; + case MM: + initXdpi = initXdpi / 25.4f; + break; + default: + } + setDensity(resources, AutoSizeConfig.getInstance().getInitDensity() + , AutoSizeConfig.getInstance().getInitDensityDpi() + , AutoSizeConfig.getInstance().getInitScaledDensity() + , initXdpi); + setScreenSizeDp(resources + , AutoSizeConfig.getInstance().getInitScreenWidthDp() + , AutoSizeConfig.getInstance().getInitScreenHeightDp()); + } + + /** + * 给几大 {@link DisplayMetrics} 赋值 + * + * @param resources {@link Resources} + * @param density {@link DisplayMetrics#density} + * @param densityDpi {@link DisplayMetrics#densityDpi} + * @param scaledDensity {@link DisplayMetrics#scaledDensity} + * @param xdpi {@link DisplayMetrics#xdpi} + */ + private static void setDensity(Resources resources, float density, int densityDpi, float scaledDensity, float xdpi) { + DisplayMetrics activityDisplayMetrics = resources.getDisplayMetrics(); + setDensity(activityDisplayMetrics, density, densityDpi, scaledDensity, xdpi); + DisplayMetrics appDisplayMetrics = AutoSizeConfig.getInstance().getApplication().getResources().getDisplayMetrics(); + setDensity(appDisplayMetrics, density, densityDpi, scaledDensity, xdpi); + + //兼容 MIUI + DisplayMetrics activityDisplayMetricsOnMIUI = getMetricsOnMiui(resources); + DisplayMetrics appDisplayMetricsOnMIUI = getMetricsOnMiui(AutoSizeConfig.getInstance().getApplication().getResources()); + + if (activityDisplayMetricsOnMIUI != null) { + setDensity(activityDisplayMetricsOnMIUI, density, densityDpi, scaledDensity, xdpi); + } + if (appDisplayMetricsOnMIUI != null) { + setDensity(appDisplayMetricsOnMIUI, density, densityDpi, scaledDensity, xdpi); + } + } + + /** + * 赋值 + * + * @param displayMetrics {@link DisplayMetrics} + * @param density {@link DisplayMetrics#density} + * @param densityDpi {@link DisplayMetrics#densityDpi} + * @param scaledDensity {@link DisplayMetrics#scaledDensity} + * @param xdpi {@link DisplayMetrics#xdpi} + */ + 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: + } + } + + /** + * 给 {@link Configuration} 赋值 + * + * @param resources {@link Resources} + * @param screenWidthDp {@link Configuration#screenWidthDp} + * @param screenHeightDp {@link Configuration#screenHeightDp} + */ + private static void setScreenSizeDp(Resources resources, int screenWidthDp, int screenHeightDp) { + if (AutoSizeConfig.getInstance().getUnitsManager().isSupportDP() && AutoSizeConfig.getInstance().getUnitsManager().isSupportScreenSizeDP()) { + Configuration activityConfiguration = resources.getConfiguration(); + setScreenSizeDp(activityConfiguration, screenWidthDp, screenHeightDp); + + Configuration appConfiguration = AutoSizeConfig.getInstance().getApplication().getResources().getConfiguration(); + setScreenSizeDp(appConfiguration, screenWidthDp, screenHeightDp); + } + } + + /** + * Configuration赋值 + * + * @param configuration {@link Configuration} + * @param screenWidthDp {@link Configuration#screenWidthDp} + * @param screenHeightDp {@link Configuration#screenHeightDp} + */ + private static void setScreenSizeDp(Configuration configuration, int screenWidthDp, int screenHeightDp) { + configuration.screenWidthDp = screenWidthDp; + configuration.screenHeightDp = screenHeightDp; + } + + /** + * 解决 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}, 可能为 {@code null} + */ + private static DisplayMetrics getMetricsOnMiui(Resources resources) { + if (AutoSizeConfig.getInstance().isMiui() && AutoSizeConfig.getInstance().getTmpMetricsField() != null) { + try { + return (DisplayMetrics) AutoSizeConfig.getInstance().getTmpMetricsField().get(resources); + } catch (Exception e) { + return null; + } + } + return null; + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/AutoSizeConfig.java b/autosize/src/main/java/me/jessyan/autosize/AutoSizeConfig.java new file mode 100644 index 0000000..92fb15a --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/AutoSizeConfig.java @@ -0,0 +1,714 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize; + +import android.app.Activity; +import android.app.Application; +import android.content.ComponentCallbacks; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.util.DisplayMetrics; + +import java.lang.reflect.Field; + +import me.jessyan.autosize.external.ExternalAdaptManager; +import me.jessyan.autosize.unit.Subunits; +import me.jessyan.autosize.unit.UnitsManager; +import me.jessyan.autosize.utils.AutoSizeLog; +import me.jessyan.autosize.utils.Preconditions; +import me.jessyan.autosize.utils.ScreenUtils; + +/** + * ================================================ + * AndroidAutoSize 参数配置类, 给 AndroidAutoSize 配置一些必要的自定义参数 + *

+ * Created by JessYan on 2018/8/8 09:58 + * Contact me + * Follow me + * ================================================ + */ +public final class AutoSizeConfig { + private static volatile AutoSizeConfig sInstance; + private static final String KEY_DESIGN_WIDTH_IN_DP = "design_width_in_dp"; + private static final String KEY_DESIGN_HEIGHT_IN_DP = "design_height_in_dp"; + public static final boolean DEPENDENCY_ANDROIDX; + public static final boolean DEPENDENCY_SUPPORT; + private Application mApplication; + /** + * 用来管理外部三方库 {@link Activity} 的适配 + */ + private ExternalAdaptManager mExternalAdaptManager = new ExternalAdaptManager(); + /** + * 用来管理 AndroidAutoSize 支持的所有单位, AndroidAutoSize 支持五种单位 (dp、sp、pt、in、mm) + */ + private UnitsManager mUnitsManager = new UnitsManager(); + /** + * 最初的 {@link DisplayMetrics#density} + */ + private float mInitDensity = -1; + /** + * 最初的 {@link DisplayMetrics#densityDpi} + */ + private int mInitDensityDpi; + /** + * 最初的 {@link DisplayMetrics#scaledDensity} + */ + private float mInitScaledDensity; + /** + * 最初的 {@link DisplayMetrics#xdpi} + */ + private float mInitXdpi; + /** + * 最初的 {@link Configuration#screenWidthDp} + */ + private int mInitScreenWidthDp; + /** + * 最初的 {@link Configuration#screenHeightDp} + */ + private int mInitScreenHeightDp; + /** + * 设计图上的总宽度, 单位 dp + */ + private int mDesignWidthInDp; + /** + * 设计图上的总高度, 单位 dp + */ + private int mDesignHeightInDp; + /** + * 设备的屏幕总宽度, 单位 px + */ + private int mScreenWidth; + /** + * 设备的屏幕总高度, 单位 px, 如果 {@link #isUseDeviceSize} 为 {@code false}, 屏幕总高度会减去状态栏的高度 + */ + private int mScreenHeight; + /** + * 状态栏高度, 当 {@link #isUseDeviceSize} 为 {@code false} 时, AndroidAutoSize 会将 {@link #mScreenHeight} 减去状态栏高度 + * AndroidAutoSize 默认使用 {@link ScreenUtils#getStatusBarHeight()} 方法获取状态栏高度 + * AndroidAutoSize 使用者可使用 {@link #setStatusBarHeight(int)} 自行设置状态栏高度 + */ + private int mStatusBarHeight; + /** + * 为了保证在不同高宽比的屏幕上显示效果也能完全一致, 所以本方案适配时是以设计图宽度与设备实际宽度的比例或设计图高度与设备实际高度的比例应用到 + * 每个 View 上 (只能在宽度和高度之中选一个作为基准), 从而使每个 View 的高和宽用同样的比例缩放, 避免在与设计图高宽比不一致的设备上出现适配的 View 高或宽变形的问题 + * {@link #isBaseOnWidth} 为 {@code true} 时代表以宽度等比例缩放, {@code false} 代表以高度等比例缩放 + * {@link #isBaseOnWidth} 为全局配置, 默认为 {@code true}, 每个 {@link Activity} 也可以单独选择使用高或者宽做等比例缩放 + */ + private boolean isBaseOnWidth = true; + /** + * 此字段表示是否使用设备的实际尺寸做适配 + * {@link #isUseDeviceSize} 为 {@code true} 表示屏幕高度 {@link #mScreenHeight} 包含状态栏的高度 + * {@link #isUseDeviceSize} 为 {@code false} 表示 {@link #mScreenHeight} 会减去状态栏的高度, 默认为 {@code true} + */ + private boolean isUseDeviceSize = true; + /** + * {@link #mActivityLifecycleCallbacks} 可用来代替在 BaseActivity 中加入适配代码的传统方式 + * {@link #mActivityLifecycleCallbacks} 这种方案类似于 AOP, 面向接口, 侵入性低, 方便统一管理, 扩展性强, 并且也支持适配三方库的 {@link Activity} + */ + private ActivityLifecycleCallbacksImpl mActivityLifecycleCallbacks; + /** + * 框架具有 热插拔 特性, 支持在项目运行中动态停止和重新启动适配功能 + * + * @see #stop(Activity) + * @see #restart() + */ + private boolean isStop; + /** + * 是否让框架支持自定义 Fragment 的适配参数, 由于这个需求是比较少见的, 所以须要使用者手动开启 + */ + private boolean isCustomFragment; + /** + * 屏幕方向, {@code true} 为纵向, {@code false} 为横向 + */ + private boolean isVertical; + /** + * 是否屏蔽系统字体大小对 AndroidAutoSize 的影响, 如果为 {@code true}, App 内的字体的大小将不会跟随系统设置中字体大小的改变 + * 如果为 {@code false}, 则会跟随系统设置中字体大小的改变, 默认为 {@code false} + */ + private boolean isExcludeFontScale; + /** + * 区别于系统字体大小的放大比例, AndroidAutoSize 允许 APP 内部可以独立于系统字体大小之外,独自拥有全局调节 APP 字体大小的能力 + * 当然, 在 APP 内您必须使用 sp 来作为字体的单位, 否则此功能无效, 将此值设为 0 则取消此功能 + */ + private float privateFontScale; + /** + * 是否是 Miui 系统 + */ + private boolean isMiui; + /** + * Miui 系统中的 mTmpMetrics 字段 + */ + private Field mTmpMetricsField; + /** + * 屏幕适配监听器,用于监听屏幕适配时的一些事件 + */ + private onAdaptListener mOnAdaptListener; + + static { + DEPENDENCY_ANDROIDX = findClassByClassName("androidx.fragment.app.FragmentActivity"); + DEPENDENCY_SUPPORT = findClassByClassName("android.support.v4.app.FragmentActivity"); + } + + private static boolean findClassByClassName(String className) { + boolean hasDependency; + try { + Class.forName(className); + hasDependency = true; + } catch (ClassNotFoundException e) { + hasDependency = false; + } + return hasDependency; + } + + public static AutoSizeConfig getInstance() { + if (sInstance == null) { + synchronized (AutoSizeConfig.class) { + if (sInstance == null) { + sInstance = new AutoSizeConfig(); + } + } + } + return sInstance; + } + + private AutoSizeConfig() { + } + + public Application getApplication() { + Preconditions.checkNotNull(mApplication, "Please call the AutoSizeConfig#init() first"); + return mApplication; + } + + /** + * v0.7.0 以后, 框架会在 APP 启动时自动调用此方法进行初始化, 使用者无需手动初始化, 初始化方法只能调用一次, 否则报错 + * 此方法默认使用以宽度进行等比例适配, 如想使用以高度进行等比例适配, 请调用 {@link #init(Application, boolean)} + * + * @param application {@link Application} + */ + AutoSizeConfig init(Application application) { + return init(application, true, null); + } + + /** + * v0.7.0 以后, 框架会在 APP 启动时自动调用此方法进行初始化, 使用者无需手动初始化, 初始化方法只能调用一次, 否则报错 + * 此方法使用默认的 {@link AutoAdaptStrategy} 策略, 如想使用自定义的 {@link AutoAdaptStrategy} 策略 + * 请调用 {@link #init(Application, boolean, AutoAdaptStrategy)} + * + * @param application {@link Application} + * @param isBaseOnWidth 详情请查看 {@link #isBaseOnWidth} 的注释 + */ + AutoSizeConfig init(Application application, boolean isBaseOnWidth) { + return init(application, isBaseOnWidth, null); + } + + /** + * v0.7.0 以后, 框架会在 APP 启动时自动调用此方法进行初始化, 使用者无需手动初始化, 初始化方法只能调用一次, 否则报错 + * + * @param application {@link Application} + * @param isBaseOnWidth 详情请查看 {@link #isBaseOnWidth} 的注释 + * @param strategy {@link AutoAdaptStrategy}, 传 {@code null} 则使用 {@link DefaultAutoAdaptStrategy} + */ + 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(); + final Configuration configuration = Resources.getSystem().getConfiguration(); + + //设置一个默认值, 避免在低配设备上因为获取 MetaData 过慢, 导致适配时未能正常获取到设计图尺寸 + //建议使用者在低配设备上主动在 Application#onCreate 中调用 setDesignWidthInDp 替代以使用 AndroidManifest 配置设计图尺寸的方式 + if (AutoSizeConfig.getInstance().getUnitsManager().getSupportSubunits() == Subunits.NONE) { + mDesignWidthInDp = 360; + mDesignHeightInDp = 640; + } else { + mDesignWidthInDp = 1080; + mDesignHeightInDp = 1920; + } + + getMetaData(application); + isVertical = application.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; + int[] screenSize = ScreenUtils.getScreenSize(application); + mScreenWidth = screenSize[0]; + mScreenHeight = screenSize[1]; + mStatusBarHeight = ScreenUtils.getStatusBarHeight(); + AutoSizeLog.d("designWidthInDp = " + mDesignWidthInDp + ", designHeightInDp = " + mDesignHeightInDp + ", screenWidth = " + mScreenWidth + ", screenHeight = " + mScreenHeight); + + mInitDensity = displayMetrics.density; + mInitDensityDpi = displayMetrics.densityDpi; + mInitScaledDensity = displayMetrics.scaledDensity; + mInitXdpi = displayMetrics.xdpi; + mInitScreenWidthDp = configuration.screenWidthDp; + mInitScreenHeightDp = configuration.screenHeightDp; + application.registerComponentCallbacks(new ComponentCallbacks() { + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (newConfig != null) { + if (newConfig.fontScale > 0) { + mInitScaledDensity = + Resources.getSystem().getDisplayMetrics().scaledDensity; + AutoSizeLog.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() { + + } + }); + AutoSizeLog.d("initDensity = " + mInitDensity + ", initScaledDensity = " + mInitScaledDensity); + mActivityLifecycleCallbacks = new ActivityLifecycleCallbacksImpl(new WrapperAutoAdaptStrategy(strategy == null ? new DefaultAutoAdaptStrategy() : strategy)); + application.registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks); + if ("MiuiResources".equals(application.getResources().getClass().getSimpleName()) || "XResources".equals(application.getResources().getClass().getSimpleName())) { + isMiui = true; + try { + mTmpMetricsField = Resources.class.getDeclaredField("mTmpMetrics"); + mTmpMetricsField.setAccessible(true); + } catch (Exception e) { + mTmpMetricsField = null; + } + } + return this; + } + + /** + * 重新开始框架的运行 + * 框架具有 热插拔 特性, 支持在项目运行中动态停止和重新启动适配功能 + */ + public void restart() { + Preconditions.checkNotNull(mActivityLifecycleCallbacks, "Please call the AutoSizeConfig#init() first"); + synchronized (AutoSizeConfig.class) { + if (isStop) { + mApplication.registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks); + isStop = false; + } + } + } + + /** + * 停止框架的运行 + * 框架具有 热插拔 特性, 支持在项目运行中动态停止和重新启动适配功能 + */ + public void stop(Activity activity) { + Preconditions.checkNotNull(mActivityLifecycleCallbacks, "Please call the AutoSizeConfig#init() first"); + synchronized (AutoSizeConfig.class) { + if (!isStop) { + mApplication.unregisterActivityLifecycleCallbacks(mActivityLifecycleCallbacks); + AutoSize.cancelAdapt(activity); + isStop = true; + } + } + } + + /** + * 设置屏幕适配逻辑策略类 + * + * @param autoAdaptStrategy {@link AutoAdaptStrategy} + */ + public AutoSizeConfig setAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy) { + Preconditions.checkNotNull(autoAdaptStrategy, "autoAdaptStrategy == null"); + Preconditions.checkNotNull(mActivityLifecycleCallbacks, "Please call the AutoSizeConfig#init() first"); + mActivityLifecycleCallbacks.setAutoAdaptStrategy(new WrapperAutoAdaptStrategy(autoAdaptStrategy)); + return this; + } + + /** + * 设置屏幕适配监听器 + * + * @param onAdaptListener {@link onAdaptListener} + */ + public AutoSizeConfig setOnAdaptListener(onAdaptListener onAdaptListener) { + Preconditions.checkNotNull(onAdaptListener, "onAdaptListener == null"); + mOnAdaptListener = onAdaptListener; + return this; + } + + /** + * 是否全局按照宽度进行等比例适配 + * + * @param baseOnWidth {@code true} 为按照宽度, {@code false} 为按照高度 + * @see #isBaseOnWidth 详情请查看这个字段的注释 + */ + public AutoSizeConfig setBaseOnWidth(boolean baseOnWidth) { + isBaseOnWidth = baseOnWidth; + return this; + } + + /** + * 是否使用设备的实际尺寸做适配 + * + * @param useDeviceSize {@code true} 为使用设备的实际尺寸 (包含状态栏), {@code false} 为不使用设备的实际尺寸 (不包含状态栏) + * @see #isUseDeviceSize 详情请查看这个字段的注释 + */ + public AutoSizeConfig setUseDeviceSize(boolean useDeviceSize) { + isUseDeviceSize = useDeviceSize; + return this; + } + + /** + * 是否打印 Log + * + * @param log {@code true} 为打印 + */ + public AutoSizeConfig setLog(boolean log) { + AutoSizeLog.setDebug(log); + return this; + } + + /** + * 是否让框架支持自定义 Fragment 的适配参数, 由于这个需求是比较少见的, 所以须要使用者手动开启 + * + * @param customFragment {@code true} 为支持 + */ + public AutoSizeConfig setCustomFragment(boolean customFragment) { + isCustomFragment = customFragment; + return this; + } + + /** + * 框架是否已经开启支持自定义 Fragment 的适配参数 + * + * @return {@code true} 为支持 + */ + public boolean isCustomFragment() { + return isCustomFragment; + } + + /** + * 框架是否已经停止运行 + * + * @return {@code false} 框架正在运行, {@code true} 框架已经停止运行 + */ + public boolean isStop() { + return isStop; + } + + /** + * {@link ExternalAdaptManager} 用来管理外部三方库 {@link Activity} 的适配 + * + * @return {@link #mExternalAdaptManager} + */ + public ExternalAdaptManager getExternalAdaptManager() { + return mExternalAdaptManager; + } + + /** + * {@link UnitsManager} 用来管理 AndroidAutoSize 支持的所有单位, AndroidAutoSize 支持五种单位 (dp、sp、pt、in、mm) + * + * @return {@link #mUnitsManager} + */ + public UnitsManager getUnitsManager() { + return mUnitsManager; + } + + /** + * 返回 {@link #mOnAdaptListener} + * + * @return {@link #mOnAdaptListener} + */ + public onAdaptListener getOnAdaptListener() { + return mOnAdaptListener; + } + + /** + * 返回 {@link #isBaseOnWidth} + * + * @return {@link #isBaseOnWidth} + */ + public boolean isBaseOnWidth() { + return isBaseOnWidth; + } + + /** + * 返回 {@link #isUseDeviceSize} + * + * @return {@link #isUseDeviceSize} + */ + public boolean isUseDeviceSize() { + return isUseDeviceSize; + } + + /** + * 返回 {@link #mScreenWidth} + * + * @return {@link #mScreenWidth} + */ + public int getScreenWidth() { + return mScreenWidth; + } + + /** + * 返回 {@link #mScreenHeight} + * + * @return {@link #mScreenHeight} + */ + public int getScreenHeight() { + return isUseDeviceSize() ? mScreenHeight : mScreenHeight - mStatusBarHeight; + } + + /** + * 获取 {@link #mDesignWidthInDp} + * + * @return {@link #mDesignWidthInDp} + */ + public int getDesignWidthInDp() { + Preconditions.checkArgument(mDesignWidthInDp > 0, "you must set " + KEY_DESIGN_WIDTH_IN_DP + " in your AndroidManifest file"); + return mDesignWidthInDp; + } + + /** + * 获取 {@link #mDesignHeightInDp} + * + * @return {@link #mDesignHeightInDp} + */ + public int getDesignHeightInDp() { + Preconditions.checkArgument(mDesignHeightInDp > 0, "you must set " + KEY_DESIGN_HEIGHT_IN_DP + " in your AndroidManifest file"); + return mDesignHeightInDp; + } + + /** + * 获取 {@link #mInitDensity} + * + * @return {@link #mInitDensity} + */ + public float getInitDensity() { + return mInitDensity; + } + + /** + * 获取 {@link #mInitDensityDpi} + * + * @return {@link #mInitDensityDpi} + */ + public int getInitDensityDpi() { + return mInitDensityDpi; + } + + /** + * 获取 {@link #mInitScaledDensity} + * + * @return {@link #mInitScaledDensity} + */ + public float getInitScaledDensity() { + return mInitScaledDensity; + } + + /** + * 获取 {@link #mInitXdpi} + * + * @return {@link #mInitXdpi} + */ + public float getInitXdpi() { + return mInitXdpi; + } + + /** + * 获取 {@link #mInitScreenWidthDp} + * + * @return {@link #mInitScreenWidthDp} + */ + public int getInitScreenWidthDp() { + return mInitScreenWidthDp; + } + + /** + * 获取 {@link #mInitScreenHeightDp} + * + * @return {@link #mInitScreenHeightDp} + */ + public int getInitScreenHeightDp() { + return mInitScreenHeightDp; + } + + /** + * 获取屏幕方向 + * + * @return {@code true} 为纵向, {@code false} 为横向 + */ + public boolean isVertical() { + return isVertical; + } + + /** + * 返回 {@link #isMiui} + * + * @return {@link #isMiui} + */ + public boolean isMiui() { + return isMiui; + } + + /** + * 返回 {@link #mTmpMetricsField} + * + * @return {@link #mTmpMetricsField} + */ + public Field getTmpMetricsField() { + return mTmpMetricsField; + } + + /** + * 设置屏幕方向 + * + * @param vertical {@code true} 为纵向, {@code false} 为横向 + */ + public AutoSizeConfig setVertical(boolean vertical) { + isVertical = vertical; + return this; + } + + /** + * 是否屏蔽系统字体大小对 AndroidAutoSize 的影响, 如果为 {@code true}, App 内的字体的大小将不会跟随系统设置中字体大小的改变 + * 如果为 {@code false}, 则会跟随系统设置中字体大小的改变, 默认为 {@code false} + * + * @return {@link #isExcludeFontScale} + */ + public boolean isExcludeFontScale() { + return isExcludeFontScale; + } + + /** + * 是否屏蔽系统字体大小对 AndroidAutoSize 的影响, 如果为 {@code true}, App 内的字体的大小将不会跟随系统设置中字体大小的改变 + * 如果为 {@code false}, 则会跟随系统设置中字体大小的改变, 默认为 {@code false} + * + * @param excludeFontScale 是否屏蔽 + */ + public AutoSizeConfig setExcludeFontScale(boolean excludeFontScale) { + isExcludeFontScale = excludeFontScale; + return this; + } + + /** + * 区别于系统字体大小的放大比例, AndroidAutoSize 允许 APP 内部可以独立于系统字体大小之外,独自拥有全局调节 APP 字体大小的能力 + * 当然, 在 APP 内您必须使用 sp 来作为字体的单位, 否则此功能无效 + * + * @param fontScale 字体大小放大的比例, 设为 0 则取消此功能 + */ + public AutoSizeConfig setPrivateFontScale(float fontScale) { + privateFontScale = fontScale; + return this; + } + + /** + * 区别于系统字体大小的放大比例, AndroidAutoSize 允许 APP 内部可以独立于系统字体大小之外,独自拥有全局调节 APP 字体大小的能力 + * 当然, 在 APP 内您必须使用 sp 来作为字体的单位, 否则此功能无效 + * + * @return 私有的字体大小放大比例 + */ + public float getPrivateFontScale() { + return privateFontScale; + } + + /** + * 设置屏幕宽度 + * + * @param screenWidth 屏幕宽度 + */ + public AutoSizeConfig setScreenWidth(int screenWidth) { + Preconditions.checkArgument(screenWidth > 0, "screenWidth must be > 0"); + mScreenWidth = screenWidth; + return this; + } + + /** + * 设置屏幕高度 + * + * @param screenHeight 屏幕高度 (需要包含状态栏) + */ + public AutoSizeConfig setScreenHeight(int screenHeight) { + Preconditions.checkArgument(screenHeight > 0, "screenHeight must be > 0"); + mScreenHeight = screenHeight; + return this; + } + + /** + * 设置全局设计图宽度 + * + * @param designWidthInDp 设计图宽度 + */ + public AutoSizeConfig setDesignWidthInDp(int designWidthInDp) { + Preconditions.checkArgument(designWidthInDp > 0, "designWidthInDp must be > 0"); + mDesignWidthInDp = designWidthInDp; + return this; + } + + /** + * 设置全局设计图高度 + * + * @param designHeightInDp 设计图高度 + */ + public AutoSizeConfig setDesignHeightInDp(int designHeightInDp) { + Preconditions.checkArgument(designHeightInDp > 0, "designHeightInDp must be > 0"); + mDesignHeightInDp = designHeightInDp; + return this; + } + + /** + * 设置状态栏高度 + * + * @param statusBarHeight 状态栏高度 + */ + public AutoSizeConfig setStatusBarHeight(int statusBarHeight) { + Preconditions.checkArgument(statusBarHeight > 0, "statusBarHeight must be > 0"); + mStatusBarHeight = statusBarHeight; + return this; + } + + /** + * 获取使用者在 AndroidManifest 中填写的 Meta 信息 + *

+ * Example usage: + *

+     * 
+     * 
+     * 
+ * + * @param context {@link Context} + */ + private void getMetaData(final Context context) { + new Thread(new Runnable() { + @Override + public void run() { + PackageManager packageManager = context.getPackageManager(); + ApplicationInfo applicationInfo; + try { + applicationInfo = packageManager.getApplicationInfo(context + .getPackageName(), PackageManager.GET_META_DATA); + if (applicationInfo != null && applicationInfo.metaData != null) { + if (applicationInfo.metaData.containsKey(KEY_DESIGN_WIDTH_IN_DP)) { + mDesignWidthInDp = (int) applicationInfo.metaData.get(KEY_DESIGN_WIDTH_IN_DP); + } + if (applicationInfo.metaData.containsKey(KEY_DESIGN_HEIGHT_IN_DP)) { + mDesignHeightInDp = (int) applicationInfo.metaData.get(KEY_DESIGN_HEIGHT_IN_DP); + } + } + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + } + }).start(); + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/DefaultAutoAdaptStrategy.java b/autosize/src/main/java/me/jessyan/autosize/DefaultAutoAdaptStrategy.java new file mode 100644 index 0000000..8e4c09a --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/DefaultAutoAdaptStrategy.java @@ -0,0 +1,76 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize; + +import android.app.Activity; +import android.app.Application; + +import java.util.Locale; + +import me.jessyan.autosize.external.ExternalAdaptInfo; +import me.jessyan.autosize.internal.CancelAdapt; +import me.jessyan.autosize.internal.CustomAdapt; +import me.jessyan.autosize.utils.AutoSizeLog; + +/** + * ================================================ + * 屏幕适配逻辑策略默认实现类, 可通过 {@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())) { + AutoSizeLog.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) { + AutoSizeLog.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) { + AutoSizeLog.w(String.format(Locale.ENGLISH, "%s canceled the adaptation!", target.getClass().getName())); + AutoSize.cancelAdapt(activity); + return; + } + + //如果 target 实现 CustomAdapt 接口表示该 target 想自定义一些用于适配的参数, 从而改变最终的适配效果 + if (target instanceof CustomAdapt) { + AutoSizeLog.d(String.format(Locale.ENGLISH, "%s implemented by %s!", target.getClass().getName(), CustomAdapt.class.getName())); + AutoSize.autoConvertDensityOfCustomAdapt(activity, (CustomAdapt) target); + } else { + AutoSizeLog.d(String.format(Locale.ENGLISH, "%s used the global configuration.", target.getClass().getName())); + AutoSize.autoConvertDensityOfGlobal(activity); + } + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/DisplayMetricsInfo.java b/autosize/src/main/java/me/jessyan/autosize/DisplayMetricsInfo.java new file mode 100644 index 0000000..7be23f2 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/DisplayMetricsInfo.java @@ -0,0 +1,150 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.DisplayMetrics; + +/** + * ================================================ + * {@link DisplayMetrics} 封装类 + *

+ * Created by JessYan on 2018/8/11 16:42 + * Contact me + * Follow me + * ================================================ + */ +public class DisplayMetricsInfo implements Parcelable { + private float density; + private int densityDpi; + private float scaledDensity; + private float xdpi; + private int screenWidthDp; + private int screenHeightDp; + + public DisplayMetricsInfo(float density, int densityDpi, float scaledDensity, float xdpi) { + this.density = density; + this.densityDpi = densityDpi; + this.scaledDensity = scaledDensity; + this.xdpi = xdpi; + } + + public DisplayMetricsInfo(float density, int densityDpi, float scaledDensity, float xdpi, int screenWidthDp, int screenHeightDp) { + this.density = density; + this.densityDpi = densityDpi; + this.scaledDensity = scaledDensity; + this.xdpi = xdpi; + this.screenWidthDp = screenWidthDp; + this.screenHeightDp = screenHeightDp; + } + + public float getDensity() { + return density; + } + + public void setDensity(float density) { + this.density = density; + } + + public int getDensityDpi() { + return densityDpi; + } + + public void setDensityDpi(int densityDpi) { + this.densityDpi = densityDpi; + } + + public float getScaledDensity() { + return scaledDensity; + } + + public void setScaledDensity(float scaledDensity) { + this.scaledDensity = scaledDensity; + } + + public float getXdpi() { + return xdpi; + } + + public void setXdpi(float xdpi) { + this.xdpi = xdpi; + } + + public int getScreenWidthDp() { + return screenWidthDp; + } + + public void setScreenWidthDp(int screenWidthDp) { + this.screenWidthDp = screenWidthDp; + } + + public int getScreenHeightDp() { + return screenHeightDp; + } + + public void setScreenHeightDp(int screenHeightDp) { + this.screenHeightDp = screenHeightDp; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeFloat(this.density); + dest.writeInt(this.densityDpi); + dest.writeFloat(this.scaledDensity); + dest.writeFloat(this.xdpi); + dest.writeInt(this.screenWidthDp); + dest.writeInt(this.screenHeightDp); + } + + protected DisplayMetricsInfo(Parcel in) { + this.density = in.readFloat(); + this.densityDpi = in.readInt(); + this.scaledDensity = in.readFloat(); + this.xdpi = in.readFloat(); + this.screenWidthDp = in.readInt(); + this.screenHeightDp = in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public DisplayMetricsInfo createFromParcel(Parcel source) { + return new DisplayMetricsInfo(source); + } + + @Override + public DisplayMetricsInfo[] newArray(int size) { + return new DisplayMetricsInfo[size]; + } + }; + + @Override + public String toString() { + return "DisplayMetricsInfo{" + + "density=" + density + + ", densityDpi=" + densityDpi + + ", scaledDensity=" + scaledDensity + + ", xdpi=" + xdpi + + ", screenWidthDp=" + screenWidthDp + + ", screenHeightDp=" + screenHeightDp + + '}'; + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/FragmentLifecycleCallbacksImpl.java b/autosize/src/main/java/me/jessyan/autosize/FragmentLifecycleCallbacksImpl.java new file mode 100644 index 0000000..3e30a39 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/FragmentLifecycleCallbacksImpl.java @@ -0,0 +1,61 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize; + + +import android.os.Build; +import android.os.Bundle; + +import androidx.annotation.RequiresApi; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +/** + * ================================================ + * {@link FragmentLifecycleCallbacksImpl} 可用来代替在 BaseFragment 中加入适配代码的传统方式 + * {@link FragmentLifecycleCallbacksImpl} 这种方案类似于 AOP, 面向接口, 侵入性低, 方便统一管理, 扩展性强, 并且也支持适配三方库的 {@link Fragment} + *

+ * Created by JessYan on 2018/8/25 13:52 + * Contact me + * Follow me + * ================================================ + */ +@RequiresApi(api = Build.VERSION_CODES.O) +public class FragmentLifecycleCallbacksImpl extends FragmentManager.FragmentLifecycleCallbacks { + /** + * 屏幕适配逻辑策略类 + */ + private AutoAdaptStrategy mAutoAdaptStrategy; + + public FragmentLifecycleCallbacksImpl(AutoAdaptStrategy autoAdaptStrategy) { + mAutoAdaptStrategy = autoAdaptStrategy; + } + + @Override + public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) { + if (mAutoAdaptStrategy != null) { + mAutoAdaptStrategy.applyAdapt(f, f.getActivity()); + } + } + + /** + * 设置屏幕适配逻辑策略类 + * + * @param autoAdaptStrategy {@link AutoAdaptStrategy} + */ + public void setAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy) { + mAutoAdaptStrategy = autoAdaptStrategy; + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/FragmentLifecycleCallbacksImplToAndroidx.java b/autosize/src/main/java/me/jessyan/autosize/FragmentLifecycleCallbacksImplToAndroidx.java new file mode 100644 index 0000000..0b03fb0 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/FragmentLifecycleCallbacksImplToAndroidx.java @@ -0,0 +1,62 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize; + +import android.os.Build; +import android.os.Bundle; + +import androidx.annotation.RequiresApi; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + + +/** + * ================================================ + * {@link FragmentLifecycleCallbacksImplToAndroidx} 可用来代替在 BaseFragment 中加入适配代码的传统方式 + * {@link FragmentLifecycleCallbacksImplToAndroidx} 这种方案类似于 AOP, 面向接口, 侵入性低, 方便统一管理, 扩展性强, 并且也支持适配三方库的 {@link Fragment} + *

+ * Created by JessYan on 2018/8/25 13:52 + * Contact me + * Follow me + * ================================================ + */ +@RequiresApi(api = Build.VERSION_CODES.O) +public class FragmentLifecycleCallbacksImplToAndroidx extends FragmentManager.FragmentLifecycleCallbacks { + /** + * 屏幕适配逻辑策略类 + */ + private AutoAdaptStrategy mAutoAdaptStrategy; + + public FragmentLifecycleCallbacksImplToAndroidx(AutoAdaptStrategy autoAdaptStrategy) { + mAutoAdaptStrategy = autoAdaptStrategy; + } + + @Override + public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) { + if (mAutoAdaptStrategy != null) { + mAutoAdaptStrategy.applyAdapt(f, f.getActivity()); + } + } + + /** + * 设置屏幕适配逻辑策略类 + * + * @param autoAdaptStrategy {@link AutoAdaptStrategy} + */ + public void setAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy) { + mAutoAdaptStrategy = autoAdaptStrategy; + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/InitProvider.java b/autosize/src/main/java/me/jessyan/autosize/InitProvider.java new file mode 100644 index 0000000..4909842 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/InitProvider.java @@ -0,0 +1,73 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize; + +import android.content.Context; +import android.app.Application; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +import me.jessyan.autosize.utils.AutoSizeUtils; + +/** + * ================================================ + * 通过声明 {@link ContentProvider} 自动完成初始化 + * Created by JessYan on 2018/8/19 11:55 + * Contact me + * Follow me + * ================================================ + */ +public class InitProvider extends ContentProvider { + @Override + public boolean onCreate() { + Context application = getContext().getApplicationContext(); + if (application == null) { + application = AutoSizeUtils.getApplicationByReflect(); + } + AutoSizeConfig.getInstance() + .setLog(true) + .init((Application) application) + .setUseDeviceSize(false); + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + return null; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/WrapperAutoAdaptStrategy.java b/autosize/src/main/java/me/jessyan/autosize/WrapperAutoAdaptStrategy.java new file mode 100644 index 0000000..641824f --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/WrapperAutoAdaptStrategy.java @@ -0,0 +1,49 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize; + +import android.app.Activity; + +/** + * ================================================ + * {@link AutoAdaptStrategy} 的包装者, 用于给 {@link AutoAdaptStrategy} 的实现类增加一些额外的职责 + *

+ * Created by JessYan on 2018/10/30 15:07 + * Contact me + * Follow me + * ================================================ + */ +public class WrapperAutoAdaptStrategy implements AutoAdaptStrategy { + private final AutoAdaptStrategy mAutoAdaptStrategy; + + public WrapperAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy) { + mAutoAdaptStrategy = autoAdaptStrategy; + } + + @Override + public void applyAdapt(Object target, Activity activity) { + onAdaptListener onAdaptListener = AutoSizeConfig.getInstance().getOnAdaptListener(); + if (onAdaptListener != null){ + onAdaptListener.onAdaptBefore(target, activity); + } + if (mAutoAdaptStrategy != null) { + mAutoAdaptStrategy.applyAdapt(target, activity); + } + if (onAdaptListener != null){ + onAdaptListener.onAdaptAfter(target, activity); + } + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/external/ExternalAdaptInfo.java b/autosize/src/main/java/me/jessyan/autosize/external/ExternalAdaptInfo.java new file mode 100644 index 0000000..d02eb2c --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/external/ExternalAdaptInfo.java @@ -0,0 +1,108 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize.external; + +import android.app.Activity; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * ================================================ + * {@link ExternalAdaptInfo} 用来存储外部三方库的适配参数, 因为 AndroidAutoSize 默认会对项目中的所有模块都进行适配 + * 三方库的 {@link Activity} 也不例外, 但三方库的适配参数可能和自己项目中的适配参数不一致, 导致三方库的适配效果和理想的效果差别很大 + * 所以需要向 AndroidAutoSize 提供三方库的适配参数, 已完成对三方库的屏幕适配 + *

+ * Created by JessYan on 2018/8/9 18:19 + * Contact me + * Follow me + * ================================================ + */ +public class ExternalAdaptInfo implements Parcelable { + /** + * 是否按照宽度进行等比例适配 (为了保证在高宽比不同的屏幕上也能正常适配, 所以只能在宽度和高度之中选一个作为基准进行适配) + * {@code true} 为按照宽度适配, {@code false} 为按照高度适配 + */ + private boolean isBaseOnWidth; + /** + * 设计图上的设计尺寸, 单位 dp (三方库页面的设计图尺寸可能无法获知, 所以如果想让三方库的适配效果达到最好, 只有靠不断的尝试) + * {@link #sizeInDp} 须配合 {@link #isBaseOnWidth} 使用, 规则如下: + * 如果 {@link #isBaseOnWidth} 设置为 {@code true}, {@link #sizeInDp} 则应该设置为设计图的总宽度 + * 如果 {@link #isBaseOnWidth} 设置为 {@code false}, {@link #sizeInDp} 则应该设置为设计图的总高度 + * 如果您不需要自定义设计图上的设计尺寸, 想继续使用在 AndroidManifest 中填写的设计图尺寸, {@link #sizeInDp} 则设置为 {@code 0} + */ + private float sizeInDp; + + public ExternalAdaptInfo(boolean isBaseOnWidth) { + this.isBaseOnWidth = isBaseOnWidth; + } + + public ExternalAdaptInfo(boolean isBaseOnWidth, float sizeInDp) { + this.isBaseOnWidth = isBaseOnWidth; + this.sizeInDp = sizeInDp; + } + + public boolean isBaseOnWidth() { + return isBaseOnWidth; + } + + public void setBaseOnWidth(boolean baseOnWidth) { + isBaseOnWidth = baseOnWidth; + } + + public float getSizeInDp() { + return sizeInDp; + } + + public void setSizeInDp(float sizeInDp) { + this.sizeInDp = sizeInDp; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte(this.isBaseOnWidth ? (byte) 1 : (byte) 0); + dest.writeFloat(this.sizeInDp); + } + + protected ExternalAdaptInfo(Parcel in) { + this.isBaseOnWidth = in.readByte() != 0; + this.sizeInDp = in.readFloat(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public ExternalAdaptInfo createFromParcel(Parcel source) { + return new ExternalAdaptInfo(source); + } + + @Override + public ExternalAdaptInfo[] newArray(int size) { + return new ExternalAdaptInfo[size]; + } + }; + + @Override + public String toString() { + return "ExternalAdaptInfo{" + + "isBaseOnWidth=" + isBaseOnWidth + + ", sizeInDp=" + sizeInDp + + '}'; + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/external/ExternalAdaptManager.java b/autosize/src/main/java/me/jessyan/autosize/external/ExternalAdaptManager.java new file mode 100644 index 0000000..6338103 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/external/ExternalAdaptManager.java @@ -0,0 +1,141 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize.external; + +import android.app.Activity; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import me.jessyan.autosize.AutoSizeConfig; +import me.jessyan.autosize.utils.Preconditions; + +/** + * ================================================ + * 管理三方库的适配信息和状态, 通过 {@link AutoSizeConfig#getExternalAdaptManager()} 获取, 切勿自己 new + * AndroidAutoSize 通过实现接口的方式来让每个 {@link Activity} 都具有自定义适配参数的功能, 从而让每个 {@link Activity} 都可以自定义适配效果 + * 但通过远程依赖的三方库并不能修改源码, 所以也不能让三方库的 {@link Activity} 实现接口, 实现接口的方式就显得无能为力 + * {@link ExternalAdaptManager} 就是专门用来处理这个问题, 项目初始化时把对应的三方库 {@link Activity} 传入 {@link ExternalAdaptManager} 即可 + *

+ * Created by JessYan on 2018/8/10 14:40 + * Contact me + * Follow me + * ================================================ + */ +public class ExternalAdaptManager { + private List mCancelAdaptList; + private Map mExternalAdaptInfos; + private boolean isRun; + + /** + * 将不需要适配的第三方库 {@link Activity} 添加进来 (但不局限于三方库), 即可让该 {@link Activity} 的适配效果失效 + *

+ * 支持链式调用, 如: + * {@link ExternalAdaptManager#addCancelAdaptOfActivity(Class)#addCancelAdaptOfActivity(Class)} + * + * @param targetClass {@link Activity} class, Fragment class + */ + public synchronized ExternalAdaptManager addCancelAdaptOfActivity(Class targetClass) { + Preconditions.checkNotNull(targetClass, "targetClass == null"); + if (!isRun) { + isRun = true; + } + if (mCancelAdaptList == null) { + mCancelAdaptList = new ArrayList<>(); + } + mCancelAdaptList.add(targetClass.getCanonicalName()); + return this; + } + + /** + * 将需要提供自定义适配参数的三方库 {@link Activity} 添加进来 (但不局限于三方库), 即可让该 {@link Activity} 根据自己提供的适配参数进行适配 + * 默认的全局适配参数不能满足您时可以使用此方法 + *

+ * 一般用于三方库的 Activity, 因为三方库的设计图尺寸可能和项目自身的设计图尺寸不一致, 所以要想完美适配三方库的页面 + * 就需要提供三方库的设计图尺寸, 以及适配的方向 (以宽为基准还是高为基准?) + * 三方库页面的设计图尺寸可能无法获知, 所以如果想让三方库的适配效果达到最好, 只有靠不断的尝试 + * 由于 AndroidAutoSize 可以让布局在所有设备上都等比例缩放, 所以只要您在一个设备上测试出了一个最完美的设计图尺寸 + * 那这个三方库页面在其他设备上也会呈现出同样的适配效果, 等比例缩放, 所以也就完成了三方库页面的屏幕适配 + * 即使在不改三方库源码的情况下也可以完美适配三方库的页面, 这就是 AndroidAutoSize 的优势 + * 但前提是三方库页面的布局使用的是 dp 和 sp, 如果布局全部使用的 px, 那 AndroidAutoSize 也将无能为力 + *

+ * 支持链式调用, 如: + * {@link ExternalAdaptManager#addExternalAdaptInfoOfActivity(Class, ExternalAdaptInfo)#addExternalAdaptInfoOfActivity(Class, ExternalAdaptInfo)} + * + * @param targetClass {@link Activity} class, Fragment class + * @param info {@link ExternalAdaptInfo} 适配参数 + */ + public synchronized ExternalAdaptManager addExternalAdaptInfoOfActivity(Class targetClass, ExternalAdaptInfo info) { + Preconditions.checkNotNull(targetClass, "targetClass == null"); + if (!isRun) { + isRun = true; + } + if (mExternalAdaptInfos == null) { + mExternalAdaptInfos = new HashMap<>(16); + } + mExternalAdaptInfos.put(targetClass.getCanonicalName(), info); + return this; + } + + /** + * 这个 {@link Activity} 是否存在在取消适配的列表中, 如果在, 则该 {@link Activity} 适配失效 + * + * @param targetClass {@link Activity} class, Fragment class + * @return {@code true} 为存在, {@code false} 为不存在 + */ + public synchronized boolean isCancelAdapt(Class targetClass) { + Preconditions.checkNotNull(targetClass, "targetClass == null"); + if (mCancelAdaptList == null) { + return false; + } + return mCancelAdaptList.contains(targetClass.getCanonicalName()); + } + + /** + * 这个 {@link Activity} 是否提供有自定义的适配参数, 如果有则使用此适配参数进行适配 + * + * @param targetClass {@link Activity} class, Fragment class + * @return 如果返回 {@code null} 则说明该 {@link Activity} 没有提供自定义的适配参数 + */ + public synchronized ExternalAdaptInfo getExternalAdaptInfoOfActivity(Class targetClass) { + Preconditions.checkNotNull(targetClass, "targetClass == null"); + if (mExternalAdaptInfos == null) { + return null; + } + return mExternalAdaptInfos.get(targetClass.getCanonicalName()); + } + + /** + * 此管理器是否已经启动 + * + * @return {@code true} 为已经启动, {@code false} 为没有启动 + */ + public boolean isRun() { + return isRun; + } + + /** + * 设置管理器的运行状态 + * + * @param run {@code true} 为让管理器启动运行, {@code false} 为让管理器停止运行 + */ + public ExternalAdaptManager setRun(boolean run) { + isRun = run; + return this; + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/internal/CancelAdapt.java b/autosize/src/main/java/me/jessyan/autosize/internal/CancelAdapt.java new file mode 100644 index 0000000..1862bbf --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/internal/CancelAdapt.java @@ -0,0 +1,32 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize.internal; + +import android.app.Activity; + +/** + * ================================================ + * AndroidAutoSize 默认项目中的所有模块都使用适配功能, 三方库的 {@link Activity} 也不例外 + * 如果某个页面不想使用适配功能, 请让该页面 {@link Activity} 实现此接口 + * 实现此接口表示放弃适配, 所有的适配效果都将失效 + *

+ * Created by JessYan on 2018/8/9 09:54 + * Contact me + * Follow me + * ================================================ + */ +public interface CancelAdapt { +} diff --git a/autosize/src/main/java/me/jessyan/autosize/internal/CustomAdapt.java b/autosize/src/main/java/me/jessyan/autosize/internal/CustomAdapt.java new file mode 100644 index 0000000..9ca4569 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/internal/CustomAdapt.java @@ -0,0 +1,49 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize.internal; + +import android.app.Activity; + +/** + * ================================================ + * 如果某些页面不想使用 AndroidAutoSize 初始化时设置的默认适配参数, 请让该页面 {@link Activity} 实现此接口 + * 实现此接口即可自定义用于适配的一些参数, 从而影响最终的适配效果 + *

+ * Created by JessYan on 2018/8/9 10:25 + * Contact me + * Follow me + * ================================================ + */ +public interface CustomAdapt { + + /** + * 是否按照宽度进行等比例适配 (为了保证在高宽比不同的屏幕上也能正常适配, 所以只能在宽度和高度之中选一个作为基准进行适配) + * + * @return {@code true} 为按照宽度适配, {@code false} 为按照高度适配 + */ + boolean isBaseOnWidth(); + + /** + * 返回设计图上的设计尺寸, 单位 dp + * {@link #getSizeInDp} 须配合 {@link #isBaseOnWidth()} 使用, 规则如下: + * 如果 {@link #isBaseOnWidth()} 返回 {@code true}, {@link #getSizeInDp} 则应该返回设计图的总宽度 + * 如果 {@link #isBaseOnWidth()} 返回 {@code false}, {@link #getSizeInDp} 则应该返回设计图的总高度 + * 如果您不需要自定义设计图上的设计尺寸, 想继续使用在 AndroidManifest 中填写的设计图尺寸, {@link #getSizeInDp} 则返回 {@code 0} + * + * @return 设计图上的设计尺寸, 单位 dp + */ + float getSizeInDp(); +} diff --git a/autosize/src/main/java/me/jessyan/autosize/onAdaptListener.java b/autosize/src/main/java/me/jessyan/autosize/onAdaptListener.java new file mode 100644 index 0000000..60f9779 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/onAdaptListener.java @@ -0,0 +1,45 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize; + +import android.app.Activity; + +/** + * ================================================ + * 屏幕适配监听器,用于监听屏幕适配时的一些事件 + *

+ * Created by JessYan on 2018/10/30 16:29 + * Contact me + * Follow me + * ================================================ + */ +public interface onAdaptListener { + /** + * 在屏幕适配前调用 + * + * @param target 需要屏幕适配的对象 (可能是 {@link Activity} 或者 Fragment) + * @param activity 当前 {@link Activity} + */ + void onAdaptBefore(Object target, Activity activity); + + /** + * 在屏幕适配后调用 + * + * @param target 需要屏幕适配的对象 (可能是 {@link Activity} 或者 Fragment) + * @param activity 当前 {@link Activity} + */ + void onAdaptAfter(Object target, Activity activity); +} diff --git a/autosize/src/main/java/me/jessyan/autosize/unit/Subunits.java b/autosize/src/main/java/me/jessyan/autosize/unit/Subunits.java new file mode 100644 index 0000000..7fbb1b1 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/unit/Subunits.java @@ -0,0 +1,53 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize.unit; + +import android.util.DisplayMetrics; + +/** + * ================================================ + * AndroidAutoSize 支持一些在 Android 系统上比较少见的单位作为副单位, 用于规避修改 {@link DisplayMetrics#density} + * 所造成的对于其他使用 dp 布局的系统控件或三方库控件的不良影响 + *

+ * Created by JessYan on 2018/8/28 10:27 + * Contact me + * Follow me + * ================================================ + */ +public enum Subunits { + /** + * 不使用副单位 + */ + NONE, + /** + * 单位 pt + * + * @see android.util.TypedValue#COMPLEX_UNIT_PT + */ + PT, + /** + * 单位 in + * + * @see android.util.TypedValue#COMPLEX_UNIT_IN + */ + IN, + /** + * 单位 mm + * + * @see android.util.TypedValue#COMPLEX_UNIT_MM + */ + MM +} diff --git a/autosize/src/main/java/me/jessyan/autosize/unit/UnitsManager.java b/autosize/src/main/java/me/jessyan/autosize/unit/UnitsManager.java new file mode 100644 index 0000000..95177a6 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/unit/UnitsManager.java @@ -0,0 +1,213 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize.unit; + +import android.util.DisplayMetrics; + +import me.jessyan.autosize.utils.Preconditions; + +/** + * ================================================ + * 管理 AndroidAutoSize 支持的所有单位, AndroidAutoSize 支持五种单位 (dp、sp、pt、in、mm) + * 其中 dp、sp 这两个是比较常见的单位, 作为 AndroidAutoSize 的主单位, 默认被 AndroidAutoSize 支持 + * pt、in、mm 这三个是比较少见的单位, 只可以选择其中的一个, 作为 AndroidAutoSize 的副单位, 与 dp、sp 一起被 AndroidAutoSize 支持 + * 副单位是用于规避修改 {@link DisplayMetrics#density} 所造成的对于其他使用 dp 布局的系统控件或三方库控件的不良影响 + * 您选择什么单位, 就在 layout 文件中用什么单位布局 + *

+ * 两个主单位和一个副单位, 可以随时使用下面的方法关闭和重新开启对它们的支持 + * 如果您想完全规避修改 {@link DisplayMetrics#density} 所造成的对于其他使用 dp 布局的系统控件或三方库控件的不良影响 + * 那请调用 {@link #setSupportDP}、{@link #setSupportSP} 都设置为 {@code false}, 停止对两个主单位的支持 (如果开启 sp, 对其他三方库控件影响不大, 也可以不关闭对 sp 的支持) + * 并调用 {@link #setSupportSubunits} 从三个冷门单位中选择一个作为副单位 (三个单位的效果都是一样的, 按自己的喜好选择, 比如我就喜欢 mm, 翻译为中文是妹妹的意思) + * 然后在 layout 文件中只使用这个副单位进行布局, 这样就可以完全规避修改 {@link DisplayMetrics#density} 所造成的问题 + * 因为 dp、sp 这两个单位在其他系统控件或三方库控件中都非常常见, 但三个冷门单位却非常少见 + *

+ * Created by JessYan on 2018/8/28 10:21 + * Contact me + * Follow me + * ================================================ + */ +public class UnitsManager { + /** + * 设计图上的总宽度, 建议单位为 px, 当使用者想将旧项目从主单位过渡到副单位, 或从副单位过渡到主单位时使用 + * 因为在使用主单位时, 建议在 AndroidManifest 中填写设计图的 dp 尺寸, 比如 360 * 640 + * 而副单位有一个特性是可以直接在 AndroidManifest 中填写设计图的 px 尺寸, 比如 1080 * 1920 + * 但在 AndroidManifest 中却只能填写一套设计图尺寸, 并且已经填写了主单位的设计图尺寸 + * 所以当项目中同时存在副单位和主单位, 并且副单位的设计图尺寸与主单位的设计图尺寸不同时, 就需要在 {@link UnitsManager} 中保存副单位的设计图尺寸 + */ + private float mDesignWidth; + /** + * 设计图上的总高度, 建议单位为 px, 当使用者想将旧项目从主单位过渡到副单位, 或从副单位过渡到主单位时使用 + * 因为在使用主单位时, 建议在 AndroidManifest 中填写设计图的 dp 尺寸, 比如 360 * 640 + * 而副单位有一个特性是可以直接在 AndroidManifest 中填写设计图的 px 尺寸, 比如 1080 * 1920 + * 但在 AndroidManifest 中却只能填写一套设计图尺寸, 并且已经填写了主单位的设计图尺寸 + * 所以当项目中同时存在副单位和主单位, 并且副单位的设计图尺寸与主单位的设计图尺寸不同时, 就需要在 {@link UnitsManager} 中保存副单位的设计图尺寸 + */ + private float mDesignHeight; + /** + * 是否支持 dp 单位, 默认支持 + */ + private boolean isSupportDP = true; + /** + * 是否支持 sp 单位, 默认支持 + */ + private boolean isSupportSP = true; + /** + * 是否支持副单位, 以什么为副单位? 默认不支持 + */ + private Subunits mSupportSubunits = Subunits.NONE; + /** + * 是否支持 ScreenSizeDp 修改, 默认不支持 + */ + private boolean isSupportScreenSizeDP = false; + + /** + * 设置设计图尺寸 + * + * @param designWidth 设计图上的总宽度, 建议单位为 px + * @param designHeight 设计图上的总高度, 建议单位为 px + * @return {@link UnitsManager} + * @see #mDesignWidth 详情请查看这个字段的注释 + * @see #mDesignHeight 详情请查看这个字段的注释 + */ + public UnitsManager setDesignSize(float designWidth, float designHeight) { + setDesignWidth(designWidth); + setDesignHeight(designHeight); + return this; + } + + /** + * 返回 {@link #mDesignWidth} + * + * @return {@link #mDesignWidth} + */ + public float getDesignWidth() { + return mDesignWidth; + } + + /** + * 设置设计图上的总宽度, 建议单位为 px + * + * @param designWidth 设计图上的总宽度, 建议单位为 px + * @return {@link UnitsManager} + * @see #mDesignWidth 详情请查看这个字段的注释 + */ + public UnitsManager setDesignWidth(float designWidth) { + Preconditions.checkArgument(designWidth > 0, "designWidth must be > 0"); + mDesignWidth = designWidth; + return this; + } + + /** + * 返回 {@link #mDesignHeight} + * + * @return {@link #mDesignHeight} + */ + public float getDesignHeight() { + return mDesignHeight; + } + + /** + * 设置设计图上的总高度, 建议单位为 px + * + * @param designHeight 设计图上的总高度, 建议单位为 px + * @return {@link UnitsManager} + * @see #mDesignHeight 详情请查看这个字段的注释 + */ + public UnitsManager setDesignHeight(float designHeight) { + Preconditions.checkArgument(designHeight > 0, "designHeight must be > 0"); + mDesignHeight = designHeight; + return this; + } + + /** + * 是否支持 dp 单位, 默认支持, 详情请看类文件的注释 {@link UnitsManager} + * + * @return {@code true} 为支持, {@code false} 为不支持 + */ + public boolean isSupportDP() { + return isSupportDP; + } + + /** + * 是否让 AndroidAutoSize 支持 dp 单位, 默认支持, 详情请看类文件的注释 {@link UnitsManager} + * + * @param supportDP {@code true} 为支持, {@code false} 为不支持 + */ + public UnitsManager setSupportDP(boolean supportDP) { + isSupportDP = supportDP; + return this; + } + + /** + * 是否支持 sp 单位, 默认支持, 详情请看类文件的注释 {@link UnitsManager} + * + * @return {@code true} 为支持, {@code false} 为不支持 + */ + public boolean isSupportSP() { + return isSupportSP; + } + + /** + * 是否让 AndroidAutoSize 支持 sp 单位, 默认支持, 详情请看类文件的注释 {@link UnitsManager} + * + * @param supportSP {@code true} 为支持, {@code false} 为不支持 + */ + public UnitsManager setSupportSP(boolean supportSP) { + isSupportSP = supportSP; + return this; + } + + /** + * AndroidAutoSize 以什么单位为副单位, 默认为 {@link Subunits#NONE}, 即不支持副单位, 详情请看类文件的注释 {@link UnitsManager} + * + * @return {@link Subunits} + */ + public Subunits getSupportSubunits() { + return mSupportSubunits; + } + + /** + * 是否支持 ScreenSizeDp 修改, 默认不支持, 详情请看类文件的注释 {@link UnitsManager} + * + * @return {@code true} 为支持, {@code false} 为不支持 + */ + public boolean isSupportScreenSizeDP() { + return isSupportScreenSizeDP; + } + + /** + * 是否让 AndroidAutoSize 支持 ScreenSizeDp 修改, 默认不支持, 详情请看类文件的注释 {@link UnitsManager} + * + * @param supportScreenSizeDP {@code true} 为支持, {@code false} 为不支持 + */ + public UnitsManager setSupportScreenSizeDP(boolean supportScreenSizeDP) { + isSupportScreenSizeDP = supportScreenSizeDP; + return this; + } + + /** + * 让 AndroidAutoSize 以什么单位为副单位, 在 pt、in、mm 这三个冷门单位中选择一个即可, 三个效果都是一样的 + * 按自己的喜好选择, 比如我就喜欢 mm, 翻译为中文是妹妹的意思 + * 默认为 {@link Subunits#NONE}, 即不支持副单位, 详情请看类文件的注释 {@link UnitsManager} + * + * @param supportSubunits {@link Subunits} + */ + public UnitsManager setSupportSubunits(Subunits supportSubunits) { + mSupportSubunits = Preconditions.checkNotNull(supportSubunits, + "The supportSubunits can not be null, use Subunits.NONE instead"); + return this; + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/utils/AutoSizeLog.java b/autosize/src/main/java/me/jessyan/autosize/utils/AutoSizeLog.java new file mode 100644 index 0000000..5869c27 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/utils/AutoSizeLog.java @@ -0,0 +1,60 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize.utils; + +import android.util.Log; + +/** + * ================================================ + * Created by JessYan on 2018/8/8 18:48 + * Contact me + * Follow me + * ================================================ + */ +public class AutoSizeLog { + private static final String TAG = "AndroidAutoSize"; + private static boolean debug; + + private AutoSizeLog() { + throw new IllegalStateException("you can't instantiate me!"); + } + + public static boolean isDebug() { + return debug; + } + + public static void setDebug(boolean debug) { + AutoSizeLog.debug = debug; + } + + public static void d(String message) { + if (debug) { + Log.d(TAG, message); + } + } + + public static void w(String message) { + if (debug) { + Log.w(TAG, message); + } + } + + public static void e(String message) { + if (debug) { + Log.e(TAG, message); + } + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/utils/AutoSizeUtils.java b/autosize/src/main/java/me/jessyan/autosize/utils/AutoSizeUtils.java new file mode 100644 index 0000000..b3bfc68 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/utils/AutoSizeUtils.java @@ -0,0 +1,81 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize.utils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.TypedValue; +import android.app.Application; + +import java.lang.reflect.InvocationTargetException; + +/** + * ================================================ + * AndroidAutoSize 常用工具类 + *

+ * Created by JessYan on 2018/8/25 15:24 + * Contact me + * Follow me + * ================================================ + */ +public class AutoSizeUtils { + + private AutoSizeUtils() { + throw new IllegalStateException("you can't instantiate me!"); + } + + public static int dp2px(Context context, float value) { + return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, context.getResources().getDisplayMetrics()) + 0.5f); + } + + public static int sp2px(Context context, float value) { + return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, value, context.getResources().getDisplayMetrics()) + 0.5f); + } + + public static int pt2px(Context context, float value) { + return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, value, context.getResources().getDisplayMetrics()) + 0.5f); + } + + public static int in2px(Context context, float value) { + return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_IN, value, context.getResources().getDisplayMetrics()) + 0.5f); + } + + public static int mm2px(Context context, float value) { + return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, value, context.getResources().getDisplayMetrics()) + 0.5f); + } + + public static Application getApplicationByReflect() { + try { + @SuppressLint("PrivateApi") + Class activityThread = Class.forName("android.app.ActivityThread"); + Object thread = activityThread.getMethod("currentActivityThread").invoke(null); + Object app = activityThread.getMethod("getApplication").invoke(thread); + if (app == null) { + throw new NullPointerException("you should init first"); + } + return (Application) app; + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + throw new NullPointerException("you should init first"); + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/utils/Preconditions.java b/autosize/src/main/java/me/jessyan/autosize/utils/Preconditions.java new file mode 100644 index 0000000..10067f2 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/utils/Preconditions.java @@ -0,0 +1,191 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize.utils; + +import android.os.Looper; + +/** + * ================================================ + * Created by JessYan on 26/09/2016 13:59 + * Contact me + * Follow me + * ================================================ + */ +public final class Preconditions { + + private Preconditions() { + throw new IllegalStateException("you can't instantiate me!"); + } + + public static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + + public static void checkArgument(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + } + + public static void checkArgument(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) { + if (!expression) { + throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs)); + } + } + + public static void checkState(boolean expression) { + if (!expression) { + throw new IllegalStateException(); + } + } + + public static void checkState(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalStateException(String.valueOf(errorMessage)); + } + } + + public static void checkState(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) { + if (!expression) { + throw new IllegalStateException(format(errorMessageTemplate, errorMessageArgs)); + } + } + + public static T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } else { + return reference; + } + } + + public static T checkNotNull(T reference, Object errorMessage) { + if (reference == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } else { + return reference; + } + } + + public static T checkNotNull(T reference, String errorMessageTemplate, Object... errorMessageArgs) { + if (reference == null) { + throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs)); + } else { + return reference; + } + } + + public static int checkElementIndex(int index, int size) { + return checkElementIndex(index, size, "index"); + } + + public static int checkElementIndex(int index, int size, String desc) { + if (index >= 0 && index < size) { + return index; + } else { + throw new IndexOutOfBoundsException(badElementIndex(index, size, desc)); + } + } + + /** + * Throws {@link IllegalStateException} if the calling thread is not the application's main + * thread. + * + * @throws IllegalStateException If the calling thread is not the application's main thread. + */ + public static void checkMainThread() { + if (Looper.myLooper() != Looper.getMainLooper()) { + throw new IllegalStateException("Not in applications main thread"); + } + } + + private static String badElementIndex(int index, int size, String desc) { + if (index < 0) { + return format("%s (%s) must not be negative", new Object[]{desc, Integer.valueOf(index)}); + } else if (size < 0) { + throw new IllegalArgumentException((new StringBuilder(26)).append("negative size: ").append(size).toString()); + } else { + return format("%s (%s) must be less than size (%s)", new Object[]{desc, Integer.valueOf(index), Integer.valueOf(size)}); + } + } + + public static int checkPositionIndex(int index, int size) { + return checkPositionIndex(index, size, "index"); + } + + public static int checkPositionIndex(int index, int size, String desc) { + if (index >= 0 && index <= size) { + return index; + } else { + throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc)); + } + } + + private static String badPositionIndex(int index, int size, String desc) { + if (index < 0) { + return format("%s (%s) must not be negative", new Object[]{desc, Integer.valueOf(index)}); + } else if (size < 0) { + throw new IllegalArgumentException((new StringBuilder(26)).append("negative size: ").append(size).toString()); + } else { + return format("%s (%s) must not be greater than size (%s)", new Object[]{desc, Integer.valueOf(index), Integer.valueOf(size)}); + } + } + + public static void checkPositionIndexes(int start, int end, int size) { + if (start < 0 || end < start || end > size) { + throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size)); + } + } + + private static String badPositionIndexes(int start, int end, int size) { + return start >= 0 && start <= size ? (end >= 0 && end <= size ? format("end index (%s) must not be less than start index (%s)", new Object[]{Integer.valueOf(end), Integer.valueOf(start)}) : badPositionIndex(end, size, "end index")) : badPositionIndex(start, size, "start index"); + } + + static String format(String template, Object... args) { + template = String.valueOf(template); + StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); + int templateStart = 0; + + int i; + int placeholderStart; + for (i = 0; i < args.length; templateStart = placeholderStart + 2) { + placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + + builder.append(template.substring(templateStart, placeholderStart)); + builder.append(args[i++]); + } + + builder.append(template.substring(templateStart)); + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + + builder.append(']'); + } + + return builder.toString(); + } +} diff --git a/autosize/src/main/java/me/jessyan/autosize/utils/ScreenUtils.java b/autosize/src/main/java/me/jessyan/autosize/utils/ScreenUtils.java new file mode 100644 index 0000000..a380696 --- /dev/null +++ b/autosize/src/main/java/me/jessyan/autosize/utils/ScreenUtils.java @@ -0,0 +1,122 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.jessyan.autosize.utils; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.os.Build; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.WindowManager; + +/** + * ================================================ + * Created by JessYan on 26/09/2016 16:59 + * Contact me + * Follow me + * ================================================ + */ +public class ScreenUtils { + + private ScreenUtils() { + throw new IllegalStateException("you can't instantiate me!"); + } + + public static int getStatusBarHeight() { + int result = 0; + try { + int resourceId = Resources.getSystem().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = Resources.getSystem().getDimensionPixelSize(resourceId); + } + } catch (Resources.NotFoundException e) { + e.printStackTrace(); + } + return result; + } + + /** + * 获取当前的屏幕尺寸 + * + * @param context {@link Context} + * @return 屏幕尺寸 + */ + public static int[] getScreenSize(Context context) { + int[] size = new int[2]; + + WindowManager w = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display d = w.getDefaultDisplay(); + DisplayMetrics metrics = new DisplayMetrics(); + d.getMetrics(metrics); + + size[0] = metrics.widthPixels; + size[1] = metrics.heightPixels; + return size; + } + + /** + * 获取原始的屏幕尺寸 + * + * @param context {@link Context} + * @return 屏幕尺寸 + */ + public static int[] getRawScreenSize(Context context) { + int[] size = new int[2]; + + WindowManager w = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display d = w.getDefaultDisplay(); + DisplayMetrics metrics = new DisplayMetrics(); + d.getMetrics(metrics); + // since SDK_INT = 1; + int widthPixels = metrics.widthPixels; + int heightPixels = metrics.heightPixels; + + // includes window decorations (statusbar bar/menu bar) + if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT < 17) + try { + widthPixels = (Integer) Display.class.getMethod("getRawWidth").invoke(d); + heightPixels = (Integer) Display.class.getMethod("getRawHeight").invoke(d); + } catch (Exception ignored) { + } + // includes window decorations (statusbar bar/menu bar) + if (Build.VERSION.SDK_INT >= 17) + try { + Point realSize = new Point(); + Display.class.getMethod("getRealSize", Point.class).invoke(d, realSize); + widthPixels = realSize.x; + heightPixels = realSize.y; + } catch (Exception ignored) { + } + size[0] = widthPixels; + size[1] = heightPixels; + return size; + } + + public static int getHeightOfNavigationBar(Context context) { + //如果小米手机开启了全面屏手势隐藏了导航栏则返回 0 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (Settings.Global.getInt(context.getContentResolver(), "force_fsg_nav_bar", 0) != 0) { + return 0; + } + } + + int realHeight = getRawScreenSize(context)[1]; + int displayHeight = getScreenSize(context)[1]; + return realHeight - displayHeight; + } +}