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