Android屏幕适配小技巧sw<n>dp

来源:互联网 发布:飘零网络验证系统源码 编辑:程序博客网 时间:2024/05/17 03:59

仅供参考


最近做一个项目需要适配到不同的平板和手持设备上,在屏幕适配上遇到了一些问题,查了Android官方文档了解了一些技巧的,现在总结如下:

先解释几个概念:
1、dpi(dot per inch),即每英寸像素,所有的android设备都会被分成120(low),160(medium),240(high),320(xhigh)四种,后来随着市场上android设备越来越多,google官方又增加了213(Added in API level13),480(Added in API level16),640(Added in API level18),三种dpi。比如320*240分辨率的屏幕物理尺寸2英寸*1.5英寸,dpi=160;
2、dp或dip(density-independent pixel)逻辑密度计算单位,与像素的换算方式为px=dp*(dpi/160)。
 
 
在android3.2以前,所有的资源文件都有相应的xhdpi,hdpi,mdpi,ldpi四种文件来对应,android3.2以后,为了提供更精准的对布局文件的控制,可以通过为资源文件(res目录下文件)增加后缀来指定该文件夹里的xml布局文件或color.xml,string.xml是为哪种大小的屏幕使用。
第一种后缀:sw<N>dp,如layout-sw600dp, values-sw600dp
这里的sw代表smallwidth的意思,当你所有屏幕的最小宽度都大于600dp时,屏幕就会自动到带sw600dp后缀的资源文件里去寻找相关资源文件,这里的最小宽度是指屏幕宽高的较小值,每个屏幕都是固定的,不会随着屏幕横向纵向改变而改变。
第二种后缀w<N>dp 如layout-w600dp, values-w600dp
带这样后缀的资源文件的资源文件制定了屏幕宽度的大于Ndp的情况下使用该资源文件,但它和sw<N>dp不同的是,当屏幕横向纵向切换时,屏幕的宽度是变化的,以变化后的宽度来与N相比,看是否使用此资源文件下的资源。
第三种后缀h<N>dp 如layout-h600dp, values-h600dp

这个后缀的使用方式和w<N>dp一样,随着屏幕横纵向的变化,屏幕高度也会变化,根据变化后的高度值来判断是否使用h<N>dp ,但这种方式很少使用,因为屏幕在纵向上通常能够滚动导致长度变化,不像宽度那样基本固定,因为这个方法灵活性不是很好,google官方文档建议尽量少使用这种方式。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

做Android应用的人都知道,要一个apk适用多个不同的手机屏幕是很容易的,就是在项目的res文件夹下面有多套相关的资源文件。程序运行的时候,Android系统会根据当前设备的信息去加载不同文件夹下的资源文件。但是Android系统是怎么做到这一点的呢?上网上搜了一下,很少有这方便的介绍,只好自己研究下代码了。下面是我研究代码得到的结果(正确性有待确认),在这里分享一下。

 

这里以ICS上在Activity的onCreate()方法里面调用setContentView(int resourceID)为例,介绍一下系统如何根据我们的id(R.layout.xxxx)找到合适的layout文件进行解析加载:

如果你的res下面有三种不同的layout:layout, layout-sw480dp和 layout-sw600dp,这里的sw<N>dp表示这个layout文件夹下面的布局文件只有在设备短边的最小宽带为N时才加载。你的设备是800x480的分辨率,那么这个apk安装在你的设备上就会加载 layout-sw480dp里面的布局文件。下面是framework的java层调用链:

Activity.setContentView(int resourceID) -> PhoneWindow.setContentView(int resourceID) -> LayoutInflater.inflate(int resource, ViewGroup root) -> LayoutInflater.inflate(int resource, ViewGroup root, boolean attachToRoot) -> Resources.getLayout(int id) -> Resources.loadXmlResourceParser(int id, String type) -> Resources.getValue(int id, TypedValue outValue, boolean resolveRefs) -> AssetManager.getResourceValue(int ident, int density, TypedValue outValue, boolean resolveRefs) -> AssetManager.loadResourceValue(int ident, short density, TypedValue outValue, boolean resolve)

 

在上面的掉用链中:

1. 最后加载的是哪个xml是由Resources.getValue(int id, TypedValue outValue, boolean resolveRefs)调用完成之后的outValue.string决定的,因为outValue.string的值就是你的资源文件的具体路径,如:

  1) xxx/values/xxx.xml

  2) xxx/layout-sw600dp/xxx.xml

2. AssetManager.loadResourceValue()调的是frameworks/base/core/jni/android_util_AssetManager.cpp里面的native方法, 如何获得正确的outValue值,在native方法俩面主要有以下几步:

  1) 调用frameworks/base/libs/utils/ResourceTypes.cpp 的ResTable::getResource(),遍历所有资源文件

  2) 在ResTable::getResource()里面调用ResTable::getEntry()来确定资源文件来自哪个entry,即layout,或者layout-sw<N>dp,由此可见,ResTable::getEntry()是我们这个问题的关键

  3) 在ResTable::getEntry()里面:

    a) 首先获取本设备的configurion信息,屏幕分辨率,屏幕大小,locale,横竖屏等。

    b) 根据得到的本设备的configurion信息,过滤掉不适应本设备的entry,比如设备是800x480的,那么超过此分辨率的资源(例:layout-sw600dp)就要被过滤掉,实现在frameworks/base/include/utils/ResourceTypes.h中ResTable_config的match函数中

    c) 对过滤后的resource进行最佳适配,找到最符合的entry文件。因为之前已经将不符合的,即大分辨率的entry已经被过滤掉了,所以这里就找剩下的最大的就是最佳适配的。实现在frameworks/base/include/utils/ResourceTypes.h中ResTable_config的isBetterThan()函数中。

3. 我做了一个尝试,就是想让800x480分辨率的设备上的应用都加载 layout-sw600dp里面的资源文件。所以将上面b)步骤的frameworks/base/include/utils/ResourceTypes.h里面ResTable_config的match函数改动如下:

/*if (smallestScreenWidthDp != 0
              && smallestScreenWidthDp > settings.smallestScreenWidthDp){
          return false;
}*/
 
if (smallestScreenWidthDp != 0
              && smallestScreenWidthDp > 600) {
          returnfalse;
}

我将settings.smallestScreenWidthDp强制换成了600,这样的话,所有比600dp小的(包含600)在内的资源文件在做过滤时就被保留了下来,而c)步骤不做检查,只找最大的,所以layout-sw600dp就成了系统认为的“最合适”的资源问价了。

 

将重新编译frameworks/base/libs/utils/生成的lib库push到/system/libs下面,再重启手机,然后启动上述应用,就可以了看见程序加载的layout-sw600dp的ui了。


0 0
原创粉丝点击