Android 屏幕適配方案
Android多分辨率適配框架
Android 屏幕適配之dimens(上線項目中實際應(yīng)用)
先讀完上面的文章再繼續(xù)讀本篇文章最好不過了
這篇文章是結(jié)合自己的開發(fā)總結(jié)的,能適應(yīng)大部分需求,就是因為安卓屏幕尺寸太多了,所以只能說適應(yīng)大部分的需求
術(shù)語和概念
屏幕尺寸(screen size): 屏幕的對角線測量。
為了方便测柠,Android把所有的屏幕尺寸分為了4個廣義的大羞赫:小蹬癌、正常、大概疆、更大
屏幕密度(screen density): 屏幕占據(jù)的物理區(qū)域所含像素的個數(shù),通常被稱為dpi(dots per inch)即每英寸的像素點(diǎn)數(shù)
分辨率(resolution): 屏幕上物理像素的點(diǎn)數(shù)
例如,有一個240px*400px的屏幕,可以理解為在這個屏幕上橫著有400條線宿稀,每條線上有240個像素點(diǎn)
像素(px): 屏幕上的點(diǎn)
注意:由于JPG容易失真, 在Android開發(fā)中盡量避免使用.jpg圖片, 應(yīng)該使用.png圖片, 它采用了從LZ77派生的無損數(shù)據(jù)壓縮算法.
圖片相關(guān)
屏幕:尺寸5.1,分辨率1920X1080
DPI:(1920<sup>2</sup> + 1080<sup>2</sup>) / 5.1 = 2202 / 5.1 = 431
mdpi 120dpi ~ 160dpi
hdpi 160dpi ~ 240dpi
xhdpi 240dpi ~ 320dpi
xxhdpi 320dpi ~ 480dpi
xxxhdpi 480dpi ~ 640dpi
在設(shè)計圖標(biāo)時:
對于五種主流像素密度(mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi)
應(yīng)按照(2:3:4:6:8)的比例進(jìn)行縮放赖捌,(1x, 1.5x, 2x, 3x, 4x)
例如:
尺寸為100x100px的圖標(biāo)祝沸,設(shè)置ImageView
為wrap_content
,把圖片放進(jìn)xxhdpi中在不同像素密度的手機(jī)上顯示:
手機(jī)像素密度/文件夾像素密度
在xxh
手機(jī)上顯示為100*3/3=100px
在xhdpi
手機(jī)上顯示100*2/3=66px
依此類推
圖片占用的內(nèi)存:
寬*高*4*(手機(jī)像素密度/文件夾像素密度)的平方/1024/1024(MB)
比如:在xxhdpi手機(jī)上100*100px的圖片占用的內(nèi)存為:
100*100*4/1024/1024 = 0.038MB
像素適配
- 主流的分辨率
我已經(jīng)集成到了我們的程序中,當(dāng)然對于特殊的越庇,你可以通過參數(shù)指定罩锐。關(guān)于屏幕分辨率信息,可以通過該網(wǎng)站查詢:http://screensiz.es/phone - 下載jar
先下載autolayout.jar - 執(zhí)行命令
java -jar xx.jar width height width,height_width,height
例如我在開發(fā)中基準(zhǔn)屏幕為1080*1920
, 其他的要適配480*800,720*1280,1440*2560
等
就可以寫成java -jar autolayout.jar 1080 1920 480,800_720,1280_1080,1776_1080,1812_1440,1921_1440,2560
最后生成res文件夾
res
對于dimens文件這里我想說一下,其實一個文件夾下面一個dimens文件即可,沒必要弄兩個(lay_x和lay_y),刪掉多余的文件夾以及文件夾里的y坐標(biāo)
里面只保留
lay_x.xml
文件分析:
在適配過程中比較常見的問題是虛擬按鍵的問題,這里重點(diǎn)說一下:有些手機(jī)有虛擬按鍵,例如華為的很多手機(jī)都有,有些手機(jī)沒有,有虛擬按鍵的手機(jī)在適配過程中會出現(xiàn)一些問題,下面以華為 honor V8為例說一下這個問題
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.WindowManager;
import java.lang.reflect.Method;
/**
* @author lqx
*/
public class PxUtil {
/**
* @param context
* @return 獲取屏幕原始尺寸高度卤唉,包括虛擬功能鍵高度
*/
public static String getTotalHeight(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
DisplayMetrics displayMetrics = new DisplayMetrics();
@SuppressWarnings("rawtypes")
Class c;
try {
c = Class.forName("android.view.Display");
@SuppressWarnings("unchecked")
Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
method.invoke(display, displayMetrics);
} catch (Exception e) {
e.printStackTrace();
}
return "包含虛擬按鍵的分辨率:"+displayMetrics.widthPixels+"x"+displayMetrics.heightPixels;
}
/**
* @param context
* @return 獲取屏幕內(nèi)容高度不包括虛擬按鍵
*/
public static String getScreenHeight(Context context) {
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
if (wm != null) {
wm.getDefaultDisplay().getMetrics(outMetrics);
}
return "不包含虛擬按鍵的分辨率:"+outMetrics.widthPixels+"x"+outMetrics.heightPixels;
}
}
通過這兩個方法我們可以得到手機(jī)的分辨率高度和手機(jī)去除虛擬按鍵的高度,兩者相減就是手機(jī)的虛擬按鍵的高度
調(diào)用后得到的結(jié)果是總高度 : 2560 內(nèi)容高度 : 2408 虛擬按鍵 : 152
如果想要適配該機(jī)型,其實也很簡單,只需要把原來的values-2560x1440文件夾復(fù)制一份重新名為values-2408x1440即可,在使用dimens適配過程中,若遇有類似虛擬按鍵問題可照此處理,親測完美適配!
減少dimens文件除了刪除文件夾中的Y坐標(biāo),還有一個方法:對于一個主流的分辨率只要留虛擬按鍵高度最高的那組dimens文件即可,什么意思呢?比如說1920x1080分辨率,有多款機(jī)型都是這個分辨率,只不過是虛擬按鍵高度不同,你可能需要創(chuàng)建1788x1080,1812x1080,1776x1080...
等多套dimens文件,其實大可不必,只需要1776x1080這一套就夠了(保留去除虛擬按鍵之后最低的那一套),因為系統(tǒng)找不到對應(yīng)尺寸的dimens文件,會使用比它略小的分辨率的dimens文件,如此一來我們的dimens文件會大大減少的涩惑。
心得
分辨率的訪問順序:
按寬高的變化,變化小的先訪問桑驱。(乘積大的先訪問) 如:720x1280
710x1280
700x1280
710x800
700x800
720x1280
720x1270
710x1280
權(quán)重和margin padding
當(dāng)一個布局平分的時候盡量用權(quán)重,每個權(quán)重里面的小布局可以用margin和padding來設(shè)置,或者直接寫死了大小,這些配置的數(shù)值不能太大,否則會有顯示不好的結(jié)果.一般的在100px左右就可以
分析布局
打開Android Device Monitor
執(zhí)行3步
分析具體某個控件
區(qū)域控件的各參數(shù)意義:
eg:(1)TextView[155,1772][212,1829]
對應(yīng)意義:
(序號)控件名[左上角x坐標(biāo),左上角y坐標(biāo)][右下角x坐標(biāo),右下角y坐標(biāo)]
從而能得出這個控件的寬 = 212-155 = 57 高 = 1829-1772 = 57
依次類推
最后結(jié)合上面的像素適配那就是絕配的存在
謹(jǐn)記:控件寫死的情況下盡量不要超過100像素,否則顯示效果不佳
生成自己定制的jar
上面介紹的是能生成x和y的適配,我在開發(fā)中只用到了x所以接下來自己定制,由于項目中可能還會用到賦值的時候所以我會在代碼中說明
直接上代碼,其中內(nèi)置了幾種常用的分辨率
480,800;720,1280;1080,1920;1440,2560
其他的需要根據(jù)自己的需求添加,我設(shè)置的參考基準(zhǔn)是1080*1920
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
/**
* @author lqx
*/
public class CreatValue {
/**
* 參考基準(zhǔn)值的X作為標(biāo)準(zhǔn)
*/
private int baseW;
private int baseH;
private String dirStr = "./res";
/**
* 生成X方向的模板
*/
private final static String W_TEMPLATE = "<dimen name=\"x{0}\">{1}px</dimen>\n";
/**
* {height}:最后生成values-1920x1080中的1920
* {width}: 最后生成values-1920x1080中的1080
*/
private final static String VALUE_TEMPLATE = "values-{height}x{width}";
private static final String SUPPORT_DIMESION = "480,800;720,1280;1080,1920;1440,2560;";
private String supportStr = SUPPORT_DIMESION;
public CreatValue(int baseX, int baseY, String supportStr) {
this.baseW = baseX;
this.baseH = baseY;
if (!this.supportStr.contains(baseX + "," + baseY)) {
this.supportStr += baseX + "," + baseY + ";";
}
this.supportStr += validateInput(supportStr);
System.out.println(supportStr);
File dir = new File(dirStr);
if (!dir.exists()) {
dir.mkdir();
}
System.out.println(dir.getAbsoluteFile());
}
/**
* @param supportStr w,h_...w,h;
*/
private String validateInput(String supportStr) {
StringBuffer sb = new StringBuffer();
String[] vals = supportStr.split("_");
int w = -1;
int h = -1;
String[] wh;
for (String val : vals) {
try {
if (val == null || val.trim().length() == 0){
continue;
}
wh = val.split(",");
w = Integer.parseInt(wh[0]);
h = Integer.parseInt(wh[1]);
} catch (Exception e) {
System.out.println("skip invalidate params : w,h = " + val);
continue;
}
sb.append(w + "," + h + ";");
}
return sb.toString();
}
public void generate() {
String[] vals = supportStr.split(";");
for (String val : vals) {
String[] wh = val.split(",");
generateXmlFile(Integer.parseInt(wh[0]), Integer.parseInt(wh[1]));
}
}
private void generateXmlFile(int w, int h) {
StringBuffer sbForWidth = new StringBuffer();
sbForWidth.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
sbForWidth.append("<resources>\n");
float cellw = w * 1.0f / baseW;
System.out.println("width : " + w + "," + baseW + "," + cellw);
for (int i = 0; i < baseW; i++) {
sbForWidth.append("\t"+W_TEMPLATE.replace("{0}", i + "").replace("{1}",
change(cellw * i) + ""));
}
sbForWidth.append("\t"+W_TEMPLATE.replace("{0}", baseW + "").replace("{1}",
w + ""));
/**-----------------------------開始----------------------------------------*/
//下面是生成負(fù)數(shù)部分,因為有時候會用到margin為負(fù)數(shù)
// for (int i = -1; i > -baseW; i--) {
// sbForWidth.append("\t"+W_TEMPLATE.replace("{0}", "f"+i*-1).replace("{1}",
// change(cellw * i) + ""));
// }
// sbForWidth.append("\t"+W_TEMPLATE.replace("{0}", "f"+baseW).replace("{1}",
// -w + ""));
//生成負(fù)數(shù)部分到這個結(jié)束
/**-----------------------------結(jié)束----------------------------------------*/
sbForWidth.append("</resources>");
File fileDir = new File(dirStr + File.separator
+ VALUE_TEMPLATE.replace("{height}", h + "")//
.replace("{width}", w + ""));
fileDir.mkdir();
File layxFile = new File(fileDir.getAbsolutePath(), "lay_x.xml");
try {
PrintWriter pw = new PrintWriter(new FileOutputStream(layxFile));
pw.print(sbForWidth.toString());
pw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private static float change(float a) {
int temp = (int) (a * 100);
return temp / 100f;
}
public static void main(String[] args) {
int baseW = 1080;
int baseH = 1920;
String addition = "";
try {
if (args.length >= 3) {
baseW = Integer.parseInt(args[0]);
baseH = Integer.parseInt(args[1]);
addition = args[2];
} else if (args.length >= 2) {
baseW = Integer.parseInt(args[0]);
baseH = Integer.parseInt(args[1]);
} else if (args.length >= 1) {
addition = args[0];
}
} catch (NumberFormatException e) {
System.err.println("right input params : java -jar xxx.jar width height w,h_w,h_..._w,h;");
e.printStackTrace();
System.exit(-1);
}
new CreatValue(baseW, baseH, addition).generate();
}
}
注意: 上面的類中有一段生成負(fù)值的代碼 如果開發(fā)中有需要直接打開就可以
- 生成自己方便的jar
- 在空白文件夾中創(chuàng)建
類名.java
,也就是:CreatValue.java
,把上面的代碼復(fù)制到文件中
- 用命令編譯生成
CreatValue.class
文件,按住shift
+右擊鼠標(biāo),找到命令行
或者`Powershell窗口
這樣就說明程序沒問題
- 在目錄下新建一個
manifest.mf
文件竭恬,文件里寫如下內(nèi)容:
Main-Class: CreatValue
注意: CreatValue后面要加上一個換行才可以否則會出現(xiàn)錯誤
然后運(yùn)行如下一行命令:
jar cvfm autolayout.jar manifest.mf CreatValue.class CreatValue.java
其中:autolayout.jar的名字隨意就可以,之所以將CreatValue.java打包進(jìn)jar是為了以后好修改程序
目錄下會生成一個autolayout.jar,是個壓縮文件熬的。到此時Jar包已經(jīng)生成痊硕,目錄如下圖:
其中META-INF文件夾下的內(nèi)容如下:
MANIFEST.MF的內(nèi)容如下:
- 如何運(yùn)行該Jar包呢?
1>如果基準(zhǔn)值1080*1920
和需要生產(chǎn)的像素文件夾等不需要修改,直接雙擊就可以運(yùn)行程序了,然后會在目錄下產(chǎn)生一個res
文件夾
2>在命令行運(yùn)行:
其中:需要設(shè)定的基準(zhǔn)值是720*1280,還需要生成的像素文件夾有300*400,500*600
那么命令如下:
java -jar autolayout.jar 720 1280 300,400_500,600
輸出結(jié)果:
PS E:\Users\Documents\生成不同分辨率的values> java -jar autolayout.jar 720 1280 300,400_500,600
300,400_500,600
E:\Users\Documents\生成不同分辨率的values\.\res
width : 480,720,0.6666667
width : 720,720,1.0
width : 1080,720,1.5
width : 1440,720,2.0
width : 300,720,0.41666666
width : 500,720,0.6944444
除了生成原來的內(nèi)定分辨率還增加了300*400,500*600
,添加成功
到此就結(jié)束了,記著需要負(fù)值的時候就直接打開注釋就可以
附:
jar命令的簡介(可運(yùn)行”jar”命令來查看各個參數(shù)用法):
-c 創(chuàng)建一個jar包
-t 顯示jar中的內(nèi)容列表
-x 解壓jar包
-u 添加文件到j(luò)ar包中
-f 指定jar包的文件名
-v 生成詳細(xì)的報造押框,并輸出至標(biāo)準(zhǔn)設(shè)備
-m 指定manifest.mf文件.(manifest.mf文件中可以對jar包及其中的內(nèi)容作一些一設(shè)置)
-0 產(chǎn)生jar包時不對其中的內(nèi)容進(jìn)行壓縮處理
-M 不產(chǎn)生所有文件的清單文件(Manifest.mf)岔绸。這個參數(shù)與忽略掉-m參數(shù)的設(shè)置
-i 為指定的jar文件創(chuàng)建索引文件
-C 表示轉(zhuǎn)到相應(yīng)的目錄下執(zhí)行jar命令,相當(dāng)于cd到那個目錄,然后不帶-C執(zhí)行jar命令
參考 Java程序在命令行下編譯運(yùn)行打Jar包
通過刪除上面部分代碼做成一個工具類,復(fù)制代碼放到項目中,沒次增刪分辨率之后右擊運(yùn)行下main()就可以了
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
/**
* 生成不同dimension的工具類
* @author lqx
*/
public class CreatValue {
/**
* 參考基準(zhǔn)值的X作為標(biāo)準(zhǔn)
*/
private int baseW;
/**
* 此處的Y值只是用來生存文件夾的
*/
private int baseH;
private String dirStr = "./res";
/**
* 生成X方向的模板
*/
private final static String W_TEMPLATE = "<dimen name=\"x{0}\">{1}px</dimen>\n";
/**
* {height}:最后生成values-1920x1080中的1920
* {width}: 最后生成values-1920x1080中的1080
*/
private final static String VALUE_TEMPLATE = "values-{height}x{width}";
private static final String SUPPORT_DIMESION = "480,800;720,1280;1080,1920;1440,2560";
private String supportStr = SUPPORT_DIMESION;
private CreatValue(int baseX, int baseY) {
this.baseW = baseX;
this.baseH = baseY;
if (!this.supportStr.contains(baseX + "," + baseY)) {
this.supportStr += baseX + "," + baseY + ";";
}
System.out.println(supportStr);
File dir = new File(dirStr);
if (!dir.exists()) {
dir.mkdir();
}
System.out.println(dir.getAbsoluteFile());
}
private void generate() {
String[] vals = supportStr.split(";");
for (String val : vals) {
String[] wh = val.split(",");
generateXmlFile(Integer.parseInt(wh[0]), Integer.parseInt(wh[1]));
}
}
private void generateXmlFile(int w, int h) {
StringBuffer sbForWidth = new StringBuffer();
sbForWidth.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
sbForWidth.append("<resources>\n");
float cellw = w * 1.0f / baseW;
System.out.println("width : " + w + "," + baseW + "," + cellw);
for (int i = 0; i < baseW; i++) {
sbForWidth.append("\t" + W_TEMPLATE.replace("{0}", i + "").replace("{1}",
change(cellw * i) + ""));
}
sbForWidth.append("\t" + W_TEMPLATE.replace("{0}", baseW + "").replace("{1}",
w + ""));
/**-----------------------------開始----------------------------------------*/
//下面是生成負(fù)數(shù)部分,因為有時候會用到margin為負(fù)數(shù),需要的話直接打開注釋就可以
// for (int i = -1; i > -baseW; i--) {
// sbForWidth.append("\t" + W_TEMPLATE.replace("{0}", "f" + i * -1).replace("{1}",
// change(cellw * i) + ""));
// }
// sbForWidth.append("\t" + W_TEMPLATE.replace("{0}", "f" + baseW).replace("{1}",
// -w + ""));
//生成負(fù)數(shù)部分到這個結(jié)束
/**-----------------------------結(jié)束----------------------------------------*/
sbForWidth.append("</resources>");
File fileDir = new File(dirStr + File.separator
+ VALUE_TEMPLATE.replace("{height}", h + "")//
.replace("{width}", w + ""));
fileDir.mkdir();
File layxFile = new File(fileDir.getAbsolutePath(), "lay_x.xml");
try {
PrintWriter pw = new PrintWriter(new FileOutputStream(layxFile));
pw.print(sbForWidth.toString());
pw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private static float change(float a) {
int temp = (int) (a * 100);
return temp / 100f;
}
/**
* CreatValue方法的參數(shù)是基準(zhǔn)值
* @param args
*/
public static void main(String[] args) {
new CreatValue(1080, 1920).generate();
}
}