安卓屏幕适配

Configuration

适配各种屏幕规格,首先要取到系统对于屏幕的配置信息,这些配置可从工具类Configuration获得。Configuration对象在Activity中通过调用getResources().getConfiguration()得到,该对象的常用属性说明如下:
touchscreen : 屏幕触摸方式。有下列几种取值定义:"未定义", "不支持触摸", "专用笔触摸", "支持手指触摸"
keyboard : 物理键盘样式。有下列几种取值定义:"未定义", "无物理键盘", "全键盘", "十二格键盘"
keyboardHidden : 键盘状态。有下列几种取值定义:"未定义", "未隐藏或软键盘", "已隐藏", "软键盘"
hardKeyboardHidden : 物理键盘状态。有下列几种取值定义:"未定义", "未隐藏", "已隐藏"
navigation : 方向控制样式。有下列几种取值定义:"未定义", "无方向控制", "方向键", "轨迹球", "滚轮"
navigationHidden : 方向控制状态。有下列几种取值定义:"未定义", "未隐藏", "已隐藏"
orientation : 屏幕方向。有下列几种取值定义:"未定义", "竖屏", "横屏"
以上属性除了屏幕方向是有用的,其他的基本没什么用。


如果属性发生变化,可重写onConfigurationChanged函数监测最新的属性值。但是由屏幕旋转导致的屏幕方向变化,按照生命周期走的是原方向onDestroy然后新方向onCreate,并不触发onConfigurationChanged方法,所以该方法基本也没机会用到。


适配竖屏/横屏

上面说到,竖屏/横屏的切换走的是Activity的生命周期流程,详细介绍参见《 Android开发笔记(三十九)Activity的生命周期 》。


要对一个页面分别适配竖屏与横屏,可在res目录下创建子目录“layout-land”,该目录放的是横屏时的布局文件,而原来的layout目录放的是默认即竖屏时的布局文件。app运行时,Android会根据当前的屏幕方向,自动选择对应目录下的布局。具体的代码例子参见《 Android开发笔记(六十七)嵌入页面的碎片 》,这里通过操作Fragment完成屏幕方向切换的适配。


适配竖屏与横屏的另一种方法,是在布局文件中采用ViewStub标签,此时无需新建layout目录,只要在代码中判断屏幕方向,从而选择合适的ViewStub标签加以显示。其实ViewStub标签也要指向不同的布局文件完成适配,该方法与新建layout目录的区别在于,新建layout方式是把选择布局操作交给Android系统完成,而ViewStub标签方式则是在app代码中自己完成。该方式的具体例子参见《 Android开发笔记(七十四)布局文件优化 》。


适配手机/平板

Android中没有明确区分手机和平板的方法,但我们可以根据某些参数来判断,具体方法如下:
1、从Configuration对象的screenLayout属性判断当前的屏幕规格,只要是大尺寸以上的都算平板;
2、从系统服务TELEPHONY_SERVICE中获得电话管理对象TelephonyManager,然后判断该对象的电话类型getPhoneType,不能打电话的都算平板(这个可能不准确,因为有的平板也能打电话;或者如果手机没插sim卡,那也不能打电话);
3、从系统服务WINDOW_SERVICE中获得窗口管理对象WindowManager,再由该对象获得屏幕的长和宽,据此算出屏幕对角线的长度,若结果大于六英寸,则认为是平板;


判断是否平板的三种实现代码示例如下:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. import android.content.Context;  
  2. import android.content.res.Configuration;  
  3. import android.telephony.TelephonyManager;  
  4. import android.util.DisplayMetrics;  
  5. import android.view.Display;  
  6. import android.view.WindowManager;  
  7.   
  8. public class TabletUtil {  
  9.   
  10.     //SCREENLAYOUT_SIZE_SMALL : 小尺寸  
  11.     //SCREENLAYOUT_SIZE_NORMAL : 正常尺寸  
  12.     //SCREENLAYOUT_SIZE_LARGE : 大尺寸  
  13.     //SCREENLAYOUT_SIZE_XLARGE : 超大尺寸  
  14.     public static boolean isTabletByLayout(Context ctx) {  
  15.         boolean bTablet = false;  
  16.         int sizeMode = ctx.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;  
  17.         if (sizeMode >= Configuration.SCREENLAYOUT_SIZE_LARGE) {  
  18.             bTablet = true;  
  19.         }  
  20.         return bTablet;  
  21.     }  
  22.   
  23.     //PHONE_TYPE_NONE : 无  
  24.     //PHONE_TYPE_GSM : GSM  
  25.     //PHONE_TYPE_CDMA : CDMA  
  26.     //PHONE_TYPE_SIP : SIP  
  27.     public static boolean isTabletByPhone(Context ctx) {  
  28.         boolean bTablet = false;  
  29.         TelephonyManager telephony = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE);  
  30.         int phoneType = telephony.getPhoneType();  
  31.         if (phoneType == TelephonyManager.PHONE_TYPE_NONE) {  
  32.             bTablet = true;  
  33.         }  
  34.         return bTablet;  
  35.     }  
  36.       
  37.     public static boolean isTabletByDisplay(Context ctx) {  
  38.         WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);  
  39.         Display display = wm.getDefaultDisplay();  
  40.         DisplayMetrics dm = new DisplayMetrics();  
  41.         display.getMetrics(dm);  
  42.         double x = Math.pow(dm.widthPixels / dm.xdpi, 2);  
  43.         double y = Math.pow(dm.heightPixels / dm.ydpi, 2);  
  44.         // 屏幕尺寸  
  45.         double screenInches = Math.sqrt(x + y);  
  46.         // 大于6尺寸则为Pad  
  47.         if (screenInches >= 6.0) {  
  48.             return true;  
  49.         }  
  50.         return false;  
  51.     }  
  52.           
  53. }  


适配不同屏幕尺寸

不同设备的屏幕尺寸有大有小,适配不同大小屏幕的方法也有三种:

1、在布局文件中,视图或控件的宽或高使用match_parent和wrap_content,或者设置layout_weight权重,由app自身在onMeasure测量方法中自行计算大小。测量尺寸的介绍参见

Android开发笔记(十二)测量尺寸与下拉刷新》。

尺寸测量的配置

控件宽和高的设置方式

大家知道,自定义视图的目的就是要在屏幕上显示期望的图案,那在绘制图案之前,我们得先知道这个图案的尺寸(如宽多少高多少)。
一般在xml中给控件的宽和高有三种赋值方式:
1、MATCH_PARENT : 表示与上级控件一样大小;
2、WRAP_CONTENT : 表示按照自身尺寸进行适配;
3、直接赋给具体的dp值;
方式3有具体的数值,不用计算就知道了。方式1与上级控件保持一致,因此只要系统依次丈量控件大小,这也不是什么难事。麻烦的是方式2,因为下级控件每个尺寸都有可能不确定,比如文本控件得看文字大小、行数,图像控件得看图片大小、拉伸情况,所以大家想想,如果这时候我们自己去一个个算过去(下级控件的个数也不确定),这算得头都大了。
幸亏Android提供了onMeasure函数自动完成了上述计算过程,通常情况下我们的自定义控件也无需重写该方法,除了一些特殊的情况。当然本文讲的便是实际开发中遇到的特殊情况,否则就不用浪费口舌了。


尺寸测量配置的三种模式

对应上面layout_width和layout_height的三种赋值方式,Android的视图底层也提供了三种测量模式,分别是:
1、MeasureSpec.AT_MOST : 表示达到最大,该常量的十进制数值为-2147483648,对应赋值方式的MATCH_PARENT;
2、MeasureSpec.UNSPECIFIED : 表示未指定(实际就是自适应),该常量的十进制数值为0,对应赋值方式的WRAP_CONTENT;
3、MeasureSpec.EXACTLY : 表示精确,该常量的十进制数值为1073741824,对应赋值方式的具体dp值;
围绕着这三种模式,衍生了MeasureSpec的相关方法,如getChildMeasureSpec、makeMeasureSpec、measure等等。废话少说,让我们直接切入正题,看看测量模式怎么用,以及用在哪里。


下拉刷新框架中的尺寸测量

许多APP都有下拉刷新的功能,比如下面这个图片是一种下拉刷新的展示框:

平时页面打开是没有这个下拉框的,只有用户在屏幕上用手指向下滑动时,才会拉出这个下拉框,然后APP响应下拉事件进行刷新处理。这期间我们需要获得下拉区域的高度,以便把整个页面下移一段距离,从而展现下拉框区域。等到刷新操作结束,整个页面再往上挪回原位,同时收回下拉框。
现在问题就是,刷新时,整个页面要下移多少dp?其实这个下移的距离就是下拉区域的高度,所以只要我们在代码中算出下拉区域的高度,就能够移动合适的距离了。
在Android规定的测量过程中,主要有三个步骤:
1、获得宽与高的测量模式;
2、按照测量模式进行丈量;
3、获得测量后的宽与高的大小;


获得宽与高的测量模式

首先取到目标视图的宽和高的取值,从布局参数中获取:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. ViewGroup.LayoutParams params = aViewObject.getLayoutParams();  
  2. int originWidth = params.width;  
  3. int originHeight = params.height;  

获取到的原始尺寸大小,为-1时表示MATCH_PARENT,为-2时表示WRAP_CONTENT,其余值表示具体数值。
接着按照原始尺寸去匹配测量模式,这里我们获取宽度模式采用了getChildMeasureSpec方法,获取高度模式采用了makeMeasureSpec方法。getChildMeasureSpec的好处是可以设置边距,并且按常规处理无需我们再分支处理;makeMeasureSpec则更灵活,像下拉刷新会不断更新下拉区域的实际高度,偏移量可能是负数统统需要重新适配,如果按照常规处理,非-1也非-2的负数被当作精确值就不会重新适配了。
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. int widthSpec = ViewGroup.getChildMeasureSpec(MeasureSpec.UNSPECIFIED, 0, params.width);  
  2. int heightSpec;  
  3. if (params.height <= 0) {  
  4.     heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);  
  5. else {  
  6.     heightSpec = MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY);  
  7. }  


按照测量模式进行丈量

实际丈量的方法很简单,直接使用上面计算得到的宽和高的模式,measure一下就可以了。当然要用一个视图的对象去操作measure
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. aViewObject.measure(widthSpec, heightSpec);  


获得测量后的宽与高的大小

这个也简单,获取宽度用getMeasuredWidth,获取高度用getMeasuredHeight
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. int realWidth = aViewObject.getMeasuredWidth();  
  2. int realHeight = aViewObject.getMeasuredHeight();  


下面是下拉刷新的效果图



PullToRefresh

说到下拉刷新,刚好介绍一下使用广泛的开源框架PullToRefresh,该框架支持ScrollView、ListView、GridView多种视图,也支持下拉刷新和上拉加载两种模式,可适用于多种场合。


PullToRefresh是个单独的工程,需做为库工程引入到开发者自己的工程。PullToRefresh对象常用的方法有:
setMode : 设置拉动模式。Mode.PULL_FROM_START表示从上往下拉(即下拉刷新),Mode.PULL_FROM_START表示从下往上拉(即上拉加载),Mode.BOTH表示既支持下拉刷新也支持上拉加载。
getLoadingLayoutProxy : 获取加载区域的对象。接着可调用该对象的如下方法:
--setPullLabel : 设置拉动时文本
--setReleaseLabel : 设置松开时的文本
--setRefreshingLabel : 设置刷新时的文本
--setLastUpdatedLabel : 设置无需更新时的文本
setOnRefreshListener : 设置刷新监听器。刷新监听器主要有OnRefreshListener和OnRefreshListener2两种,前者是普通刷新,需重写监听方法onRefresh;后者是双重刷新,需重写监听方法onPullDownToRefresh(下拉监听)、onPullUpToRefresh(上拉监听)。
getRefreshableView : 获取可刷新的视图对象,如ScrollView、ListView、GridView等等,接着方可调用视图对象的相应方法,如setAdapter等等。


下面是PullToRefresh的一个使用例子代码:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. PullToRefreshListView ptrl_hello = (PullToRefreshListView) findViewById(R.id.ptrl_hello);  
  2. //从下往上拉,上拉加载  
  3. //ptrl_hello.setMode(Mode.PULL_FROM_END);  
  4. ptrl_hello.setMode(Mode.BOTH); //试试如何区分下拉与上拉两个监听器  
  5. //设置下拉刷新的文字  
  6. ptrl_hello.getLoadingLayoutProxy().setLastUpdatedLabel("当前已是最新数据");  
  7. ptrl_hello.getLoadingLayoutProxy().setPullLabel("拉动标志");  
  8. ptrl_hello.getLoadingLayoutProxy().setRefreshingLabel("数据更新中...");  
  9. ptrl_hello.getLoadingLayoutProxy().setReleaseLabel("松开标志");  
  10. //设置上拉加载的文字  
  11. ptrl_hello.getLoadingLayoutProxy(false,true).setLastUpdatedLabel("当前已是最新数据");  
  12. ptrl_hello.getLoadingLayoutProxy(false,true).setPullLabel("拉动标志");  
  13. ptrl_hello.getLoadingLayoutProxy(false,true).setRefreshingLabel("数据更新中...");  
  14. ptrl_hello.getLoadingLayoutProxy(false,true).setReleaseLabel("松开标志");  
  15.   
  16. ptrl_hello.setOnRefreshListener(new OnRefreshListener2<ListView>() {  
  17.     @Override  
  18.     public void onPullDownToRefresh(PullToRefreshBase<ListView> refreshView) {  
  19.         Toast.makeText(FrameListActivity.this"列表视图在处理下拉刷新数据啦", Toast.LENGTH_LONG).show();  
  20.         ptrl_hello.onRefreshComplete();  
  21.     }  
  22.   
  23.     @Override  
  24.     public void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {  
  25.         Toast.makeText(FrameListActivity.this"列表视图在处理上拉加载数据啦", Toast.LENGTH_LONG).show();  
  26.         ptrl_hello.onRefreshComplete();  
  27.     }  
  28. });  
  29.   
  30. String[] starArray = {"水星""金星""地球""火星""木星""土星"};  
  31. ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,  
  32.         R.layout.spinner_item, starArray);  
  33. ptrl_hello.getRefreshableView().setAdapter(adapter);  


2、在代码中获得当前屏幕的分辨率,根据不同分辨率做不同处理。获取屏幕分辨率的详细说明参见《 Android开发笔记(三)屏幕分辨率 》。
3、参照drawable的处理方式,不同尺寸的图片放在不同的drawable目录下,详见《 Android开发笔记(七)初识Drawable 》。同样的,对于布局文件layout,我们也可以创建诸如“layout-mdpi”、“layout-hdpi”、“layout-xhdpi”等目录,分别存放中等分辨率、高分辨率、超高分辨率的布局文件,由Android在运行时自行选择最接近当前分辨率的布局。除了drawable和layout,dimens.xml中的维度定义也可区分不同的分辨率,具体做法就是创建“values-mdpi”、“values-hdpi”、“values-xhdpi”等目录,在这几个目录下分别保存已设置对应维度的dimens.xml。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值