从事Android开发起,就在工作中就遇到了Android手机屏幕适配的问题,每次遇到都是查找资料解决,也没有进行回顾总结,最近又遇到了屏幕适配问题,我醒悟的发现,我必须进行一次屏幕适配总结了。
我相信从事Android开发的程序员,或多或少都接触过屏幕适配的问题,那大家清楚为什么要对Android的屏幕进行适配吗?原因是,Android的屏幕已经碎片化,屏幕尺寸这么多,为了让我们开发的程序能够比较美观的显示在不同尺寸、分辨率、像素密度的设备上,那就要在开发的过程中进行处理。至于如何去进行处理,这就是我们今天的主题了。开始主题之前,很多重要的关于屏幕适配的基础知识大家还是要有所准备的,不过我就不贴出来了,大家事先了解啦,可从这个网址去预习:http://www.cocoachina.com/android/20151030/13971.html,那么现在开始我的经验总结吧:
一、首先是一些特殊屏幕需要屏幕适配
我的这个特殊也不是很特殊了,只是说Android应用程序开发中,有的页面需要横竖屏切换,各个控件需要适配横竖屏的屏幕,同时也是说要满足各种屏幕尺寸,那我的方案一就是:
尽量恰当的使用wrap_content、match_parent、weight的布局属性,要确保布局的灵活性并适应各种尺寸的屏幕,应使用 “wrap_content” 和 “match_parent” 控制某些视图组件的宽度和高度。使用 “wrap_content”,系统就会将视图的宽度或高度设置成所需的最小尺寸以适应视图中的内容,而 “match_parent”(在低于API 级别8 的级别中称为 “fill_parent”)则会展开组件以匹配其父视图的尺寸。如果使用 “wrap_content” 和 “match_parent” 尺寸值而不是硬编码的尺寸,视图就会相应地仅使用自身所需的空间或展开以填满可用空间。此方法可让布局正确适应各种屏幕尺寸和屏幕方向。weight是线性布局的一个独特的属性,我们可以使用这个属性来按照比例对界面进行分配,完成一些特殊的需求。既然要用weight属性,当然布局的时候就要使用线性布局了。那么我们对于weight这个属性的计算应该如何理解呢?android:layout_weight的真实含义是:如果View设置了该属性并且有效,那么该View的宽度等于原有宽度(android:layout_width)加上剩余空间的占比。从这个角度我们来解释一下上面的现象。在上面的代码中,我们设置每个Button的宽度都是match_parent,假设屏幕宽度为L,那么每个Button的宽度也应该都为L,剩余宽度就等于L-(L+L)= -L。Button1的weight=1,剩余宽度占比为1/(1+2)= 1/3,所以最终宽度为L+1/3*(-L)=2/3L,Button2的计算类似,最终宽度为L+2/3(-L)=1/3L。这是在水平方向上的,那么在垂直方向上也是这样吗?答案是,也是一样的,这里我就不重复解释了。
那我的方案二就是:
使用官方提供的百分比布局,同时可借助第三方开源框架的增强版百分比布局。官方提供了android-percent-support这个库,这个库提供了两种布局供大家使用:PercentRelativeLayout、PercentFrameLayout,通过名字就可以看出,这是继承自FrameLayout和RelativeLayout两个容器类;支持的属性有:
layout_widthPercent、layout_heightPercent、
layout_marginPercent、layout_marginLeftPercent、
layout_marginTopPercent、layout_marginRightPercent、
layout_marginBottomPercent、layout_marginStartPercent、layout_marginEndPercent。
可以看到支持宽高,以及margin。
也就是说,大家只要在开发过程中使用PercentRelativeLayout、PercentFrameLayout替换FrameLayout、RelativeLayout即可。
是不是很简单,不过貌似没有LinearLayout,有人会说LinearLayout有weight属性呀。但是,weight属性只能支持一个方向呀~~哈,没事,刚好给我们一个机会去自定义一个PercentLinearLayout。有人说自定义我不会呀,或者太花时间呀,没关系,我们可以借助第三方开源库,这里我借助的是博主张鸿洋的:(我的水平有待提高,总是没创新,大家谅解下了)http://blog.csdn.net/lmj623565791/article/details/46695347。
1.首先是对官方百分比布局使用的讲解:
关于使用,其实及其简单,并且github上也有例子,android-percent-support-lib-sample。我们就简单过一下:首先在build.gradle添加依赖:compile 'com.android.support:percent:22.2.0',这里请注意:每个控件的百分比使用,都是针对直接的父布局来说的。然后就可以开始写布局了:
(一)PercentFrameLayout
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">
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_gravity="left|top"
android:background="#44ff0000"
android:text="width:30%,height:20%"
app:layout_heightPercent="20%"
android:gravity="center"
app:layout_widthPercent="30%"/>
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_gravity="right|top"
android:gravity="center"
android:background="#4400ff00"
android:text="width:70%,height:20%"
app:layout_heightPercent="20%"
app:layout_widthPercent="70%"/>
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_gravity="bottom"
android:background="#770000ff"
android:text="width:100%,height:10%"
android:gravity="center"
app:layout_heightPercent="10%"
app:layout_widthPercent="100%"/>
(二) 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"
android:clickable="true">
android:id="@+id/row_one_item_one"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_alignParentTop="true"
android:background="#7700ff00"
android:text="w:70%,h:20%"
android:gravity="center"
app:layout_heightPercent="20%"
app:layout_widthPercent="70%"/>
android:id="@+id/row_one_item_two"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_toRightOf="@+id/row_one_item_one"
android:background="#396190"
android:text="w:30%,h:20%"
app:layout_heightPercent="20%"
android:gravity="center"
app:layout_widthPercent="30%"/>
android:id="@+id/row_two_item_one"
android:layout_width="match_parent"
android:layout_height="0dp"
android:src="@drawable/tangyan"
android:scaleType="centerCrop"
android:layout_below="@+id/row_one_item_one"
android:background="#d89695"
app:layout_heightPercent="70%"/>
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_below="@id/row_two_item_one"
android:background="#770000ff"
android:gravity="center"
android:text="width:100%,height:10%"
app:layout_heightPercent="10%"
app:layout_widthPercent="100%"/>
就上面两个例子,图我就不上了,大家谅解谅解哈。
2.其次是第三方百分比布局使用的讲解:
这里我使用的是博主张鸿洋的框架,首先也是要添加依赖了,在build.gradle添加依赖:dependencies {
//...
compile 'com.zhy:percent-support-extends:1.0.1'
}
然后不需要导入官方的percent-support-lib了,提供了三个类:
com.zhy.android.percent.support.PercentLinearLayout
com.zhy.android.percent.support.PercentRelativeLayout
com.zhy.android.percent.support.PercentFrameLayout
对于eclipse的用户:github上自行下载源码,就几个类和一个attrs.xml,也可以在bintray.com/percent-support-extends下载相关文件。
下面是几个代码的具体使用示例:
Demo 1
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"
android:orientation="vertical">
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_gravity="center"
android:background="#ff44aacc"
app:layout_heightPercent="50%w"
app:layout_widthPercent="50%w">
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_gravity="center"
android:background="#ffcc5ec7"
app:layout_heightPercent="50%w"
app:layout_widthPercent="50%w">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="#ff7ecc16"
android:gravity="center"
android:text="margin 15% of w"
app:layout_marginPercent="15%w"
/>
android:layout_height="0dp"
android:layout_gravity="bottom|right"
android:background="#44ff0000"
android:gravity="center"
android:text="15%w,15%w"
app:layout_heightPercent="15%w"
app:layout_marginPercent="5%w"
app:layout_widthPercent="15%w"/>
Demo 2
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"
android:clickable="true">
android:id="@+id/row_one_item_one"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_alignParentTop="true"
android:background="#7700ff00"
android:text="w:70%,h:20%"
android:gravity="center"
app:layout_heightPercent="20%"
app:layout_widthPercent="70%"/>
android:id="@+id/row_one_item_two"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_toRightOf="@+id/row_one_item_one"
android:background="#396190"
android:text="w:30%,h:20%"
app:layout_heightPercent="20%"
android:gravity="center"
app:layout_widthPercent="30%"/>
android:id="@+id/row_two_item_one"
android:layout_width="match_parent"
android:layout_height="0dp"
android:src="@drawable/tangyan"
android:scaleType="centerCrop"
android:layout_below="@+id/row_one_item_one"
android:background="#d89695"
app:layout_heightPercent="70%"/>
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_below="@id/row_two_item_one"
android:background="#770000ff"
android:gravity="center"
android:text="width:100%,height:10%"
app:layout_heightPercent="10%"
app:layout_widthPercent="100%"/>
Demo 3
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">
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="wrap_content"
android:orientation="vertical">
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#ff44aacc"
android:gravity="center"
android:text="width:60%,height:5%,ts:3%"
android:textColor="#ffffff"
app:layout_heightPercent="5%"
app:layout_marginBottomPercent="5%"
app:layout_textSizePercent="3%"
app:layout_widthPercent="60%"/>
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#ff4400cc"
android:gravity="center"
android:text="width:70%,height:10%"
android:textColor="#ffffff"
app:layout_heightPercent="10%"
app:layout_marginBottomPercent="5%"
app:layout_widthPercent="70%"/>
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#ff44aacc"
android:gravity="center"
android:text="w:80%,h:15%,textSize:5%"
android:textColor="#ffffff"
app:layout_heightPercent="15%"
app:layout_marginBottomPercent="5%"
app:layout_textSizePercent="5%"
app:layout_widthPercent="80%"/>
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#ff4400cc"
android:gravity="center"
android:text="width:90%,height:5%"
android:textColor="#ffffff"
app:layout_heightPercent="20%"
app:layout_marginBottomPercent="5%"
app:layout_widthPercent="90%"/>
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#ff44aacc"
android:gravity="center"
android:text="width:100%,height:25%"
android:textColor="#ffffff"
app:layout_heightPercent="25%"
app:layout_marginBottomPercent="5%"
/>
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#ff44aacc"
android:gravity="center"
android:text="width:100%,height:30%"
android:textColor="#ffffff"
app:layout_heightPercent="30%"
app:layout_marginBottomPercent="5%"
/>
依然是我就不上图了啦,有点懒哈。使用的话对大家应该是不难的,大家自己去多试试啦。还有百分比布局的源码,不管是官方的还是张鸿洋博主的,我都不贴出来一一解释了,大家有时间的话就自己多看看啦。
二、对整个Android应用页面都需要进行屏幕适配
对于这种情况,我有做过两个大型的国有App应用项目,这两个国有项目用的方案都是自己引入百分比概念进行适配(我觉得那样做,是因为那时百分比布局还没有普及吧,我猜的)。我给出的方案就是结合上面两个项目方法,再结合一下自己的思路。我就不饶圈子啦,直接结合自己的经验,给出自己的解决方案:首先也是自己引入百分比概念,然后结合上面给出的的一、的情况进行结合来适配。
自己引入百分比概念:
1.在项目中针对你所需要适配的手机屏幕的分辨率各自新建一个文件夹。
2.然后我们选择一个基准,基准的意思就是:
比如选择480*320的分辨率为基准:
宽度为320,将任何分辨率的宽度分为320份,取值为x1-x320
高度为480,将任何分辨率的高度分为480份,取值为y1-y480
其他分辨率类似上面的分析~~
例如对于例如对于800*480的宽度480:它的份数就是x1=1.5px,x2=3px,x3=4.5px...以此类推。。。。
那么,你可能有个疑问,这么写有什么好处呢?
假设我现在需要在屏幕中心有个按钮,宽度和高度为我们屏幕宽度的1/2,我可以怎么编写布局文件呢?
android:layout_gravity="center"
android:gravity="center"
android:text="@string/hello_world"
android:layout_width="@dimen/x160"
android:layout_height="@dimen/x160"/>
可以看到我们的宽度和高度定义为x160,其实就是宽度的50%;不论在什么分辨率的机型,我们的按钮的宽和高始终是屏幕宽度的一半。
你可能又会问,这么多文件,难道我们要手算,然后自己编写?不会啦,有方法,我们可以用一个工具类自动生成,生成代码步骤如下:
1.分析需要的支持的分辨率
对于主流的分辨率我已经集成到了我们的程序中,当然对于特殊的,你可以通过参数指定。关于屏幕分辨率信息,可以通过该网站查询:http://screensiz.es/phone
2.根据分析编写自动生成文件的程序,代码如下:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
/**
* Created by zhy on 15/5/3.
*/
public class GenerateValueFiles {
private int baseW;
private int baseH;
private String dirStr = "./res";
private final static String WTemplate = "{1}px \n";
private final static String HTemplate = "{1}px \n";
/**
* {0}-HEIGHT
*/
private final static String VALUE_TEMPLATE = "values-{0}x{1}";
private static final String SUPPORT_DIMESION = "320,480;480,800;480,854;540,960;600,1024;720,1184;720,1196;720,1280;768,1024;800,1280;1080,1812;1080,1920;1440,2560;";
private String supportStr = SUPPORT_DIMESION;
public GenerateValueFiles(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;
* @return
*/
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("\n");
sbForWidth.append("");
float cellw = w * 1.0f / baseW;
System.out.println("width : " + w + "," + baseW + "," + cellw);
for (int i = 1; i < baseW; i++) {
sbForWidth.append(WTemplate.replace("{0}", i + "").replace("{1}",
change(cellw * i) + ""));
}
sbForWidth.append(WTemplate.replace("{0}", baseW + "").replace("{1}",
w + ""));
sbForWidth.append(" ");
StringBuffer sbForHeight = new StringBuffer();
sbForHeight.append("\n");
sbForHeight.append("");
float cellh = h *1.0f/ baseH;
System.out.println("height : "+ h + "," + baseH + "," + cellh);
for (int i = 1; i < baseH; i++) {
sbForHeight.append(HTemplate.replace("{0}", i + "").replace("{1}",
change(cellh * i) + ""));
}
sbForHeight.append(HTemplate.replace("{0}", baseH + "").replace("{1}",
h + ""));
sbForHeight.append(" ");
File fileDir = new File(dirStr + File.separator
+ VALUE_TEMPLATE.replace("{0}", h + "")//
.replace("{1}", w + ""));
fileDir.mkdir();
File layxFile = new File(fileDir.getAbsolutePath(), "lay_x.xml");
File layyFile = new File(fileDir.getAbsolutePath(), "lay_y.xml");
try {
PrintWriter pw = new PrintWriter(new FileOutputStream(layxFile));
pw.print(sbForWidth.toString());
pw.close();
pw = new PrintWriter(new FileOutputStream(layyFile));
pw.print(sbForHeight.toString());
pw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public static float change(float a) {
int temp = (int) (a * 100);
return temp / 100f;
}
public static void main(String[] args) {
int baseW = 320;
int baseH = 400;
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 GenerateValueFiles(baseW, baseH, addition).generate();
}
}
注:其实编写代码生成文件还是挺麻烦的,如果你比较懒的话,其实已经有大神制作了自动生成文件的jar包了,默认情况下,双击jar包即可生成,下载地址即使用说明可参考http://blog.csdn.net/lmj623565791/article/details/45460089
按照java -jar xx.jar width height width,height_width,height的格式即可生成我们需要的屏幕分辨率文件。到此,我们通过编写一个工具,根据某基准尺寸,生成所有需要适配分辨率的values文件,做到了编写布局文件时,可以参考屏幕的分辨率;在UI给出的设计图,可以快速的按照其标识的px单位进行编写布局。基本解决了适配的问题。
虽然这个适配方法还可以,但是相对于百分布局来说还是逊色一点,如果大家以后做屏幕适配,我还是建议大家更多的考虑使用百分比布局。而且像张鸿洋这样的博主大神,有自己的封装好的百分比布局框架开源,已经能很好的满足适配要求了。
最后来个面试题,看一下我的想法:
面试官:一个控件width设置了match_parent,height设置了100dp,在不同的手机上可能会出现适配比较差的情况,那你会怎么解决?
我:按照您说的情况,在当前手机上测试显示的效果是满足要求,但如果在屏幕更小、或屏幕更大的手机上显示的话height就会不理想。我想到就是要height占屏幕高的百分比,不管什么屏幕的话,height都能显示合理高度了。
面试官:怎么设置百分比呢
我:我首先想到的方案是用百分比布局,可以直接设置控件height占屏幕的高度。
第二,也可以针对现在比较流行的手机尺寸,分别创建对应的values文件夹,在对应创建dp和px转换的资源文件,然后在控件的height属性中引用对应的值就可以了。