基于UI设计稿的宽进行屏幕适配工具类,在Activity的onCreate中几句代码搞定,对原项目基本无侵入。
步骤一:在全局Application里初始化。
public class XXXApp extends Application {
@Override
public void onCreate() {
super.onCreate();
//屏幕适配方案初始化
ScreenAdaptive.initDensity(this, new ScreenAdaptive.ILogger() {
@Override
public void printLog(Object msg) {
//使用自己的日志打印工具类
}
});
}
}
步骤二:在需要适配的Activity里实现ScreenAdaptive.IActivity接口。
public abstract class ScreenAdaptActivity extends BaseActivity implements ScreenAdaptive.IActivity {
/**
* 返回false将不进行屏幕适配
*/
@Override
public boolean enableAdapt() {
return true;
}
/**
* 指定设计稿宽度,宽度单位为dp,即依赖于设备的像素。
* 一般情况下,iPhone6设计稿750px为375dp、Android设计稿1080px为540dp
*/
@Override
public int designWidthInDp() {
return 375;
}
}
public class XXXActivity extends ScreenAdaptActivity {
}
完整的工具类源码如下:
package com.gitee.li_yu_jiang.toolkit;
import android.app.Activity;
import android.app.Application;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Point;
import android.os.Build;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.WindowManager;
/**
* 屏幕适配方案辅助类(原理类似于今日头条的屏幕适配方案 https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA),
* 类似的比较胖的开源库有 https://github.com/JessYanCoding/AndroidAutoSize
* 参阅 https://blog.csdn.net/waplyj/article/details/86081689
* <p>
* Created by liyujiang on 2019/10/12
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public final class ScreenAdaptive {
private static DisplayMetrics originalAppMetrics;
private static ILogger logger;
private ScreenAdaptive() {
//DON'T CREATE INSTANCE
}
/**
* 本类核心代码改自 https://blog.csdn.net/u013000152/article/details/80855315
* 设计图px转dp可参阅 https://blog.csdn.net/zengd0/article/details/52464627
* <pre>
* PPI=(sqrt(屏幕高度像素数²+屏幕宽度像素数²)/屏幕英寸大小
* density=PPI/160
* dp=px/density
* ldpi(0-120),mdpi(120-160),hdpi(160-240),xhdpi(240-320),xxhdpi(320-480),xxxhdpi(480-640)
* 例如,设计图是以iPhone6(4.7寸屏,分辨率750*1334px)为基准的,计算步骤如下:
* 1334²=1779556,750²=562500,1779556+562500=2342056,sqrt(2342056)≈1530.38,1530.38/4.7≈325.61,
* 325.61/160≈2.03
* 所以iPhone6的PPI是326,1dp≈2px,也就是说设计图即375*667dp
* </pre>
* 直接在{@link Application#onCreate}中调用,并为{@link Activity}实现屏幕适配接口{@link IActivity}
*
* @param application 全局上下文
*/
public static void initDensity(final Application application, ILogger iLogger) {
if (iLogger != null) {
logger = iLogger;
}
if (originalAppMetrics == null) {
//初始化的保存原始的密度
originalAppMetrics = getDisplayMetrics(application.getResources());
}
logger.printLog("original density=" + originalAppMetrics.density + ", scaledDensity=" + originalAppMetrics.scaledDensity);
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
//字体改变后,将原始的密度重新赋值
if (newConfig != null && newConfig.fontScale > 0) {
originalAppMetrics = getDisplayMetrics(application.getResources());
logger.printLog("font changed, scaledDensity=" + originalAppMetrics.scaledDensity);
}
}
@Override
public void onLowMemory() {
}
});
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
if (activity instanceof IActivity) {
//实现了屏幕适配接口的Activity才进行适配
IActivity adaptiveActivity = (IActivity) activity;
boolean enableAdaptive = adaptiveActivity.enableAdapt();
logger.printLog("can adaptive screen for " + activity.getClass().getName() + ": " + enableAdaptive);
if (enableAdaptive) {
changeDensity(activity, adaptiveActivity.designWidthInDp());
}
} else {
logger.printLog("can not adaptive screen for " + activity.getClass().getName());
}
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
public static void initDensity(Application application) {
initDensity(application, new DefaultLogger());
}
/**
* 在需要适配的Activity的onCreate中调用
*
* @param activity 所在的的Activity
* @param designWidthDp 设计稿宽度,单位为dp
*/
public static void changeDensity(Activity activity, float designWidthDp) {
if (activity == null) {
return;
}
if (originalAppMetrics == null) {
logger.printLog(activity.getClass().getSimpleName() + " not init original density");
return;
}
int screenWidthPixel = obtainScreenSize(activity).x;
logger.printLog(activity.getClass().getSimpleName() + ", screenWidthPixel=" + screenWidthPixel + ", designWidthDp=" + designWidthDp);
float targetDensity = screenWidthPixel / designWidthDp;
float targetScaledDensity = targetDensity * (originalAppMetrics.scaledDensity / originalAppMetrics.density);
int targetDensityDpi = (int) (160 * targetDensity);
DisplayMetrics activityMetrics = getDisplayMetrics(activity.getResources());
//基于设计图计算出新的密度并值赋给系统参数
activityMetrics.density = targetDensity;
activityMetrics.scaledDensity = targetScaledDensity;
activityMetrics.densityDpi = targetDensityDpi;
logger.printLog(activity.getClass().getSimpleName() + " target density="
+ targetDensity + ", scaledDensity=" + targetScaledDensity);
}
/**
* 在不需要适配的Activity的onCreate中调用
*
* @param activity 所在的的Activity
*/
public static void resetDensity(Activity activity) {
if (activity == null) {
return;
}
DisplayMetrics activityMetrics = getDisplayMetrics(activity.getResources());
activityMetrics.setToDefaults();//NOTICE: widthPixels and heightPixels will set to 0
Point point = obtainScreenSize(activity);
activityMetrics.widthPixels = point.x;
activityMetrics.heightPixels = point.y;
logger.printLog(activity.getClass().getSimpleName() + ", reset density="
+ activityMetrics.density + ", scaledDensity=" + activityMetrics.scaledDensity);
}
/**
* Return the width/height of screen, in pixel.
*
* @return the width/height of screen, in pixel
*/
public static Point obtainScreenSize(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Point point = new Point();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
//noinspection ConstantConditions
wm.getDefaultDisplay().getRealSize(point);
} else {
//noinspection ConstantConditions
wm.getDefaultDisplay().getSize(point);
}
return point;
}
/**
* 解决 MIUI 更改框架导致的 MIUI7 + Android5.1.1 上出现的失效问题 (以及极少数基于这部分 MIUI 去掉 ART 然后置入 XPosed 的手机)
* 来源于: https://github.com/Firedamp/Rudeness/blob/master/rudeness-sdk/src/main/java/com/bulong/rudeness/RudenessScreenHelper.java#L61:5
*
* @param resources {@link Resources}
* @return {@link DisplayMetrics}
*/
private static DisplayMetrics getDisplayMetrics(Resources resources) {
DisplayMetrics metrics = null;
if ("MiuiResources".equals(resources.getClass().getSimpleName()) || "XResources".equals(resources.getClass().getSimpleName())) {
try {
//noinspection JavaReflectionMemberAccess
java.lang.reflect.Field field = Resources.class.getDeclaredField("mTmpMetrics");
field.setAccessible(true);
metrics = (DisplayMetrics) field.get(resources);
} catch (Throwable e) {
logger.printLog("not match MIUI or XPosed: " + e);
}
}
if (metrics == null) {
metrics = resources.getDisplayMetrics();
}
return metrics;
}
private static class DefaultLogger implements ILogger {
@Override
public void printLog(Object msg) {
if (msg instanceof Throwable) {
android.util.Log.e(ScreenAdaptive.class.getSimpleName(), "", (Throwable) msg);
} else {
android.util.Log.w(ScreenAdaptive.class.getSimpleName(), msg.toString());
}
}
}
/**
* {@link Activity}屏幕适配接口,基于UI设计稿的宽进行适配
*/
public interface IActivity {
/**
* 是否启用屏幕适配
*/
boolean enableAdapt();
/**
* UI设计稿宽度,建议范围在320-560内
*/
int designWidthInDp();
}
/**
* 日志打印接口
*/
public interface ILogger {
void printLog(Object msg);
}
}