版權聲明:本文為HaiyuKing原創文章,轉載請注明出處!
前言
這里只是根據參考資料整理下,具體內容請閱讀參考資料。
原型設計圖
推薦1倍效果圖,即采用 720 * 360 大小( 1280 *720:兩倍圖 \ 1920 * 1080: 三倍圖),最主要的原因就是1px = 1dp,效果圖標多大的px,布局就寫多大dp。
屏幕各項參數
- 手機像素(px):一個小黑點就是像素;
- 手機尺寸:屏幕的對角線的長度;
- 手機分辨率:整個屏幕一共有多少個點(像素),常見取值 480X800 ,320X480等;
- 像素密度(dpi):
1. 每英寸中的像素數(假如設備分辨率為320*240,屏幕長2英寸寬1.5英寸,dpi=320/2 = 240/1.5 =160)
2. 對應於DisplayMetrics類中屬性densityDpi的值;
3. 當然這種寬和高的dpi都相同的情況現在已經很少見,所以實際計算方式見下圖;
像素密度(dpi)、屏幕尺寸、分辨率三者關系:
舉例說明:屏幕分辨率為:1920*1080,屏幕尺寸為5吋的話,那么dpi為440:
- 密度(density):
1. 每平方英寸中的像素數(density = dpi / 160 );
3. 對應於DisplayMetrics類中屬性density的值;
4. 可用於px與px與dip的互相轉換 :dp = px / density ;
720P,和1080P的手機,dpi是不同的,這也就意味着,不同的分辨率中,1dp對應不同數量的px(720P中,1dp=2px,1080P中1dp=3px)。
- 設備獨立像素(dp、dip):
1.不同設備有不同的顯示效果,不依賴像素(dp = px / density = px / (dpi / 160) )
2.dpi(像素密度)為160 的設備上1dp = 1px。 - 放大像素(sp):用於字體顯示;
- dp轉px、px轉dp:
public class Dp2Px { public static int dp2px(Context context, int dp) { return (int) (dp * context.getResources().getDisplayMetrics().density + 0.5); } public static int px2dp(Context context, int px) { return (int) (px / context.getResources().getDisplayMetrics().density + 0.5); } }
說明:0.5 是為了避免損失精度。
適配分類
一、圖片適配
在AndroidStudio的資源目錄res下有五個層級圖片文件夾,分別用來存放不同分辨率的圖片:
- drawable-ldpi :低分辨率(用的少了,一般不再用)
- drawable-mdpi:中分辨率
- drawable-hdpi:高分辨率
- drawable-xdpi:較高分辨率
- drawable-xxdpi:超級高分辨率
- drawable-xxxhpi:頂級分辨率
在對應的文件夾下放置不同分辨率的圖片就可以很好的對圖片進行適配。
隨着屏幕越來越大,推薦xxdpi的一套切圖,這樣就可以向下和向上兼容,節省資源。
建議圖標使用svg格式,圖片仍然使用png格式,svg的圖標大小約是png的1/4,在很大的項目中,圖標有很多,這個時候svg的優勢就凸顯無疑了。
二、布局適配
在layout之外,根據不同分辨率,創建不同的布局文件夾:
- layout-800 * 480
- layout-1280 * 720
手機會根據分辨率去找設定的不同大小的layout的布局。實際開發中這種使用的情況非常少,因為占用太多資源,慎用。
三、權重適配
當兩個或者更多布局占滿屏幕寬或高的時候,子布局可以使用權重適配,常見於LinearLayout線性布局中。
四、屏幕適配
詳細內容請閱讀參考資料。以下內容均是引用!
1、dp+自適應布局+weight比例布局直接適配
這基本是最原始的Android適配方案。
wrap_content,match_parent,layout_weight等,我們就要毫不猶豫的使用,而且在高這個維度上,我們要依照情況設計為可滑動的方式,或者match_parent,盡量不要寫死。總之,所有的適配方案都不是用來取代match_parent,wrap_content的,而是用來完善他們的。
缺點
(1)這只能保證我們寫出來的界面適配絕大部分手機,部分手機仍然需要單獨適配;
為什么dp只解決了90%的適配問題,因為並不是所有的1080P的手機dpi都是480,比如Google 的Pixel2(1920*1080)的dpi是420,也就是說,在Pixel2中,1dp=2.625px,這樣會導致相同分辨率的手機中,這樣,一個100dp*100dp的控件,在一般的1080P手機上,可能都是300px,而Pixel 2 中 ,就只有262.5px,這樣控件的實際大小會有所不同。
(2)這種方式無法快速高效的把設計師的設計稿實現到布局代碼中,通過dp直接適配,我們只能讓UI基本適配不同的手機,但是在設計圖和UI代碼之間的鴻溝,dp是無法解決的,因為dp不是真實像素。而且,設計稿的寬高往往和Android的手機真實寬高差別極大,以我們的設計稿為例,設計稿的寬高是375px*750px,而真實手機可能普遍是1080*1920;那么在日常開發中我們是怎么跨過這個鴻溝的呢?基本都是通過百分比啊,或者通過估算,或者設定一個規范值等等。總之,當我們拿到設計稿的時候,設計稿的ImageView是128px*128px,當我們在編寫layout文件的時候,卻不能直接寫成128dp*128dp。在把設計稿向UI代碼轉換的過程中,我們需要耗費相當的精力去轉換尺寸,這會極大的降低我們的生產力,拉低開發效率。
2、dimens基於px的適配(寬高限定符適配)
原理
根據市面上手機分辨率的占比分析,我們選定一個占比例值大的(比如1280*720)設定為一個基准,然后其他分辨率根據這個基准做適配。
基准的意思(比如320*480的分辨率為基准)是:
寬為320,將任何分辨率的寬度分為320份,取值為x1到x320
長為480,將任何分辨率的高度分為480份,取值為y1到y480
例如對於800 * 480的分辨率設備來講,需要在項目中values-800x480目錄下的dimens.xml文件中的如下設置(當然了,可以通過工具自動生成):
<?xml version="1.0" encoding="utf-8"?> <resources> <dimen name="x1">1.5px</dimen> <dimen name="x2">3.0px</dimen> <dimen name="x3">4.5px</dimen> <dimen name="x4">6.0px</dimen> <dimen name="x5">7.5px</dimen>
可以看到x1 = 480 / 基准 = 480 / 320 = 1.5 ;它的意思就是同樣的1px,在320/480分辨率的手機上是1px,在480/800的分辨率的手機上就是1*1.5px,px會根據我們指定的不同values文件夾自動適配為合適的大小。
缺點
第一,Android不同分辨率的手機實在太多了,可能你說主流就可以,的確小公司主流就可以,淘寶這種App肯定不能只適配主流手機。
第二,控件在設計圖上顯示的大小以及控件之間的間隙在小分辨率和大分辨率手機上天壤之別,你會發現大屏幕手機上控件超級大。可能你會覺得正常,畢竟分辨率不同。但實際效果大的有些誇張。
第三,設計圖(比如360640)上的內容占據屏幕的2/3,按照這種適配,特長手機(29601440 S8)上的內容也會占據2/3,這肯定不合理,控件之間的間隙會特別大,一看就不符合設計效果,手機長,內容占據低於2/3才正常,比如可能占據1/3.第四,占據資源大:好幾百KB,甚至多達1M或跟多。
3、dimen 基於dp的適配(smallestWidth適配)
原理
這種適配依據的是最小寬度限定符。指的是Android會識別屏幕可用高度和寬度的最小尺寸的dp值(其實就是手機的寬度值),然后根據識別到的結果去資源文件中尋找對應限定符的文件夾下的資源文件。這種機制和上文提到的寬高限定符適配原理上是一樣的,都是系統通過特定的規則來選擇對應的文件。
舉個例子,小米5的dpi是480,橫向像素是1080px,根據px=dp(dpi/160),橫向的dp值是1080/(480/160),也就是360dp,系統就會去尋找是否存在value-sw360dp的文件夾以及對應的資源文件。
缺點
- Android 私人訂制的原因,寬度方面參差不齊,不可能適配所有的手機。
- sp和dp值有些值不全(這個應該是可以解決的),姑且算是一個小問題。
- 項目中增加了N個文件夾,上拉下拉查看文件非常不方便:想看string或者color資源文件需要拉很多再能到達。
- 通過寬度限定符就近查找的原理,可以看出來匹配出來的大小不夠准確。
- 是在Android 3.2 以后引入的,Google的本意是用它來適配平板的布局文件(但是實際上顯然用於diemns適配的效果更好),不過目前所有的項目應該最低支持版本應該都是4.0了(糗事百科這么老的項目最低都是4.0哦),所以,這問題其實也不重要了。
4、今日頭條適配(修改手機的設備密度 density)
這套方案對老項目是不太友好的,因為修改了系統的density值之后,整個布局的實際尺寸都會發生改變,如果想要在老項目文件中使用,恐怕整個布局文件中的尺寸都可能要重新按照設計稿修改一遍才行。因此,如果你是在維護或者改造老項目,使用這套方案就要三思了。
原理
通過修改density值,強行把所有不同尺寸分辨率的手機的寬度dp值改成一個統一的值,這樣就解決了所有的適配問題。
比如,設計稿寬度是360px,那么開發這邊就會把目標dp值設為360dp,在不同的設備中,動態修改density值,從而保證(手機像素寬度)px/density這個值始終是360dp,這樣的話,就能保證UI在不同的設備上表現一致了。
UI設計圖是按屏幕寬度為360dp來設計的,那么在上述設備上,屏幕寬度其實為1080/(440/160)=392.7dp,也就是屏幕是比設計圖要寬的。這種情況下, 即使使用dp也是無法在不同設備上顯示為同樣效果的。 同時還存在部分設備屏幕寬度不足360dp,這時就會導致按360dp寬度來開發實際顯示不全。加上16:9、4:3甚至其他寬高比層出不窮,寬高比不同,顯示完全一致就不可能了。
通常下,我們只需要以寬或高一個維度去適配,比如我們Feed是上下滑動的,只需要保證在所有設備中寬的維度上顯示一致即可,再比如一個不支持上下滑動的頁面,那么需要保證在高這個維度上都顯示一致,尤其不能存在某些設備上顯示不全的情況。
- 支持以寬或者高一個維度去適配,保持該維度上和設計圖一致;
- 支持dp和sp單位,控制遷移成本到最小。
今日頭條的適配方式,今日頭條適配方案默認項目中只能以高或寬中的一個作為基准,進行適配。
px = dp * density(dp是360dp),想要px正好是屏幕寬度的話,只能修改density。
/** * 適配:修改設備密度 */ private static float sNoncompatDensity; private static float sNoncompatScaledDensity; public static void setCustomDensity(@NonNull Activity activity, @NonNull final Application application) { DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics(); if (sNoncompatDensity == 0) { sNoncompatDensity = appDisplayMetrics.density; sNoncompatScaledDensity = appDisplayMetrics.scaledDensity; // 防止系統切換后不起作用 application.registerComponentCallbacks(new ComponentCallbacks() { @Override public void onConfigurationChanged(Configuration newConfig) { if (newConfig != null && newConfig.fontScale > 0) { sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity; } } @Override public void onLowMemory() { } }); } float targetDensity = appDisplayMetrics.widthPixels / 360; // 防止字體變小 float targetScaleDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity); int targetDensityDpi = (int) (160 * targetDensity); appDisplayMetrics.density = targetDensity; appDisplayMetrics.scaledDensity = targetScaleDensity; appDisplayMetrics.densityDpi = targetDensityDpi; final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics(); activityDisplayMetrics.density = targetDensity; activityDisplayMetrics.scaledDensity = targetScaleDensity; activityDisplayMetrics.densityDpi = targetDensityDpi; }
只需要在baseActivity中添加一句話即可。適配就是這么簡單。
DisplayUtil.setCustomDensity(this, getApplication());
缺點
- 只需要修改一次 density,項目中的所有地方都會自動適配,這個看似解放了雙手,減少了很多操作,但是實際上反應了一個缺點,那就是只能一刀切的將整個項目進行適配,但適配范圍是不可控的。這樣不是很好嗎?這樣本來是很好的,但是應用到這個方案是就不好了,因為我上面的原理也分析了,這個方案依賴於設計圖尺寸,但是項目中的系統控件、三方庫控件、等非我們項目自身設計的控件,它們的設計圖尺寸並不會和我們項目自身的設計圖尺寸一樣。當這個適配方案不分類型,將所有控件都強行使用我們項目自身的設計圖尺寸進行適配時,這時就會出現問題,當某個系統控件或三方庫控件的設計圖尺寸和和我們項目自身的設計圖尺寸差距非常大時,這個問題就越嚴重。