前言
前面我們已經將android的繪制基礎已經講完,那么現在我們下面的兩個內容點是事件分發問題,和螢屏適配相關,這篇我們主要來進android但中的各種螢屏適配問題
1.螢屏適配概念
而隨著支持Android系統的設備(手機、平板、電視、手表)的增多,設備碎片化、品牌碎片化、系統碎片化、傳感器碎片化和螢屏碎片化的程度也在不斷地加深,而我們今天要探討的,則是對我們開發影響比較大的——螢屏的碎片化,
下面這張圖是Android螢屏尺寸的示意圖,在這張圖里面,藍色矩形的大小代表不同尺寸,顏色深淺則代表所占百分比的大小,

下面是IOS的

通過對比可以很明顯知道adnroid的螢屏到底有多少種了吧,而蘋果只有5種包括現在最新的劉海屏,那么想要對螢屏適配的相關處理方案有一定的自己的心得,那么首先我們需要了解關于android螢屏的一定基礎
2.螢屏適配基礎
那么下面是我給大家寫的一個螢屏適配基礎的思維導圖,基本為一個基礎篇的大綱,這里我不會非常詳細的給大家去過,就全部體現在腦圖當中

那么螢屏適配相關概念上我們需要掌握最基礎的3點,相對基礎的內容是給段位比較低的同學,高段位可選擇跳過
2.1 什么是螢屏尺寸,螢屏解析度,螢屏像素密度
螢屏尺寸指的是:

解析度:

螢屏像素密度(DPI)指每一英寸長度中,可顯示輸出的像素個數,DPI的數字受螢屏尺寸和解析度所影響,DPI可以通過計算所得

上述內容在于掃盲…畢竟還是有不清楚的同學,而DPI跟下面內容結合比較密切所以啰嗦了兩句
2.2 什么是dp,dip,sp,px?它們之間的關系?
- px:構成影像的最小單位
- dip(重點):Desity Independent pixels的縮寫,即密度無關像素
android內部在識別影像像素時以160dpi為基準,1dip=1px或1dp=1px,例:在下列兩臺設備上使用DP進行操作
- 480 * 320 160dpi 那么這臺機器上的1DP會被翻譯成1px
- 800 * 480 240dpi 而這臺機器上的1DP會被翻譯成1.5px
也就是說當前我們設備的DP是由android給予的基礎標準按比例進行翻譯的,這也是為什么我們用DP能解決一部分適配的原因
2.3 mdpi,hdpi,xdpi,xxdpi,xxxdpi?如何計算和區分?
名稱 像素密度范圍 圖片大小
mdpi 120dp~160dp 48×48px
hdpi 160dp~240dp 72×72px
xhdpi 240dp~320dp 96×96px
xxhdpi 320dp~480dp 144×144px
xxxhdpi 480dp~640dp 192×192px
在Google官方開發檔案中,說明了mdpi:hdpi:xhdpi:xxhdpi:xxxhdpi=2:3:4:6:8的尺寸比例進行縮放,例如,一個圖示的大小為48×48dp,表示在mdpi上,實際大小為48×48px,在hdpi像素密度上,實際尺寸為mdpi上的1.5倍,即72×72px,以此類推,可以繼續往后增加,不過一般情況下已經夠用了,這種用來去適配手機和平板之間的圖形問題
3.螢屏適配方案基礎篇(常識,見思維導圖,這里只詳細講一下限定符)
3.1使用 “wrap_content” 和 “match_parent”
3.2相對布局控制螢屏
3.3圖的應用
上面三個都是最基本的android使用,我們只需要在平常應用是注意到就行了,這里不詳細去講
3.4 限定符
我們在做螢屏的適配時在螢屏 尺寸相差不大的情況下,dp可以使不同解析度的設備上展示效果相似,但是在螢屏尺寸相差比較大的情況下(平板),dp就失去了這種效果,所以需要以下的限定符來約束,采用多套布局,數值等方式來適配,
那么其實所謂的限定符就是android在進行資源加載的時候會按照螢屏的相關資訊對檔案夾對應的名字進行識別,而這些特殊名字就是我們的限定符
限定符分類:
螢屏尺寸
small 小螢屏
normal 基準螢屏
large 大螢屏
xlarge 超大螢屏
螢屏密度
ldpi <=120dpi
mdpi <= 160dpi
hdpi <= 240dpi
xhdpi <= 320dpi
xxhdpi <= 480dpi
xxhdpi <= 640dpi(只用來存放icon)
nodpi 與螢屏密度無關的資源.系統不會針對螢屏密度對其中資源進行壓碩訓者拉伸
tvdpi 介于mdpi與hdpi之間,特定針對213dpi,專門為電視準備的,手機應用開發不需要關心這個密度值.
螢屏方向
land 橫向
port 縱向
螢屏寬高比
long 比標準螢屏寬高比明顯的高或者寬的這樣螢屏
notlong 和標準螢屏配置一樣的螢屏寬高比
2.4.1使用尺寸限定符:
當我們要在大螢屏上顯示不同的布局,就要使用large限定符,例如,在寬的螢屏左邊顯示串列右邊顯示串列項的詳細資訊,在一般寬度的螢屏只顯示串列,不顯示串列項的詳細資訊,我們就可以使用large限定符,
res/layout/main.xml 單面板:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 串列 -->
<fragment android:id="@+id/headlines"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="match_parent" />
</LinearLayout>
res/layout-large/main.xml 雙面板:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<!-- 串列 -->
<fragment android:id="@+id/headlines"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="400dp"
android:layout_marginRight="10dp"/>
<!-- 串列項的詳細資訊 -->
<fragment android:id="@+id/article"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.ArticleFragment"
android:layout_width="fill_parent" />
</LinearLayout>
如果這個程式運行在螢屏尺寸大于7inch的設備上,系統就會加載res/layout-large/main.xml 而不是res/layout/main.xml,在小于7inch的設備上就會加載res/layout/main.xml,
需要注意的是,這種通過large限定符分辨螢屏尺寸的方法,適用于android3.2之前,在android3.2之后,為了更精確地分辨螢屏尺寸大小,Google推出了最小寬度限定符,
2.4.2 使用最小寬度限定符
最小寬度限定符的使用和large基本一致,只是使用了具體的寬度限定,
res/layout/main.xml,單面板(默認)布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:id="@+id/headlines"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="match_parent" />
</LinearLayout>
res/layout-sw600dp/main.xml,雙面板布局: Small Width 最小寬度
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<fragment android:id="@+id/headlines"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="400dp"
android:layout_marginRight="10dp"/>
<fragment android:id="@+id/article"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.ArticleFragment"
android:layout_width="fill_parent" />
</LinearLayout>
這種方式是不區分螢屏方向的,這種最小寬度限定符適用于android3.2之后,所以如果要適配android全部的版本,就要使用large限定符和sw600dp檔案同時存在于專案res目錄下,
這就要求我們維護兩個相同功能的檔案,為了避免繁瑣操作,我們就要使用布局別名,
2.4.3使用布局別名
- res/layout/main.xml: 單面板布局
- res/layout-large/main.xml: 多面板布局
- res/layout-sw600dp/main.xml: 多面板布局
由于后兩個文具檔案一樣,我們可以用以下兩個檔案代替上面三個布局檔案:
- res/layout/main.xml 單面板布局
- res/layout/main_twopanes.xml 雙面板布局
然后在res下建立res/values/layout.xml、res/values-large/layout.xml、res/values-sw600dp/layout.xml三個檔案,
**默認布局:**res/values/layout.xml:
<resources>
<item name="main" type="layout">@layout/main</item>
</resources>
**Android3.2之前的平板布局:**res/values-large/layout.xml:
<resources>
<item name="main" type="layout">@layout/main_twopanes</item>
</resources>
**Android3.2之后的平板布局:**res/values-sw600dp/layout.xml:
<resources>
<item name="main" type="layout">@layout/main_twopanes</item>
</resources>
這樣就有了main為別名的布局,
在activity中setContentView(R.layout.main); 這樣,程式在運行時,就會檢測手機的螢屏大小,如果是平板設備就會加載res/layout/main_twopanes.xml,如果是手機設備,就會加載res/layout/main.xml ,我們就解決了只使用一個布局檔案來適配android3.2前后的所有平板設備,
2.4.4使用螢屏方向限定符
如果我們要求給橫屏、豎屏顯示的布局不一樣,就可以使用螢屏方向限定符來實作,例如,要在平板上實作橫豎屏顯示不用的布局,可以用以下方式實作,
res/values-sw600dp-land/layouts.xml:橫屏
<resources>
<item name="main" type="layout">@layout/main_twopanes</item>
</resources>
res/values-sw600dp-port/layouts.xml:豎屏
<resources>
<item name="main" type="layout">@layout/main</item>
</resources>
那么上述是最基本的螢屏適配的解決方案
這里找到一個神人給官方適配方案做的翻譯推給大家參考:https://blog.csdn.net/wzy_1988/article/details/52932875
4.螢屏適配解決方案:
基礎篇結束之后,我們市場上最常用的解決方案我給大家總結了兩種
4.1 通過自定義布局組件來完成
核心原理是根據一個參照解析度進行布局,然后再各個機器上提取當前機器解析度換算出系數之后,然后再通過重新測量的方式來達到適配的效果,這一套方案基本能適用于95以上的機型,到時候再加上劉海屏的適配就OK了,
/**
* Created by barry on 2018/6/7.
*/
public class ScreenAdaptationRelaLayout extends RelativeLayout {
public ScreenAdaptationRelaLayout(Context context) {
super(context);
}
public ScreenAdaptationRelaLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScreenAdaptationRelaLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
static boolean isFlag = true;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(isFlag){
int count = this.getChildCount();
float scaleX = UIUtils.getInstance(this.getContext()).getHorizontalScaleValue();
float scaleY = UIUtils.getInstance(this.getContext()).getVerticalScaleValue();
Log.i("testbarry","x系數:"+scaleX);
Log.i("testbarry","y系數:"+scaleY);
for (int i = 0;i < count;i++){
View child = this.getChildAt(i);
//代表的是當前空間的所有屬性串列
LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
layoutParams.width = (int) (layoutParams.width * scaleX);
layoutParams.height = (int) (layoutParams.height * scaleY);
layoutParams.rightMargin = (int) (layoutParams.rightMargin * scaleX);
layoutParams.leftMargin = (int) (layoutParams.leftMargin * scaleX);
layoutParams.topMargin = (int) (layoutParams.topMargin * scaleY);
layoutParams.bottomMargin = (int) (layoutParams.bottomMargin * scaleY);
}
isFlag = false;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}
public class UIUtils {
private Context context;
private static UIUtils utils ;
public static UIUtils getInstance(Context context){
if(utils == null){
utils = new UIUtils(context);
}
return utils;
}
//參照寬高
public final float STANDARD_WIDTH = 720;
public final float STANDARD_HEIGHT = 1232;
//當前設備實際寬高
public float displayMetricsWidth ;
public float displayMetricsHeight ;
private final String DIMEN_CLASS = "com.android.internal.R$dimen";
private UIUtils(Context context){
this.context = context;
//
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//加載當前界面資訊
DisplayMetrics displayMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
if(displayMetricsWidth == 0.0f || displayMetricsHeight == 0.0f){
//獲取狀態框資訊
int systemBarHeight = getValue(context,"system_bar_height",48);
if(displayMetrics.widthPixels > displayMetrics.heightPixels){
this.displayMetricsWidth = displayMetrics.heightPixels;
this.displayMetricsHeight = displayMetrics.widthPixels - systemBarHeight;
}else{
this.displayMetricsWidth = displayMetrics.widthPixels;
this.displayMetricsHeight = displayMetrics.heightPixels - systemBarHeight;
}
}
}
//對外提供系數
public float getHorizontalScaleValue(){
return displayMetricsWidth / STANDARD_WIDTH;
}
public float getVerticalScaleValue(){
Log.i("testbarry","displayMetricsHeight:"+displayMetricsHeight);
return displayMetricsHeight / STANDARD_HEIGHT;
}
public int getValue(Context context,String systemid,int defValue) {
try {
Class<?> clazz = Class.forName(DIMEN_CLASS);
Object r = clazz.newInstance();
Field field = clazz.getField(systemid);
int x = (int) field.get(r);
return context.getResources().getDimensionPixelOffset(x);
} catch (Exception e) {
return defValue;
}
}
}
4.2 給各個解析度單獨適配,res,dimens里設定各個對應的px,再統一呼叫,由系統篩選,
這種方式比較久遠了,但是確實還是有很多專案在使用到這種方式,其原理就是據設備螢屏的解析度各自寫一套dimens.xml檔案,然后根據一個基準解析度(例如720x1080),將寬度分成720份,取值為1px——720px,將高度分成1080份,取值為1px——1080px,生成各自dimens.xml檔案對應的值,
但是今天我根據這個方法,在這個方案的基礎之上給大家做了一次改變,運用之前所見的DP的概念,結合之前講的限定符,用DP來升級了這種方案,dp適配原理與px適配一樣,區別就在于px適配是根據螢屏解析度,即拿px值等比例縮放,而dp適配是拿dp值來等比縮放而已,
既然原理都一樣,都需要多套dimens.xml檔案,為什么說dp適配就比px適配好呢?因為px適配是根據螢屏解析度的,Android設備解析度一大堆,而且還要考慮虛擬鍵盤,而dp適配無論手機螢屏的像素多少,密度比值多少,80%的手機的最小寬度dp值(widthPixels / density)都為360dp,這樣就大大減少了dimens.xml檔案
PS:(現在基本上手機的dpi都在350+以上 那么按最低算 350/160=2.1 那么360 * 2.1 = 720+ 基本上手機的解析度都會在360dp之內 上面例子19201080的情況 500/160=3.125 那么 3603.125=1125其實也在360之內),
傳統做法:

改良后的做法:

獲取最小寬度獲取如下:
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int widthPixels = dm.widthPixels;
float density = dm.density;
float widthDP = widthPixels / density;
所以通過這種兩種形式的結合能夠達到我們整體適配任意機型的目的
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/375936.html
標籤:其他
