今日頭條適配方案?
一、屏幕適配原理
1、Android中的dp、px、dpi、desity關(guān)系
px = density * dp;
density = dpi / 160;
px = dp * (dpi / 160);
其中dpi是根據(jù)屏幕的真實(shí)分辨率和尺寸計(jì)算的,每個(gè)設(shè)備可能都不一樣
2、為什么要算出 density,這和屏幕適配有什么關(guān)系呢?
public static float applyDimension(int unit, float value,
?????????????????????????????????????? DisplayMetrics metrics)
??? {
??????? switch (unit) {
??????? case COMPLEX_UNIT_PX:
??????????? return value;
??????? case COMPLEX_UNIT_DIP:
??????????? return value * metrics.density;
??????? case COMPLEX_UNIT_SP:
??????????? return value * metrics.scaledDensity;
??????? case COMPLEX_UNIT_PT:
??????????? return value * metrics.xdpi * (1.0f/72);
??????? case COMPLEX_UNIT_IN:
??????????? return value * metrics.xdpi;
??????? case COMPLEX_UNIT_MM:
??????????? return value * metrics.xdpi * (1.0f/25.4f);
??????? }
??????? return 0;
??? }
不管你在布局文件中填寫(xiě)的是什么單位,最后都會(huì)被轉(zhuǎn)化為px,系統(tǒng)就是通過(guò)上面的方法,將你在項(xiàng)目中任何地方填寫(xiě)的單位都轉(zhuǎn)換為px 的,所以我們常用的px轉(zhuǎn)dp的公式dp = px / density,就是根據(jù)上面的方法得來(lái)的
3、修改density的大小,保證在所有的設(shè)備上計(jì)算出來(lái)的px 的值正好是屏幕寬度(解決方案)
(1)、Denstiy是DisplayMetrics?中的成員變量,DisplayMetrics 實(shí)例通過(guò)?Resources#getDisplayMetrics()?可以獲得,而Resouces通過(guò)Activity或者Application的Context獲得
DisplayMetrics#density?就是上述的density
DisplayMetrics#densityDpi?就是上述的dpi
DisplayMetrics#scaledDensity?字體的縮放因子
正常情況下和density相等,但是調(diào)節(jié)系統(tǒng)字體大小后會(huì)改變這個(gè)值
(2)假設(shè)按照設(shè)計(jì)圖是320dp,依據(jù)寬度來(lái)適配
注:今日頭條的適配方式,今日頭條適配方案默認(rèn)項(xiàng)目中只能以高或?qū)捴械囊粋€(gè)作為基準(zhǔn),進(jìn)行適配
那么適配后的 density = 設(shè)備真實(shí)寬(單位px) / 320,接下來(lái)只需要把我們計(jì)算好的 density 在系統(tǒng)中修改下即可,代碼實(shí)現(xiàn)如下
// 獲取原始密度大小
private static float sRoncompatDennsity;
// 縮放比例因子
private static float sRoncompatScaledDensity;
private void setCustomDensity(@NonNull Activity activity,final @NonNull Application application) {
??? //application
final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
??? if (sRoncompatDennsity == 0) {
??????? sRoncompatDennsity = appDisplayMetrics.density;
??????? sRoncompatScaledDensity = appDisplayMetrics.scaledDensity;
??????? //
當(dāng)應(yīng)用程序運(yùn)行中,系統(tǒng)配置發(fā)生改變時(shí),系統(tǒng)回調(diào)用此方法
??????? application.registerComponentCallbacks(new ComponentCallbacks() {
??????????? @Override
??????????? public void onConfigurationChanged(Configuration newConfig) {
??????????????? if (newConfig !=null && newConfig.fontScale > 0) {
???????????????? // 當(dāng)系統(tǒng)配置發(fā)生改變,縮放因子也隨之改變
??????????????????? sRoncompatScaledDensity = application.getResources()
??????????????????????????? .getDisplayMetrics().scaledDensity;
??????????????? }
??????????? }
??????? });
??? }
??? //
計(jì)算寬為320dp
??? final float targetDensity = appDisplayMetrics.widthPixels / 320;
??? final float targetScaledDensity = targetDensity * (sRoncompatScaledDensity /sRoncompatDennsity);
??? final int targetDensityDpi = (int) (targetDensity * 160);
??? appDisplayMetrics.density = targetDensity;
??? appDisplayMetrics.densityDpi = targetDensityDpi;
??? appDisplayMetrics.scaledDensity = targetScaledDensity;
??? //activity
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
??? activityDisplayMetrics.density = targetDensity;
??? activityDisplayMetrics.densityDpi = targetDensityDpi;
??? activityDisplayMetrics.scaledDensity = targetScaledDensity;
}
AndroidAutoSize 使用注意事項(xiàng)和原理
項(xiàng)目地址:https://github.com/JessYanCoding/AndroidAutoSize
1、AndroidAutosize使用注意事項(xiàng)
????????????? 我們還是依照寬度為320dp為基準(zhǔn)說(shuō)明
[if !supportLists]a、? [endif]當(dāng)我們的設(shè)計(jì)圖寬度為填充整個(gè)屏幕的寬度時(shí),我們的寬度寫(xiě)成layout_width="320dp"和layout_width="match_parent"都可以
[if !supportLists]b、? [endif]對(duì)于固定尺寸的圖片或者其他控件,如果mark圖上面明確標(biāo)注了寬、高,我就按照設(shè)計(jì)圖寬和高的1\2設(shè)置對(duì)應(yīng)的寬、高(單位為:dp)
2、AndroidAutosize原理
?????? AndroidAntuosize 第三方庫(kù)的實(shí)現(xiàn)基本原理和今日頭條適配原理一樣,其實(shí)就是對(duì)今日頭條適配方案的封裝
1、通過(guò)聲明{@link
InitProvider} 自動(dòng)啟動(dòng)初始化
Provider是由ActivityThread負(fù)責(zé)啟動(dòng)的,ActivityThread對(duì)應(yīng)應(yīng)用進(jìn)程的主線(xiàn)程,即在應(yīng)用進(jìn)程啟動(dòng)時(shí),會(huì)將ContentProvider啟動(dòng)起來(lái)。
@Override
public boolean onCreate() {
??? AutoSizeConfig.getInstance()
??????????? .setLog(true)
??????????? .init((Application) getContext().getApplicationContext())
??????????? .setUseDeviceSize(false);
??? return true;
}
2、配置AutosizeConfig中完成具體的初始化工作
?? 注意:初始化方法只能調(diào)用一次, 否則報(bào)錯(cuò),對(duì)activity和fragment的生命周期注冊(cè)監(jiān)聽(tīng),初始化默認(rèn)的設(shè)配策略(在manifest 中配置)如果對(duì)某個(gè)activiyh 進(jìn)行了自定義策略,則使用自定義策略
/**
?*框架會(huì)在APP 啟動(dòng)時(shí)自動(dòng)調(diào)用此方法進(jìn)行初始化, 使用者無(wú)需手動(dòng)初始化, 初始化方法只能調(diào)用一次, 否則報(bào)錯(cuò)
?*
?* @param application?? {@link Application}
?* @param isBaseOnWidth詳情請(qǐng)查看{@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();
??? //從manifest中獲取配置的寬或者高
??? getMetaData(application);
//獲取屏幕的真實(shí)寬度和高度
??? int[] screenSize = ScreenUtils.getScreenSize(application);
??? mScreenWidth = screenSize[0];
?? ?mScreenHeight = screenSize[1];
??? mInitDensity = displayMetrics.density;
??? mInitDensityDpi = displayMetrics.densityDpi;
??? mInitScaledDensity = displayMetrics.scaledDensity;
??? application.registerComponentCallbacks(new ComponentCallbacks() {
??????? @Override
??????? public void onConfigurationChanged(Configuration newConfig) {
??????????? if (newConfig != null) {
??????????????? if (newConfig.fontScale > 0) {
??????????????????? mInitScaledDensity =
??????????????????????????? Resources.getSystem().getDisplayMetrics().scaledDensity;
??????????????? int[] screenSize = ScreenUtils.getScreenSize(application);
??????????????? mScreenWidth = screenSize[0];
??????????????? mScreenHeight = screenSize[1];
??????????? }
??????? }
??????? @Override
??????? public void onLowMemory() {
???? ???}
??? });
??? mActivityLifecycleCallbacks = new ActivityLifecycleCallbacksImpl(strategy == null ? new DefaultAutoAdaptStrategy() : strategy);
??? //對(duì)Activity的生命周期事件進(jìn)行監(jiān)聽(tīng)
??? application.registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
? ??return this;
}
3、獲取使用者在Androidmanifest中填寫(xiě)的meta信息
?? ????????android:value="320"/>
?????????? android:value="568"/>
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();
}
4、默認(rèn)配置策略
這里是今日頭條適配方案的核心代碼, 核心在于根據(jù)當(dāng)前設(shè)備的實(shí)際情況做自動(dòng)計(jì)算并轉(zhuǎn)換DisplayMetrics#density、DisplayMetrics#scaledDensity、DisplayMetrics#densityDpi這三個(gè)值, 有興趣請(qǐng)看下面的鏈接設(shè)計(jì)圖上的設(shè)計(jì)尺寸, sizeInDp設(shè)計(jì)圖上的設(shè)計(jì)尺寸,單位dp, 如果isBaseOnWidth設(shè)置為true,則應(yīng)該填寫(xiě)設(shè)計(jì)圖的總寬度, 如果isBaseOnWidth 設(shè)置為false, sizeInDp 則應(yīng)該填寫(xiě)設(shè)計(jì)圖的總高度,isBaseOnWidth 是否按照寬度進(jìn)行等比例適配, true為以寬度進(jìn)行等比例適配, false 為以高度進(jìn)行等比例適配
public static void autoConvertDensity(Activity activity, float sizeInDp, boolean isBaseOnWidth) {
??? Preconditions.checkNotNull(activity, "activity == null");
??? int screenSize = isBaseOnWidth ? AutoSizeConfig.getInstance().getScreenWidth()
??????????? : AutoSizeConfig.getInstance().getScreenHeight();
??? String key = sizeInDp + "|" + isBaseOnWidth + "|"
??????????? + AutoSizeConfig.getInstance().isUseDeviceSize() + "|"
??????????? + AutoSizeConfig.getInstance().getInitScaledDensity() + "|"
??????????? + screenSize;
??? DisplayMetricsInfo displayMetricsInfo = mCache.get(key);
??? float targetDensity = 0;
??? int targetDensityDpi = 0;
??? float targetScaledDensity = 0;
??? if (displayMetricsInfo == null) {
??????? if (isBaseOnWidth) {
??????????? targetDensity = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / sizeInDp;
??????? } else {
????? ??????targetDensity = AutoSizeConfig.getInstance().getScreenHeight() * 1.0f / sizeInDp;
??????? }
??????? targetScaledDensity = targetDensity * (AutoSizeConfig.getInstance().
??????????????? getInitScaledDensity() * 1.0f / AutoSizeConfig.getInstance().getInitDensity());
??????? targetDensityDpi = (int) (targetDensity * 160);
??????? mCache.put(key, new DisplayMetricsInfo(targetDensity, targetDensityDpi, targetScaledDensity));
??? } else {
??????? targetDensity = displayMetricsInfo.density;
??????? targetDensityDpi = displayMetricsInfo.densityDpi;
??????? targetScaledDensity = displayMetricsInfo.scaledDensity;
??? }
?? //對(duì)activity和 application的DisplayMetrics設(shè)置參數(shù)
???? setDensity(activity, targetDensity, targetDensityDpi, targetScaledDensity);
}