android界面的布局十分简单,简单到不懂编程的人,也能借用android的布局面板快速设计出需要的用户界面。android的界面布局又十分复杂,既使是工任经验十分丰富的老鸟,也不敢保证他的布局在所有能运行的手机里显示都是完整的,一切都是因为屏幕的分辨率。分辨率决定了能够用来显示的像素的多少和图像文字的最终显示效果,在过去非常漫长的计算机发展历史上,我们都是通过操作像素来精心构建用户界面,我们努力的把用户窗口的位置和大小精确到每一像素,像素是一切显示的根源,窗口,图像,动画,最终都是转化为像素展示给客户,分辨率不固定,这个转化过程也变得不可预料,难以计算。由于这个原因,各种平台都会支持点阵位图,也会支持与设备无关的位图。android屏幕布局有三多,屏幕种类多,分辨率多,单位标准多,这使得分辨率这个问题就像物理学中的测不准定理一样让人很难准确的拿捏。这让传统的喜欢精确控制像素布局的人十分沮丧,难以忍受,不可适应,特别是习惯于严谨思维和精确构建代码的C程序员,我们常常不知道我们的程序到了用户手里会是什么样的显示,这种感觉总是让人不放心。于是在android平台上,推荐使用与设备无关的DIP作为解决方案。
软件设计目前处于一种多元化的时期,这得益智能手机的迅猛发展,廉价而高端的微芯片,成熟的微操作系统,使得智能手机能够提供和替代传统上需要高配置的电脑才能完成的很多业务。携带方便,功能强大,拥有接近无限的可扩展性,使得手机在某些场合成为笔记本,甚至台式机的当之无悔的替代品。从软件设计来看,这一时期的显著标志在于,win8支持了ARM架构,android支持了X86架构,ubuntu也有了自己的移动方案,做为软件生态基础的操作系统正在走向大整合,这预示着我们设计的程序有可能运行在320X480屏幕的嵌入式设备上或者移动终端上,也可能运行在分辨率几千X几千的高配置台机上。分辨率跨度大,终端类型繁多,传统的像素布局有着很大的局限性。这也许是android使用DIP的一个原因,通过一定的处理机制,对原始图像和控件进行一些放大和缩小的插值操作,以提供在不同显示分辨率下,尽量为终端用户提供接近统一的用户界面。这种改变是为了适应目前终端显示屏的巨大差异而提供的方案,虽然这种方案存在着巨大的缺陷,这种布局单位忽视了屏幕宽高的对比,使得某些情况下,我们的窗口或者图标会被拉伸到很难看的程度,并且也会出现和像素布局一样的显示不完全的问题,但他明显比像素的适应性更强,至少解决了当像素密度不同时,不会出现对比过于强烈的情况,所以我们应该抱着开放的态度学习总结,而不能一直坚持的使用像素布局,综合运用各种方法,以为各种终端提供适应性更强的显示方案。
一。Android屏幕的计量单位
px :是屏幕的像素点
in :英寸
mm:毫米
pt :磅,1/72英寸
dp :一个基于density的抽象单位,如果一个160dpi的屏幕,1dp=1px
dip :等同于dp
sp :同dp相似,但还会根据用户的字体大小偏好来缩放。
建议使用sp作为文本的单位,其它用dip
dip和px的关系:
QVGA: density=0.75; densityDpi=120; QVGA(240*320)
HVGA: density=1.0; densityDpi=160; HVGA(320*480)
VGA: density=1.0; densityDpi=160; VGA(480*640)
WVGA: density=1.5; densityDpi=240; WVGA(480*800)
WQVGA:density=2.0; densityDpi=120; WQVGA(240*400)
densityDip值表示每英寸有多少个显示点DIP,不同于像素,但和像素有个比率,就是density,每英寸屏幕里包含的像素数和DIP的比值就是density,当density等于1.0时,每英寸里包含的DPI点数和像素数是相等的。他们之间有如下的换算关系:
ensity = densityDpi/160;
pixs=dips * (densityDpi/160)=dips/density;
dips=(pixs*160)/densityDpi=pixs*density;
dp与px转换的方法:
public static int dip2px(Contextcontext, float dipValue){
finalfloatscale = context.getResources().getDisplayMetrics().density;
return(int)(dipValue * scale +0.5f);
}
public static int px2dip(Contextcontext, float pxValue){
finalfloatscale = context.getResource().getDisplayMetrics().density;
return(int)(pxValue / scale +0.5f);
}
android默认是使用density来匹配图像资源,
当屏幕density=2时,使用xhdpi标签的资源
当屏幕density=1.5时,使用hdpi标签的资源
当屏幕density=1时,使用mdpi标签的资源
当屏幕density=0.75时,使用ldpi标签的资源
需要注意的是,资源是使用density来区分的,而不是屏幕大小,大的屏,也可能使用mdpi的资源,小的高密屏,也可能使用xhdpi资源,这样是为了解决图像被拉伸和缩放后变形或者模糊的问题,小分辨率图片放在高密的屏幕中为显得很小,大分辨率的图片放在低密度的屏幕中,可能会有显示不全的问题。同时图片分辨率和屏幕不匹配时,系统为需要大量的额外内存来对图像进行拉伸和缩放,这些额外的开销,会降低程序运行的效率。给用户不好的使用体验。最关健是可能会引起内存溢出的异常,这是由android内存回收机制引起,对于大量图片使用的情况,发生的概率相当的高,目前没有有效办法解决。
手机density和DIP可以使用以下方法获取,两种方法,一种是getWindowManager获取,一种是通过getResources的获取,两种结果是一样的,但因为某些原因,可能会有差异,有些手机厂商会修改API,有些厂商无良厂商会让低端屏故意显示高端的数据,如果你获取的屏幕数据不太准确,也不必太过在意:
voidgetDefaultDisplayScreenSize()
{
DisplayMetricsdm = newDisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
intscreenWidthDip = dm.widthPixels;// 屏幕宽(dip,如:320dip)
intscreenHeightDip = dm.heightPixels;// 屏幕宽(dip,如:533dip)
density= dm.density;// 屏幕密度(像素比例:0.75/1.0/1.5/2.0)
intdensityDPI= dm.densityDpi;// 屏幕密度(每寸像素:120/160/240/320)
floatxdpi= dm.xdpi;
floatydpi= dm.ydpi;
w= dm.widthPixels;// 屏幕宽(px,如:480px)
h= dm.heightPixels;// 屏幕高(px,如:800px)
Log.e(TAG+ " DisplayMetrics(222)","screenWidthDip="+ screenWidthDip
+"; screenHeightDip="+ screenHeightDip);
Log.e(TAG+ " DisplayMetrics(222)","w="+ w
+"; h="+ h);
}
voidgetDefaultDisplayScreenDensityDPI()
{
DisplayMetricsdm = newDisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
density= dm.density;// 屏幕密度(像素比例:0.75/1.0/1.5/2.0)
densityDPI= dm.densityDpi;// 屏幕密度(每寸像素:120/160/240/320)
floatxdpi = dm.xdpi;
floatydpi = dm.ydpi;
Log.e(TAG+ " DefaultDisplay","xdpi="+ xdpi + "; ydpi="+ ydpi);
Log.e(TAG+ " DefaultDisplay","density="+ density +"; densityDPI="
+densityDPI);
}
voidgetDisplayMetricsScreenSize()
{
DisplayMetricsdm = newDisplayMetrics();
dm= getResources().getDisplayMetrics();
floatdensity = dm.density;// 屏幕密度(像素比例:0.75/1.0/1.5/2.0)
intdensityDPI = dm.densityDpi;// 屏幕密度(每寸像素:120/160/240/320)
floatxdpi = dm.xdpi;
floatydpi = dm.ydpi;
Log.e(TAG+ " DisplayMetrics","xdpi="+ xdpi + "; ydpi="+ ydpi);
Log.e(TAG+ " DisplayMetrics","density="+ density + "; densityDPI="
+densityDPI);
w= dm.widthPixels;// 屏幕宽(像素,如:480px)
h= dm.heightPixels;// 屏幕高(像素,如:800px)
Log.e(TAG+ " getDisplayMetricsScreenSize","w="+ w
+"; h="+ h);
}
voidgetDisplayMetricsScreenDensityDPI()
{
DisplayMetricsdm = newDisplayMetrics();
dm= getResources().getDisplayMetrics();
density= dm.density;// 屏幕密度(像素比例:0.75/1.0/1.5/2.0)
densityDPI= dm.densityDpi;// 屏幕密度(每寸像素:120/160/240/320)
floatxdpi = dm.xdpi;
floatydpi = dm.ydpi;
Log.e(TAG+ " DisplayMetrics","xdpi="+ xdpi + "; ydpi="+ ydpi);
Log.e(TAG+ " DisplayMetrics","density="+ density +"; densityDPI="
+densityDPI);
}
二。屏幕布局
其实我们的布局,是根据屏幕物理尺寸来区分的,使用的单位是英寸,为简单起见,Android把所有的屏幕大小分为四种尺寸:小,普通,大,超大(分别对应:small,normal, large, and extra large).
屏幕物理尺寸和资源并不是绝对一一对应,从上图来看,他们之间有不少的重叠区,这意味着小屏small因为屏幕密度也有可能使用MDPI的资源,LARGE的大屏也有可能使用MDPI的资源,这样会产生图像大小和屏幕比例失谳的问题,在一些情况下图像小的看不清楚或者有些情况下图像大的显示不完全,所以一般来说,资源之外,是需要为不同的屏设计不同的LAYOUT文件。解决资源文件的问题可以创建不同的资源文件夹,比如:drawable-hdpi,drawable-mdp,drawable-ldpi,drawable-xhdpi等,解决不同尺寸屏的布局同样可以使用不同的文件夹,比如:layout-small,layout-large,layout-xlarge,layout-normal等,当然,如果你使用的是android默认的控件和普通的常规布局比如GridLayout,LinearLayout,TableLayout来构建用户界面,一切都会变得相对简单,布局的适自应性强,在不同屏幕下也不容易出现误差。一般来说,出现误差错乱的地方主要集中在AbsoluteLayout和RelativeLayout,AbsoluteLayout在新版本的android已经不再提供为标准布局,可能在早期是为了兼容其他平台程序员的习惯,在新版本中,RelativeLayout全面替代了AbsoluteLayout布局,也就是DIP单位全面替代了象素单位的布局,虽然你仍然可以使用AbsoluteLayout,但使用他会让你需要花很大的力气去维护,这是不可取的。对于小范围的有针对的机型,可以偶一为之,要为大众机型开发,这不是很好的方案,而且RelativeLayout完全可以实现AbsoluteLayout的效果,并能提供比较好的兼容性和自适应性,所以没有必要为了一些习惯而不采用更好的方案。当你针对不同尺寸的屏幕使用不同的布局文件时,由于资源是使用密度来选择的,所以在某些layout中使用wrap_content属性,在小屏幕上可能会无法全部显示,这个时候就需要用固定的长度,并采用dp作为单位进行设置。
android新建一个工程早期默认的是LinearLayout布局,目前新的默认布局是RelativeLayout,其他的还有Grid,frame,Table,fragment等等,Linear和Grid,table,有很好的兼容性和自适应性,fragment主要是为了解决充分利用大屏幕的资源而提供的解决方案,可以为不同的客户端提供高质量的差异化服务。Grid和Table可以用来构建M*N类型的方阵布局比如虚拟键盘,九宫菜单,当然这种布局使用linear横的竖的相互嵌套,也是可以实现的,但使用Grid和Table会更简单的一些,毕竟嵌套的布局一是有层数限制,二是增加了处理器的负担,加载的效率会有所降低。Grid比table对于构建不规则的M*N方阵有优势,比如虑拟键盘,有些按键会比其他的按键大很大,会占用更多的位置,需要合并行合列,而table只能合并行。这些布局可以使用嵌套,但过多的嵌套层数会降低布局文件的解析效率,选择恰当的方式布局,有时也是必要的。RelativeLayout布局可以实现和绝对布局AbsoluteLayout几乎一致的效果,并且可以防止布局中出现的不完整显示,相对布局根据屏幕的密度,把像素换算成DIP,针对屏幕四个角,屏幕中心,屏幕四条边中点等几个关健点设置相对坐标,能提供比绝对布局再优异的自适应性。
提高自适应布局的一些有用方法:
1。不要使用像素PX,而是使用DiP。sp
2。尽量使用linear,grid,table等布局,这些的布局具有很好的兼容性,
3。对于不能使用计算获得精确位置的控件,尽量使用RelativeLayout布局,而不要使用AbsoluteLayout,指定的位置尽量是相对于屏幕中心,四个角,四条边中点,或者是相对另一个控件对齐
4。针对不同物理尺寸的屏使用不同的布局,可以使用layout-sw720dp,layout-w720dp,从而软件兼容更多的屏,为了精确控制某种屏的布局,可以使用layout-1280x720,
5。使用wrap-content和match-parent(api2.2之前使用fill-parent),即宽高根据内容调整以及伸展至父控件一致。而不是硬编码写死控件的大小。
6.使用最小宽度标识符,也就是layout-swaaaadp的方式。官方在3.2的新特性中特别申明了如下四种方式用于使用3.2版本的平板建议使用的布局方式,如下:
res/layout-sw600dp/main_activity.xml // 7英寸平板
res/layout-sw720dp/main_activity.xml //10英寸平板
res/layout-w600dp/main_activity.xml //根据宽度自适应
res/layout-sw600dp-w720dp/main_activity.xml //超宽的布局
7。如果你的屏幕实在特殊,你希望能使用传统的象素精确的布局,你可以自定义view类,在类里的onDraw方法里,根据客户手机屏幕宽高,计算每个字符的行高,行长,精确的自绘出所有控件,如下:
CanvastmpCanvas = newCanvas();
tmpCanvas.setBitmap(tmpBitmap);
Paintp1 = newPaint();
p1.setColor(Color.BLACK);
p1.setStyle(Style.FILL);
tmpCanvas.drawRect(0,0, w, h,p1);
//设置字体
if(!IMG_ENABLE){
Paintp = newPaint();
p.setColor(Color.GRAY);
p.setAntiAlias(true);
p.setStrokeWidth(1);
p.setStyle(Style.FILL);
p.setTextSize(26);
Typefacefont = Typeface.create("黑体",Typeface.BOLD);
p.setTypeface(font);
//显示时间
p.setTextSize(100);
SimpleDateFormatsdf = newSimpleDateFormat("HH:mm:ss");
Stringtime = sdf.format(newDate());
time= time.substring(0, time.length() - 3);
intfw = (int)p.measureText(time);
FontMetricsfm = p.getFontMetrics();
intfh =(int)Math.ceil(fm.descent- fm.top) +2;
if(m_call_count> 0 || m_sms_count> 0 || mBatteryLevel<= 10) {
tmpCanvas.drawText(time,(w - fw) /2 - 40, 120, p);// 200
}else{
tmpCanvas.drawText(time,(w - fw) /2, 120, p);// 200
}
p.measureText(time);
//显示星期
sdf= newSimpleDateFormat("EEEE");
Stringweek = sdf.format(newDate());
//tmpCanvas.drawText(week, 100, 200, p);
//显示日期
p.setTextSize(30);
sdf= newSimpleDateFormat("yyyy-MM-dd");
Stringdate = sdf.format(newDate());
date= date.substring(5, date.length());
date= date + " "+ week;
fw= (int)p.measureText(date);
if(m_call_count> 0 || m_sms_count> 0 || mBatteryLevel<= 10) {
tmpCanvas.drawText(date,(w - fw) /2 - 40, 100 + 80, p);// 200
}else{
tmpCanvas.drawText(date,(w - fw) /2, 100 + 80, p);// 200
}
Typefacefont1 = Typeface.create("黑体",Typeface.NORMAL);
p.setTextSize(26);
p.setTypeface(font1);
//显示未接电话
//fw =(int)p.measureText(time);
//FontMetrics fm= p.getFontMetrics();
//fh =(int)Math.ceil(fm.descent- fm.top) + 2;
Bitmapstatus_bm = null;
status_bm= getImageFromAssetsFile("missedCall.png");
if(m_call_count> 0) {
tmpCanvas.drawBitmap(status_bm,(w - fw) /2 + fw + 50, 55,
p);
tmpCanvas.drawText("" + String.valueOf(m_call_count),
(w- fw) / 2 + fw + 80, 78, p);
}
//显示未读短信
BitmapmissedMessage_bm = null;
missedMessage_bm= getImageFromAssetsFile("missedMessage.png");
if(m_sms_count> 0) {
tmpCanvas.drawBitmap(missedMessage_bm,(w - fw) /2 + fw
+50, 105, p);
tmpCanvas.drawText("" + String.valueOf(m_sms_count),
(w- fw) / 2 + fw + 80, 127, p);
}
//显示电量
Bitmapbattery_bm = null;
battery_bm= getImageFromAssetsFile("battery_charge.png");
if(mBatteryLevel<= 10 && mBatteryLevel>= 0) {
tmpCanvas.drawBitmap(battery_bm,(w - fw) /2 + fw + 50,
150,p);
}
//tmpCanvas.drawText(String.valueOf(mBatteryLevel), (w -
//fw)/2+fw+80,168,p);
}else{
drawDate(tmpCanvas,100, 500);
drawTime(tmpCanvas,100, 400);
drawWeek(tmpCanvas,300, 500);
}
canvas.drawBitmap(tmpBitmap,0, 0, null);
this.invalidate();
}
注,布局匹配规则:
一,首先会去匹配对应的屏,如layout-1280x720
二。首先去匹配layout-xlarge,接着是layout-large,然后是layout-normal,最后是layout-small。上述中的任一一个可以匹配成功,布局文件就匹配成功。
三。如果匹配未成功,就会去找layout-swadp这个文件,如果可以找到adp能比屏幕宽度小的,就匹配成功。即使你的屏幕是:1280x800,如果layout中有layout-sw1dp,且第一个条件未被匹配,而layout-swadp又只有这一个,那么此时,不管其他还有什么布局文件,layout-sw1dp都会去匹配,即使你的布局文件中有layout-1280x800。
四。如果上面两中情况都未匹配到,此时就要分版本来匹配layout-axb文件了,这个没怎么用过,不太懂
五。有时你可以在activity的onCreate函数中,通过获取的屏幕宽高重新校正控件位置和大小,以获得更好的显示效果:
如:
//取得time控件的布局LayoutParams参数
RelativeLayout.LayoutParamstime_params = (RelativeLayout.LayoutParams) time
.getLayoutParams();
//取消左对齐属性
time_params.addRule(RelativeLayout.ALIGN_PARENT_LEFT,0);
//设置居中对齐
time_params.addRule(RelativeLayout.CENTER_HORIZONTAL,
RelativeLayout.TRUE);
//设置大小
time_params.height=200;
time_params.width=100;
time.setLayoutParams(time_params);
三。屏幕布局常用的控件属性介绍:
android:id
为控件指定相应的ID
android:text
指定控件当中显示的文字,需要注意的是,这里尽量使用strings.xml文件当中的字符串
android:gravity
指定View组件的对齐方式,比如说居中,居右等位置这里指的是控件中的文本位置并不是控件本身
android:layout_gravity(区别于:android:gravity )
指定Container组件的对齐方式.比如一个button在linearlayout里,你想把该button放在靠左、靠右等位置就可以通过该属性设置.以button为例,android:layout_gravity="right"则button靠右
android:textSize
指定控件当中字体的大小
android:background
指定该控件所使用的背景色,RGB命名法
android:width
指定控件的宽度
android:height
指定控件的高度
android:layout_width
指定Container组件的宽度
android:layout_height
指定Container组件的高度
android:layout_weight
View中很重要的属性,按比例划分空间
android:padding*
指定控件的内边距,也就是说控件当中的内容
android:sigleLine
如果设置为真的话,则控件的内容在同一行中进行显示
android:scaleType
是控制图片如何resized/moved来匹对ImageView的size
android:layout_centerHrizontal
水平居中
android:layout_centerVertical
垂直居中
android:layout_centerInparent
相对于父元素完全居中
android:layout_alignParentBottom
贴紧父元素的下边缘
android:layout_alignParentLeft
贴紧父元素的左边缘
android:layout_alignParentRight
贴紧父元素的右边缘
android:layout_alignParentTop
贴紧父元素的上边缘
android:layout_alignWithParentIfMissing
如果对应的兄弟元素找不到的话就以父元素做参照物
android:layout_below
在某元素的下方
android:layout_above
在某元素的的上方
android:layout_toLeftOf
在某元素的左边
android:layout_toRightOf
在某元素的右边
android:layout_alignTop
本元素的上边缘和某元素的的上边缘对齐
android:layout_alignLeft
本元素的左边缘和某元素的的左边缘对齐
android:layout_alignBottom
本元素的下边缘和某元素的的下边缘对齐
android:layout_alignRight
本元素的右边缘和某元素的的右边缘对齐
android:layout_marginBottom
离某元素底边缘的距离
android:layout_marginLeft
离某元素左边缘的距离
android:layout_marginRight
离某元素右边缘的距离
android:layout_marginTop
离某元素上边缘的距离
android:paddingLeft
本元素内容离本元素右边缘的距离
android:paddingRight
本元素内容离本元素上边缘的距离
android:hint
设置EditText为空时输入框内的提示信息
android:LinearLayout
它确定了LinearLayout的方向,其值可以为vertical,表示垂直布局horizontal,表示水平布局
如下一些常见屏:
不同densityDpi下屏幕分辨率信息,以480dip*800dip的WVGA(density=240)为例
densityDpi=120时
屏幕实际分辨率为240px*400px
状态栏和标题栏高各19px或者25dip
横屏是屏幕宽度400px或者800dip,工作区域高度211px或者480dip
竖屏时屏幕宽度240px或者480dip,工作区域高度381px或者775dip
densityDpi=160时
屏幕实际分辨率为320px*533px
状态栏和标题栏高个25px或者25dip
横屏是屏幕宽度533px或者800dip,工作区域高度295px或者480dip
竖屏时屏幕宽度320px或者480dip,工作区域高度508px或者775dip
densityDpi=240时
屏幕实际分辨率为480px*800px
状态栏和标题栏高个38px或者25dip
横屏是屏幕宽度800px或者800dip,工作区域高度442px或者480dip
竖屏时屏幕宽度480px或者480dip,工作区域高度762px或者775dip
不加任何标签的资源是各种分辨率情况下共用的
布局时尽量使用单位dip,少使用px
VGA : 640*480
QVGA : 320*240
HVGA : 320*480
WVGA : 800*480
WQVGA: 480X272或400X240
分辨率(水平数×垂直数) 类型 比例 88×72 QQCIF 11:9 128×96 SUB-QCIF 4:3 128×128 知道的补上 1:1 160×120 QQVGA 4:3 176×144 QCIF 11:9 208×176 Sub-QVGA- 13:11 220×176 Sub-QVGA 5:4 240×176 Sub-QVGA+ 15:11 320×200 CGA 16:10 320×240 QVGA 4:3 352×288 CIF 11:9 640×360 nHD 4:3 400×240 WQVGA 5:3 400×320 WQVGA 5:4 480×240 WQVGA 2:1 480×272 WQVGA 16:9 480×320 HQVGA 3:2 640×480 VGA 4:3 640×350 EGA 64:35 720×480 VGA+ 3:2 768×576 PAL 800×480 WVGA 5:3 854×480 FWVGA 16:9 800×600 SVGA 4:3 960×540 QHD 16:9 960×640 DVGA 3:2 1024×600 WSVGA 128:75 1024×768 XGA 4:3 1280×768 WXGA 15:9 1280×800 WXGA 16:10 1280×960 UxGA/XVGA 4:3 1280×1024 SXGA 25:16 1400×1050 SXGA+ 4:3 1440×900 WXGA+ 16:10 1600×1024 WSXGA 25:16 1600×1050 WSXGA 32:21 1600×1200 USVGA/UXGA/UGA 4:3 1680×1050 WSXGA+ 16:10 1900×1200 UXGA 19:12 1920×1080 WSUVGA+(WSUGA/HDTV) 4:3 1920×1200 WUXGA 16:10 2048×1536 SUVGA(QXGA) 4:3 2560×1600 UWXGA 16:10 2560×2048 USXGA 5:4 3200×2400 QUXGA 4:3 3840×2400 WQUXGA 16:10
参考:
http://blog.csdn.net/sunboy_2050/article/details/6688303
http://blog.csdn.net/yanbin1079415046/article/details/7879314
http://blog.csdn.net/macong01/article/details/7334607
http://www.cnblogs.com/nuliniaoboke/archive/2012/10/22/2733955.html