今日頭條屏幕適配方案
原理
1.無論我們?cè)趚ml中使用何種尺寸單位(dp畴栖、sp、pt......),最后在繪制時(shí)都會(huì)給我們轉(zhuǎn)成px
2.我們選定一種尺寸單位(dp八千、sp吗讶、pt ......)作為我們的適配單位,然后篡改這個(gè)單位與px之間的轉(zhuǎn)化比例恋捆,然后在xml中使用選定的單位做適配
原理解析
px值 = dp值 * metrics.density
解釋:當(dāng)前設(shè)備屏幕總像素/ 設(shè)計(jì)圖總寬度(單位為 dp) = density
density 的意思就是 1dp 占當(dāng)前設(shè)備多少像素
常規(guī)的density 在每個(gè)設(shè)備上都是固定的照皆,DPI / 160 = density,屏幕的總 px 寬度 / density = 屏幕的總 dp 寬度
舉個(gè)例子:
設(shè)備 1沸停,屏幕寬度為 1080px膜毁,480DPI,屏幕總 dp 寬度為 1080 / (480 / 160) = 360dp
設(shè)備 2星立,屏幕寬度為 1440爽茴,560DPI,屏幕總 dp 寬度為 1440 / (560 / 160) = 411dp
屏幕的總 dp 寬度在不同的設(shè)備上是會(huì)變化的绰垂,但是我們?cè)诓季种刑顚懙?dp 值卻是固定不變的
這會(huì)導(dǎo)致什么呢室奏?假設(shè)我們布局中有一個(gè) View 的寬度為 100dp,在設(shè)備 1 中 該 View 的寬度占整個(gè)屏幕寬度的 27.8% (100 / 360 = 0.278)
但在設(shè)備 2 中該 View 的寬度就只能占整個(gè)屏幕寬度的 24.3% (100 / 411 = 0.243)劲装,可以看到這個(gè) View 在像素越高的屏幕上胧沫,dp 值雖然沒變,但是與屏幕的實(shí)際比例卻發(fā)生了較大的變化占业,所以肉眼的觀看效果绒怨,會(huì)越來越小,這就導(dǎo)致了傳統(tǒng)的填寫 dp 的屏幕適配方式產(chǎn)生了較大的誤差
這時(shí)我們要想完美適配谦疾,那就必須保證這個(gè) View 在任何分辨率的屏幕上南蹂,與屏幕的比例都是相同的
1.改變每個(gè) View 的 dp 值?不現(xiàn)實(shí)念恍,在每個(gè)設(shè)備上都要通過代碼動(dòng)態(tài)計(jì)算 View 的 dp 值六剥,工作量太大——————pass
2.每個(gè) View 的 dp 值是固定不變的晚顷,那我們只要保證每個(gè)設(shè)備的屏幕總 dp 寬度不變,就能保證每個(gè) View 在所有分辨率的屏幕上與屏幕的比例都保持不變疗疟,從而完成等比例適配该默,并且這個(gè)屏幕總 dp 寬度如果還能保證和設(shè)計(jì)圖的寬度一致的話,那我們?cè)诓季謺r(shí)就可以直接按照設(shè)計(jì)圖上的尺寸填寫 dp 值
屏幕的總 px 寬度 / density = 屏幕的總 dp 寬度
這個(gè)公式就是把上面公式中的 屏幕的總 dp 寬度 換成 設(shè)計(jì)圖總寬度策彤,原理都是一樣的栓袖,只要 density 根據(jù)不同的設(shè)備進(jìn)行實(shí)時(shí)計(jì)算并作出改變
density是指的手機(jī)的屏幕密度,由系統(tǒng)提供店诗,不同的手機(jī)的density可能不同裹刮;所以我們不能直接使用系統(tǒng)的density,需要篡改density來達(dá)到適配的目的
方案實(shí)現(xiàn)
一般只對(duì)寬度適配必搞,畢竟高度可以滑動(dòng)解決必指!所以方案中給出的是用dp適配寬度
建議
選用dp作為適配單位,給出的理由是項(xiàng)目中大部分都是使用dp做單位
即使適配方案萬一失效了呢恕洲,咋辦塔橡?選用dp至少很大程度可以防止出現(xiàn)頁面顯示不全,顯示效果太差的問題霜第! 侵入性低
優(yōu)點(diǎn)
1.在頁面布局時(shí)不需要額外的代碼和操作
2.侵入性非常低葛家,該方案和項(xiàng)目完全解耦,在項(xiàng)目布局時(shí)不會(huì)依賴哪怕一行該方案的代碼泌类,而且使用的還是 Android 官方的 API癞谒,意味著當(dāng)你遇到什么問題無法解決,想切換為其他屏幕適配方案時(shí)刃榨,基本不需要更改之前的代碼
3.可適配三方庫的控件和系統(tǒng)的控件(不止是 Activity 和 Fragment弹砚,Dialog、Toast 等所有系統(tǒng)控件都可以適配)枢希,由于修改的 density 在整個(gè)項(xiàng)目中是全局
4.不會(huì)有任何性能的損耗
缺點(diǎn)
當(dāng)某個(gè)系統(tǒng)控件或三方庫控件的設(shè)計(jì)圖尺寸和和我們項(xiàng)目自身的設(shè)計(jì)圖尺寸差距非常大時(shí)桌吃,這個(gè)問題就越嚴(yán)重
具體的就是比如第三方庫的設(shè)計(jì)圖是給的640dp的,而自身項(xiàng)目給的是320dp苞轿,當(dāng)一個(gè)View 64dp寬的時(shí)候茅诱,第三方中就是10% ,而在自身項(xiàng)目中就是20% 搬卒,相當(dāng)于差距比較大的時(shí)候瑟俭,就存在問題
處理方式就是針對(duì)不同的activity 去做特殊適配就可以
代碼
首先要在Application初始化,針對(duì)封裝的可以在BaseActivity中初始化契邀,放在onCreate()之前摆寄,也可以單個(gè)activity不適配,看具體注釋
/**
* 通過修改系統(tǒng)參數(shù)來適配android設(shè)備
*/
public class DensityUtils {
private static float appDensity;
private static float appScaledDensity;
private static DisplayMetrics appDisplayMetrics;
private static int barHeight;
public final static String WIDTH = "width";
public final static String HEIGHT = "height";
/**
* 在Application里初始化一下
* @param application
*/
public static void setDensity(@NonNull final Application application) {
//獲取application的DisplayMetrics
appDisplayMetrics = application.getResources().getDisplayMetrics();
//獲取狀態(tài)欄高度
barHeight = getStatusBarHeight(application);
if (appDensity == 0) {
//初始化的時(shí)候賦值
appDensity = appDisplayMetrics.density;
appScaledDensity = appDisplayMetrics.scaledDensity;
//添加字體變化的監(jiān)聽
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
//字體改變后,將appScaledDensity重新賦值
if (newConfig != null && newConfig.fontScale > 0) {
appScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
}
/**
* 此方法在BaseActivity中做初始化(如果不封裝BaseActivity的話,直接用下面那個(gè)方法就好)
* 在setContentView()之前設(shè)置
* @param activity
*/
public static void setDefault(Activity activity) {
setAppOrientation(activity, WIDTH);
}
/**
* 此方法用于在某一個(gè)Activity里面更改適配的方向
* 在setContentView()之前設(shè)置
* @param activity
* @param orientation
*/
public static void setOrientation(Activity activity, String orientation) {
setAppOrientation(activity, orientation);
}
/**
* targetDensity
* targetScaledDensity
* targetDensityDpi
* 這三個(gè)參數(shù)是統(tǒng)一修改過后的值
* orientation:方向值,傳入width或height
*/
private static void setAppOrientation(@Nullable Activity activity, String orientation) {
float targetDensity;
if (orientation.equals("height")) {
targetDensity = (appDisplayMetrics.heightPixels - barHeight) / 667f;//設(shè)計(jì)圖的高度 單位:dp
} else {
targetDensity = appDisplayMetrics.widthPixels / 360f;//設(shè)計(jì)圖的寬度 單位:dp
}
float targetScaledDensity = targetDensity * (appScaledDensity / appDensity);
int targetDensityDpi = (int) (160 * targetDensity);
/**
*
* 最后在這里將修改過后的值賦給系統(tǒng)參數(shù)
* 只修改Activity的density值
*/
DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaledDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
}
/**
* 獲取狀態(tài)欄高度
*
* @param context
* @return
*/
public static int getStatusBarHeight(Context context) {
int result = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
}