看簡書上和CSDN都沒有一篇特別有針對性的Android屏幕適配解決方案澎灸,大部分都是基于官方的基本的唾糯,或者挪來挪去的爽撒,最近自己剛好處理一個老項目需要適配大小屏,這里就貼一個自己的屏幕適配解決方案腊徙,有問題大家一起溝通。
大家都知道Android屏幕適配是件非常頭疼的事檬某,目前全世界安卓設(shè)備的大大小小分辨率撬腾,大大小小的尺寸,最終形成的設(shè)備屏幕大小種類不計其數(shù)恢恼,但就這一項就給開發(fā)者造成了不少困難民傻,總是照顧住這種屏幕照顧不了那種屏幕。
先來放一種很熟悉的圖:
針對Android屏幕適配场斑,除了我們按照“靈活運用布局”漓踢、“尺寸限定符”、“布局相關(guān)屬性”漏隐、“.9圖”喧半、“屏幕密度適配”等官方標(biāo)準(zhǔn),但是卻發(fā)現(xiàn)還遠(yuǎn)遠(yuǎn)不夠青责,要么添加很多圖片(從加載性能說似乎是必要的)使得apk變得很大挺据,要么有多套布局工作量變大難以維護。針對屏幕適配脖隶,google官方也是系統(tǒng)默認(rèn)支持的是采用屏幕像素密度來進(jìn)行匹配相關(guān)資源和長度的扁耐,這一塊不做過多的闡述了,建議參看官方文檔产阱。
這里我提供一個最牛婉称、最簡單、一步到位的方案心墅,github地址:https://github.com/toperc/ScreenAdaptation
大概其優(yōu)點如下:
- 只需要提供一套最優(yōu)圖片酿矢,用于減小apk包大小榨乎。當(dāng)然為了加載性能和圖片更精細(xì)化顯示采用多套也可以怎燥。
- 無論大小屏如果布局確定只需要一套布局,當(dāng)然如果橫豎屏變換不同的屏幕展示蜜暑,多套也是必要的铐姚。
- 沒有重疊的現(xiàn)象發(fā)生,按照默認(rèn)布局方法可能在大屏上適配很好肛捍,到小屏上卻出現(xiàn)重疊的現(xiàn)象隐绵,很常見,但這里我不允許他有拙毫!
- 無論大小屏整體看過去布局幾乎相同依许,因為是按比例來的。
- 在大屏上展示不會隨著完全比例放大而顯得傻缀蹄、大峭跳、憨膘婶。
其原理也很簡單,就是根據(jù)基準(zhǔn)屏幕像素密度來進(jìn)行適當(dāng)縮放后得到相對屏幕像素蛀醉,然后給系統(tǒng)的像素密度重新賦值悬襟。
準(zhǔn)備工作:
確定一套基準(zhǔn)屏幕參數(shù),然后布局時根據(jù)這套參數(shù)布局拯刁,單位仍然采用dp脊岳,并盡可能確定其布局控件的長度。
//繪制頁面時參照的設(shè)計圖尺寸
final float DESIGN_WIDTH = 1080f;
final float DESIGN_HEIGHT = 1920f;
final float DESTGN_INCH = 5.0f;
三步走:
- 確定放大縮小比例
- 確定參考屏幕密度
- 確定相對屏幕密度并重新賦值給系統(tǒng)的像素密度
主要方法:
/**
* 重置屏幕密度
*/
public static void resetDensity(Context context) {
//繪制頁面時參照的設(shè)計圖尺寸
final float DESIGN_WIDTH = 800f;
final float DESIGN_HEIGHT = 1280f;
final float DESTGN_INCH = 5.0f;
//大屏調(diào)節(jié)因子垛玻,范圍0~1割捅,因屏幕同比例放大視圖顯示非常傻大憨,用于調(diào)節(jié)感官度
final float BIG_SCREEN_FACTOR = 0.8f;
DisplayMetrics dm = context.getResources().getDisplayMetrics();
//確定放大縮小比率
float rate = Math.min(dm.widthPixels, dm.heightPixels) / Math.min(DESIGN_WIDTH, DESIGN_HEIGHT);
//確定參照屏幕密度比率
float referenceDensity = (float) Math.sqrt(DESIGN_WIDTH * DESIGN_WIDTH + DESIGN_HEIGHT * DESIGN_HEIGHT) / DESTGN_INCH / DisplayMetrics.DENSITY_DEFAULT;
//確定最終屏幕密度比率
float relativeDensity = referenceDensity * rate;
if (ORIGINAL_DENSITY == -1) {
ORIGINAL_DENSITY = dm.density;
}
if (relativeDensity > ORIGINAL_DENSITY) {
relativeDensity = ORIGINAL_DENSITY + (relativeDensity - ORIGINAL_DENSITY) * BIG_SCREEN_FACTOR;
}
dm.density = relativeDensity;
dm.densityDpi = (int) (relativeDensity * DisplayMetrics.DENSITY_DEFAULT);
dm.scaledDensity = relativeDensity;
}
上邊放大縮小比例是根據(jù)屏幕的長寬和參考屏幕的長寬對應(yīng)設(shè)定的帚桩,這樣計算出來的屏幕密度是固定的棺牧。
注意最終賦值的三個成員變量的含義:
- dm.density
屏幕密度比率,不同設(shè)備的屏幕視圖的長寬大小都是根據(jù)屏幕密度比率與屏幕密度基準(zhǔn)值(160dp)的乘積朗儒。此成員變量控制擁有固定長寬值視圖的縮放颊乘。 - dm.densityDpi
實際屏幕密度比率,但是單獨設(shè)定此值并不一定起到縮放效果醉锄,需要配合density一起設(shè)定乏悄,此成員變量控制長寬自動wrapConent形式的縮放,因為wrapContent形式下系統(tǒng)自動為其分配默認(rèn)的屏幕密度恳不,如果不對其重新賦值檩小,則不能根據(jù)規(guī)則進(jìn)行縮放。 - dm.scaledDensity
獨立像素密度烟勋,主要處理字體大小縮放规求,目前比較流行的字體單位為dp,第一能隨著應(yīng)用適應(yīng)不同的設(shè)備縮放卵惦,第二避免了跟隨系統(tǒng)縮放使得布局展示錯亂阻肿。但是某些系統(tǒng)機開發(fā)者不能控制的例如toast字體大小等默認(rèn)使用的單位仍然是sp,所以此成員變量也需要進(jìn)行重新賦值。系統(tǒng)默認(rèn)應(yīng)用sp和dp二者的比率為1沮尿,但是某些情況下又想根據(jù)系統(tǒng)縮放丛塌,還要保持整體縮放比率,這是就要系統(tǒng)縮放和應(yīng)用縮放結(jié)合著得出一個比率來最終賦值了畜疾。
配置地方:
DisplayMetrics相關(guān)參數(shù)的值有兩種赴邻,一種是默認(rèn)的,一種是動態(tài)調(diào)整后的啡捶。
獲取默認(rèn)的:
DisplayMetrics display = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(display);
獲取動態(tài)調(diào)整后的:
DisplayMetrics dm = context.getResources().getDisplayMetrics();
應(yīng)用展示最終使用的DisplayMetrics相關(guān)參數(shù)就是調(diào)整后的姥敛。android8.0之前,整個應(yīng)用長寬縮放比率均是采用一套瞎暑,無論時Activity和Application兩者任意其一配置均可彤敛,但是在Android8.0的時候忿偷,系統(tǒng)架構(gòu)調(diào)整,由原來的統(tǒng)一現(xiàn)在重點分配到每個Activity中和Application中臊泌,為了兼顧全局鲤桥,兩者都要設(shè)置,尤其是Activity渠概。Activity中設(shè)置的時候要注意一定要設(shè)置在setContentView()之前茶凳,Application設(shè)置在onCreat()即可。
還應(yīng)注意一點播揪,當(dāng)屏幕旋轉(zhuǎn)時有時我們?yōu)榱吮4嫦嚓P(guān)狀態(tài)贮喧,所以不想讓Activity的onCreat()方法重走一遍,但是屏幕旋轉(zhuǎn)會使得屏幕密度參數(shù)進(jìn)行重置猪狈,不得已我們還要在屏幕旋轉(zhuǎn)的時候在重新設(shè)置一下屏幕密度箱沦。
源碼中屏幕旋轉(zhuǎn)所作的工作:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
if (mResources != null) {
// The real (and thus managed) resources object was already updated
// by ResourcesManager, so pull the current metrics from there.
final DisplayMetrics newMetrics = super.getResources().getDisplayMetrics();
mResources.updateConfiguration(newConfig, newMetrics);
}
}
為了使這個應(yīng)用產(chǎn)生效果,建議將Activity形式的配置在BaseActivity中雇庙。
Activity中:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//一定要寫在setContentView之前
ScreenUtil.resetDensity(this);
setContentView(R.layout.activity_main);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//屏幕旋轉(zhuǎn)時會使一些參數(shù)初始化谓形,所以也需要在此重置一下
ScreenUtil.resetDensity(this);
}
Application中:
@Override
public void onCreate() {
super.onCreate();
ScreenUtil.resetDensity(this);
}
這些配置完畢后,一旦app啟動優(yōu)先處理這件事疆前,其他均按照默認(rèn)處理寒跳,就是這么簡單。
使用和不使用截圖直觀感受
采用默認(rèn)布局方式截圖對比:
采用此方案截圖對比:
轉(zhuǎn)載請注明出處竹椒,我是toperc.