前言:
Android技能樹系列:
Android基礎(chǔ)知識
Android技能樹 — Android存儲路徑及IO操作小結(jié)
Android技能樹 — 多進(jìn)程相關(guān)小結(jié)
數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)知識
Android技能樹 — 數(shù)組,鏈表,散列表基礎(chǔ)小結(jié)
Android技能樹 — 樹基礎(chǔ)知識小結(jié)(一)
算法基礎(chǔ)知識
Android技能樹 — 排序算法基礎(chǔ)小結(jié)
Rx系列相關(guān)
Android技能樹 — Rxjava取消訂閱小結(jié)(1):自帶方式
Android技能樹 — Rxjava取消訂閱小結(jié)(2):RxLifeCycle
關(guān)于屏幕適配,幾乎每隔一段時間就會看見有人發(fā)出來說XXX方案某筐,實現(xiàn)超級簡單的適配方式等等比搭。所以我把我目前了解過的常用的適配方案做個總結(jié),并簡單說說原理南誊,從而讓大家也初步了解各個方案的實現(xiàn)身诺。(其實很多人都是看見別人寫的適配方案,雖然可能實際在使用了抄囚,但是卻從來沒有去了解過這個方案的原理,而且遇到一些簡單的坑的時候霉赡,因為不知道原理,也無法自己解決幔托。)
常見適配方案:
- 生成分辨率values文件夾
- 生成values -sw 文件夾
- 谷歌百分比布局庫
- AutoLayout
- 動態(tài)更改density
1. 基礎(chǔ)知識
其實本來不想寫這塊穴亏,因為基本大家都懂什么dp, dpi ,px , inch ,density等蜂挪,但是后面的一些適配都會涉及到這些原理,外加有時候面試別人嗓化,都是感覺知道這個知識點(diǎn)棠涮,但并不是真正的了解,所以我這邊還是重新提一下刺覆,我會用通俗易懂的例子來讓大家更好的理解
严肪。
(PS: 當(dāng)然想不看的可以直接跳過。)
這邊直接放一個腦圖講下基本的基礎(chǔ)知識:
1.1 px
我們可以看到現(xiàn)在市面上的手機(jī)分辨率截止到2018-05月隅津,統(tǒng)計為:
這里額外提一下诬垂,類似1080 x 1812,720 x 1184 等看著很奇怪的結(jié)尾不是0的分辨率,大部分是因為有虛擬鍵的原因伦仍,虛擬鍵占去了一部分高度结窘。
以1080 X 1920為例,它代表的是手機(jī)上的像素點(diǎn)充蓝,
類似這種隧枫,表示橫著有1080個像素點(diǎn),豎著有1920個像素點(diǎn)谓苟,所以1080 X 1920 代表了手機(jī)在橫向官脓、縱向上的像素點(diǎn)數(shù)總和
所以如果我們寫了一個Button,假設(shè)高度和寬度都為10px , 則說明在這個屏幕點(diǎn)上高寬都占了10個點(diǎn)涝焙。
1.2 inch(屏幕尺寸)
手機(jī)屏幕的物理尺寸卑笨,我們經(jīng)常聽到有人說我買的是iPhone 8 plus,尺寸是5.5的屏幕仑撞,iPhone 8尺寸是 4.7的赤兴。其實它們所帶的單位都是inch(英寸), 1(inch)≈2.54(cm)
所以屏幕尺寸就是按屏幕對角測量的實際物理尺寸。
為簡便起見隧哮,Android 將所有實際屏幕尺寸分組為四種通用尺寸:小桶良、 正常、大和超大沮翔。
1.3 dpi
屏幕物理區(qū)域中的像素量陨帆;通常稱為 dpi(Dots Per Inch 每英寸 點(diǎn)數(shù))。所以看標(biāo)題就知道采蚀,他更像是在求一個密度疲牵。那我們既然知道了手機(jī)屏幕對角線的尺寸,我們只要知道了手機(jī)對角線上的px數(shù)量榆鼠,除一下就知道了每英寸上的像素點(diǎn)數(shù)了纲爸。
所以我們只需要通過勾股定理獲取對角線上的像素值,再除以屏幕尺寸值就可以了璧眠。
為簡便起見缩焦,Android 將所有屏幕密度分組為六種通用密度: 低读虏、中、高袁滥、超高盖桥、超超高和超超超高。
六種通用的密度:
- ldpi(低)~120dpi
- mdpi(中)~160dpi
- hdpi(高)~240dpi
- xhdpi(超高)~320dpi
- xxhdpi(超超高)~480dpi
- xxxhdpi(超超超高)~640dpi
1.4 dp 和 density
其實dp 本來是叫dip (Density Independent Pixels)题翻,所有有時候面試的別人揩徊,面試者會弄錯,把dip當(dāng)做了dpi嵌赠,所以你問他請說下 dp 和 dip 塑荒,他會把 dip說能dpi的內(nèi)容。
我們舉例說下這塊知識點(diǎn):
要畫一個 高和寬各為屏幕的一般的按鈕姜挺,我們假設(shè)有二塊屏幕齿税,一塊是100 X 100 ,一塊是 200 X 200 炊豪,那這時候第一塊的屏幕上我們寫B(tài)utton 應(yīng)該為:
<Button
layout_height = "50px"
layout_width = "50px"/>
第二個屏幕的Button應(yīng)該為:
<Button
layout_height = "100px"
layout_width = "100px"/>
這樣是不是都各自占了屏幕的高寬的一半凌箕,但是假如有第三個屏幕 300 X 300 呢,難不成再寫一個Button的高寬值词渤? 所以我們可以用一種單位來代替牵舱,但是這種單位可以在不同的屏幕環(huán)境下,值是不同的缺虐。比如我們就把這個單位當(dāng)做“haha”芜壁。
比如我們現(xiàn)在都這么寫:
<Button
layout_height = "50haha"
layout_width = "50haha"/>
這時候在100 x 100的時候, 50haha = 50px 高氮,在200 X 200 屏幕的時候 慧妄, 50 haha = 100px , 在 300 X 300 屏幕的時候,50haha 等150px纫溃。
這個感覺就很像你跟別人說我欠你50 money腰涧,如果在中國韧掩,代表你欠別人50元人民幣紊浩,但是如果在美國,你這么說疗锐,指你欠50美元坊谁,也就是欠了三百多元人民幣。(這個例子不要跟我較真滑臊,我就意思意思而已)
所以dp就是類似我們上面自己定義的haha這個單位口芍。
比如50dp = 50px ,這時候1dp = 1px , 50dp = 100px的時候 是 1dp = 2px 雇卷,所以我們可以看到倍數(shù)分別為 1 和 2 鬓椭,我們用density來代表這個倍數(shù)颠猴。也就是說: dp * density = px,這時候就是 50 dp * 1 = 50px , 50dp * 2 = 100px
(就像是我說我欠你50 money小染,在中國翘瓮,這個density就是1 , 也就是欠你50元人民幣裤翩,在美國可能就是指300多人民幣资盅,這個density也就是 美元換算成人民幣的倍數(shù))
那么這個density具體是怎么來的呢?其實很簡單踊赠,記不記得我們前面說過dpi 呵扛,也就是屏幕的密度,我們就用這個密度來做比較筐带,比如我們 把160dpi 作為標(biāo)準(zhǔn)今穿,那另外一個手機(jī)是320dpi ,那么這個density就是 (320/160 = 2)伦籍。
所以我們再次把公式 : dp * density = px 轉(zhuǎn)變?yōu)椋?dp * (dpi / 160) = px
那么為什么用160dpi作為標(biāo)準(zhǔn)呢荣赶,以前看到文章提過:mdpi基于第一款 Android 設(shè)備 ″T-Mobile G1″ 的屏幕配置(縮放系數(shù)scale=1)。
1.5 基礎(chǔ)知識小結(jié)
所以假如我們現(xiàn)在的手機(jī)分辨率知道了鸽斟,手機(jī)屏幕尺寸也知道了拔创。我們通過公式求出 dpi ,然后 dpi / 160 就是當(dāng)前手機(jī)的density富蓄,然后我們就知道我寫了1dp 在這臺手機(jī)上具體是多少px了剩燥。
具體的安卓手機(jī)尺寸四個分類及6中dpi分類:
我們的某臺手機(jī)的dpi,density立倍,分辨率等如何獲取呢灭红,:
DisplayMetrics mDisplayMetrics = getResources().getDisplayMetrics();
//橫向分辨率
int width = mDisplayMetrics.widthPixels;
//豎向分辨率
int height = mDisplayMetrics.heightPixels;
//density值
float density = mDisplayMetrics.density;
//dpi的值就等于density * 160
float dpi = density * 160;
也許有人說,那我們使用dp不是已經(jīng)完美的實現(xiàn)了各種兼容性嗎口注,就像我們上面提到過的变擒,100 X 100 ,200X200 寝志, 300 X 300的屏幕娇斑,我們都只要寫50haha, 就分別代表了50,100材部,150毫缆,不是就占了各自屏幕的一半了么。理論上的確是這樣乐导,但是我們剛提過我們的density是等于 (dpi / 160),而dpi又由分辨率和屏幕尺寸同時決定苦丁,安卓手機(jī)的碎片化太過嚴(yán)重,所以很多手機(jī)雖然分辨率不同及屏幕尺寸不同物臂,造成最后的dpi一樣旺拉,所以最后的density也一樣产上,就造成了適配實現(xiàn)不全。假設(shè)我們多了一個400X400 的設(shè)備蛾狗,因為它的屏幕尺寸也同時變大了很多蒂秘,所以最終的density和300X300一樣,那這時候我們寫了50haha淘太,也就代表了150px姻僧,這時候明顯在400X400上面并沒有顯示為一半,甚至當(dāng)這個400X400的設(shè)置的屏幕尺寸超級大蒲牧,反而可能算下來的density與100X100的一樣撇贺,那這時候50haha可能就只有50px,則顯示差距就更大了冰抢。
(其實主要原因就是dpi不是單獨(dú)由分辨率來決定松嘶,同時還有屏幕尺寸影響,所以二個變量同時作用挎扰,造成不同分辨率的手機(jī)最后的density也可能相同翠订。這樣dp轉(zhuǎn)換成的px也就相同了,但是手機(jī)的分辨率本身有不同遵倦,這時候就會出現(xiàn)適配不對尽超。)
2 各類適配方案
2.1 生成分辨率values文件夾
因為我們上面提過 , px = (dpi / 160) * dp梧躺, 但是dpi又是同時由分辨率和屏幕尺寸同時決定似谁,造成了不同的分辨率,dpi可能一樣掠哥,這樣最終得到的px一樣巩踏,比如都是占屏幕的一半,300X300得到的可能是150续搀,但是400X 400得到的也是150痢缎,這時候就不對了用含。
那我們就想到了方仿。我們能不能不是同時受到分辨率和屏幕尺寸決定花盐,而是只受一個因素來影響褐啡,這樣就是真正的按比例來了霜第。比如300X300是150玉锌,400X400是200抢肛,500X500是250煞抬,是只受分辨率的影響霜大,所以分辨率大的,最終得到的結(jié)果一定就大革答。所以我們就不能使用dp了战坤。而是一個新的單位曙强,而這個單位是根據(jù)不同的分辨率,得到不同的值途茫,那怎么計算呢碟嘴,就是窮舉法,比如剛才的300X300囊卜,我們規(guī)定1 haha等于1 px,然后再600 X 600里面娜扇,1 haha 等2 px , 1200X1200里面是 1 haha 等于 3 px 栅组。所以我們在不同分辨率下的values文件夾下寫上不同的值:
300X300下
<dimens name = "1haha"> 1px </dimens>
600X600下
<dimens name = "1haha"> 2px </dimens>
1200X1200下
<dimens name = "1haha"> 3px </dimens>
所以這個就是方案1 雀瓢,附上文章鏈接。
Android 屏幕適配方案
我們可以看下面的圖:
我們可以看到列舉了所有可能的屏幕分辨率的values玉掸,然后手動按照倍數(shù)刃麸,進(jìn)行相應(yīng)的賦值。當(dāng)然這些文件不可能手寫司浪,通過Java自動生成相應(yīng)的文件:
這樣最終影響結(jié)果的就只是分辨率的了泊业,分辨率越大的,x1的值越大啊易。
但是這個方案有一個致命的缺陷吁伺,那就是需要精準(zhǔn)命中才能適配,比如1920x1080的手機(jī)就一定要找到1920x1080的限定符租谈,否則就只能用統(tǒng)一的默認(rèn)的dimens文件了箱蝠。而使用默認(rèn)的尺寸的話,UI就很可能變形垦垂,簡單說宦搬,就是容錯機(jī)制很差。
2.2 生成values -sw 文件夾
可以參考:Android 目前最穩(wěn)定和高效的UI適配方案
騷年你的屏幕適配方式該升級了!-smallestWidth 限定符適配方案
其實這個方式跟上面的2.1方法原理可以說一模一樣劫拗。唯一的區(qū)別就是使用了sw來保證一定的容錯性间校。
我們看到其實就是把上面具體的分辨率values改成了values - sw而已。
2.3 百分比布局庫
Android 百分比布局庫(percent-support-lib) 解析與擴(kuò)展
Android 增強(qiáng)版百分比布局庫 為了適配而擴(kuò)展
其實這個也是很簡單的页慷,字面意思憔足,我寫了這個Button寬度為父布局的百分之50,則在不同手機(jī)上酒繁,都是占據(jù)了百分之50滓彰。使用過過百分比布局的人都應(yīng)該知道,我們寫的時候是這么寫的:
<android.support.percent. PercentRelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_heightPercent="20%"
app:layout_widthPercent="50%"
android:gravity="center"
/>
</PercentRelativeLayout >
其實原理很簡單州袒,就是動態(tài)計算實際的百分之50在不同機(jī)器的時候到底占了多少px揭绑,2.1,2.2則是等于提前幫我們計算好了具體的px,然后寫在了文件里面他匪,然后我們?nèi)プx數(shù)據(jù)菇存。
那它的實現(xiàn)原理是什么呢?簡單來說就是二步:
- 獲取用戶到底填了多少的百分比數(shù)值
- 獲取父布局的空間邦蜜,然后乘以用戶填的百分比數(shù)值依鸥,或者一個新數(shù)值,然后賦值給該控件悼沈。
我們一步步來看源碼:
2.3.1 獲取用戶到底填了多少的百分比數(shù)值:
我們知道我們的百分比布局中的核心屬性是子控件填寫:
app:layout_heightPercent="20%"
app:layout_widthPercent="30%"
所以我們需要在PercentRelativeLayout中遍歷它下面的子控件贱迟,然后分別獲取每個子控件的百分比數(shù)值。
其實很簡單絮供,寫過自定義View的人應(yīng)該都知道关筒,因為這個其實就是自定義屬性而已。
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PercentLayout_Layout);
float value = array.getFraction(R.styleable.PercentLayout_Layout_layout_widthPercent, 1, 1,-1f);
2.3.2 獲取計算后的值并且賦值:
因為要動態(tài)獲取父控件的控件杯缺,同時把新的值賦值給子控件蒸播,所以該行為在onMeasure
方法中執(zhí)行。
//傳入的ViewGroup.LayoutParams params是遍歷的每個子View的LayoutParams
public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint,
int heightHint) {
// Preserve the original layout params, so we can restore them after the measure step.
mPreservedParams.width = params.width;
mPreservedParams.height = params.height;
if (widthPercent >= 0) {
params.width = (int) (widthHint * widthPercent);
}
if (heightPercent >= 0) {
params.height = (int) (heightHint * heightPercent);
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "after fillLayoutParams: (" + params.width + ", " + params.height + ")");
}
}
當(dāng)然具體源碼會更多萍肆,我不會大篇幅完整講流程袍榆,更多的是講解思路。
2.4 AutoLayout
Android AutoLayout全新的適配方式 堪稱適配終結(jié)者
使用方式很簡單:
- 注冊設(shè)計圖尺寸
將autolayout引入
dependencies {
compile project(':autolayout')
}
在你的項目的AndroidManifest中注明你的設(shè)計稿
的尺寸塘揣。
<meta-data android:name="design_width" android:value="768"></meta-data>
<meta-data android:name="design_height" android:value="1280"></meta-data>
- Activity中開啟設(shè)配
讓你的Activity去繼承AutoLayoutActivity
我們想到的原理包雀,肯定也是把填在AndroidManifest.xml里面的數(shù)值讀取出來,然后作為參考值亲铡。然后在不同手機(jī)上動態(tài)的計算出來數(shù)值才写,是不是感覺和百分比布局有點(diǎn)相似。
我們來看下AutoLayoutActivity源碼:
public class AutoLayoutActivity extends AppCompatActivity
{
private static final String LAYOUT_LINEARLAYOUT = "LinearLayout";
private static final String LAYOUT_FRAMELAYOUT = "FrameLayout";
private static final String LAYOUT_RELATIVELAYOUT = "RelativeLayout";
@Override
public View onCreateView(String name, Context context, AttributeSet attrs)
{
View view = null;
if (name.equals(LAYOUT_FRAMELAYOUT))
{
view = new AutoFrameLayout(context, attrs);
}
if (name.equals(LAYOUT_LINEARLAYOUT))
{
view = new AutoLinearLayout(context, attrs);
}
if (name.equals(LAYOUT_RELATIVELAYOUT))
{
view = new AutoRelativeLayout(context, attrs);
}
if (view != null) return view;
return super.onCreateView(name, context, attrs);
}
}
我們發(fā)現(xiàn)把我們寫在Layout.xml里面的布局控件替換成AutoXXXX等自定義控件奖蔓。那我們以AutoLinearLayout來分析:其實看過百分比布局的源碼赞草,就會發(fā)現(xiàn)基本架構(gòu)都一樣,所以百分比布局的代碼看得懂吆鹤,再去看AutoLayout相關(guān)代碼會很快厨疙。
2.5 動態(tài)更改density
一種極低成本的Android屏幕適配方式
Android屏幕適配很麻煩嗎?不疑务!太簡單了沾凄。
Android 屏幕適配從未如斯簡單
騷年你的屏幕適配方式該升級了!-今日頭條適配方案
- 假如設(shè)計圖是按1920px * 1080px來設(shè)計,以density為3來標(biāo)注知允,也就是屏幕其實是640dp * 360dp撒蟀。這時候如果我們的Button想要占據(jù)一半,是不是寬度需要設(shè)置成180dp温鸽。
- 那假如我們的手機(jī)屏幕是1280X 720保屯,density是2 ,則寬度是360dp,的確當(dāng)設(shè)置成180dp的時候也正好占據(jù)一半配椭。
- 但是萬一1280X 720的手機(jī)的density是3呢虫溜,則寬度為240dp, 這時候設(shè)置成180dp雹姊,實際的px值為: 180 * 3 = 540px 股缸,但是我們想要的是360px ,也就是 180 * density = 360px , 既然我們設(shè)置成的180dp不能改變(也就是設(shè)置一個值吱雏,適配各種手機(jī))敦姻,那么我們只能改變這個density值。
- 換成公式就是: 180 * density = 360歧杏,那么density是多少镰惦。哈哈。沒錯是2 犬绒,我們動態(tài)把density從 3變成2旺入,是不是就符合了。
- 比如960X540 的手機(jī)凯力,density是2 茵瘾,因為我們的Button寬度設(shè)置成了180dp,寬度為180 X 2 = 360px,超過了一半咐鹤,我們只需要動態(tài)更改density滿足 180X density = 270px即可拗秘,所以我們的density算出來是1.5。
那么density具體怎么得出來呢祈惶,很簡單雕旨,我們剛才假設(shè)的是有一個按鈕,占了屏幕的一半捧请,那我們假設(shè)占了整個手機(jī)屏幕不就可以了凡涩。
設(shè)計圖的寬度是360dp,而960X540的手機(jī)疹蛉,只要540/360 = 1.5就可以得到,所以 density = 設(shè)備真實寬(單位px) / 360
if (orientation.equals("height")) {
targetDensity = (appDisplayMetrics.heightPixels - barHeight) / 667f;
} else {
targetDensity = appDisplayMetrics.widthPixels / 360f;
}
所以本方案就是動態(tài)更改density以滿足設(shè)計圖方案突照。
結(jié)語:
emm.......大家輕噴即可。氧吐。讹蘑。。