有关UI适配的屏幕相关概念很多,如分辨率、density、dpi、dip、dp、sp和px。
孤立地去看这些概念有点难理解,因此我找了3台不同的android设备,希望通过实物,以及一个UI适配的实例去学习这些知识点。
首先,可以使用adb查看屏幕分辨率和屏密度
wm size //查看屏幕分辨率
wm density //查看屏密度
以下3台设备屏幕分辨率和密度分别如下
1.1280x800,160
2.720x1280,320
3.1080x1920,480
先大致看一下相关名词的解释,这样看很难看懂。
dip : 英文density-independent pixel的缩写,意为密度无关像素。
dp :就是dip
px : 像素
dpi :dots per inch , 直接来说就是一英寸多少个像素点。常见取值 120,160,240。
density :缩放因子density。常见取值 1.5 , 1.0 。
sp :英文scale-independent pixel的缩写,意为缩放无关像素。它是一种与密度无关的像素。
分辨率 : 横纵2个方向的像素点的数量,常见取值 480X800 ,320X480
屏幕尺寸: 屏幕对角线的长度。电脑电视同理。
屏幕比例的问题。因为只确定了对角线长,2边长度还不一定。所以有了4:3、16:9这种,这样就可以算出屏幕边长了。
粗略看了一下相关概念后,再来新建一个工程,在activity中加入以下代码。
float density = getResources().getDisplayMetrics().density;
int densityDpi = getResources().getDisplayMetrics().densityDpi;
//获取的像素宽高包含虚拟键所占空间
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getRealMetrics(dm);
int screenWidth = dm.widthPixels;
int screenHeight = dm.heightPixels;
//获取的像素宽高不包含虚拟键所占空间
DisplayMetrics dm1 = getResources().getDisplayMetrics();
int width= dm1.widthPixels;
int height= dm1.heightPixels;
Log.d(TAG,"density = " + density + ", densityDpi = " + densityDpi + " screenWidth = " + screenWidth
+ " screenHeight = " + screenHeight + " width = " + width + " height = " + height);
在3台不同设备下运行结果如下:
设备1
06-26 11:36:35.789 2014-2014/com.demo.myapplication D/MainActivity: density = 1.0, densityDpi = 160 screenWidth = 1280 screenHeight = 800 width = 1280 height = 752
设备2
2019-06-26 11:41:54.135 31444-31444/com.demo.myapplication D/MainActivity: density = 2.0, densityDpi = 320 screenWidth = 720 screenHeight = 1280 width = 720 height = 1280
设备3
2019-06-26 11:51:07.665 4530-4530/com.demo.myapplication D/MainActivity: density = 3.0, densityDpi = 480 screenWidth = 1080 screenHeight = 1920 width = 1080 height = 1920
DisplayMetrics类中注释如下。
package android.util;
import android.os.SystemProperties;
public class DisplayMetrics {
/**
* The logical density of the display. This is a scaling factor for the
* Density Independent Pixel unit, where one DIP is one pixel on an
* approximately 160 dpi screen (for example a 240x320, 1.5"x2" screen),
* providing the baseline of the system's display. Thus on a 160dpi screen
* this density value will be 1; on a 120 dpi screen it would be .75; etc.
*
* <p>This value does not exactly follow the real screen size (as given by
* {@link #xdpi} and {@link #ydpi}, but rather is used to scale the size of
* the overall UI in steps based on gross changes in the display dpi. For
* example, a 240x320 screen will have a density of 1 even if its width is
* 1.8", 1.3", etc. However, if the screen resolution is increased to
* 320x480 but the screen size remained 1.5"x2" then the density would be
* increased (probably to 1.5).
*
* @see #DENSITY_DEFAULT
*/
public float density;
/**
* The screen density expressed as dots-per-inch. May be either
* {@link #DENSITY_LOW}, {@link #DENSITY_MEDIUM}, or {@link #DENSITY_HIGH}.
*/
public int densityDpi;
}
/**
* The absolute width of the available display size in pixels.
*/
public int widthPixels;
/**
* The absolute height of the available display size in pixels.
*/
public int heightPixels;
下面来仔细想一下分辨率、density、dpi、dip、dp和px这些定义。
1.wm density 获取的是densityDpi 。数值为160,320,480,这个就android中定义的dpi。
2.wm size 获取的就是 widthPixels和heightPixels。注意,这里的代码获取的是2种widthPixels和heightPixels,输入wm size 命令后获取的数据与getRealMetrics获取的数据一致,因此wm size获取的数据为包含底部虚拟键的数据。3台设备的分辨率分别为1280x800,720x1280,1080x1920,这个就是分辨率。我手上3台设备,1台有底部虚拟键,其余2台无底部虚拟键。
3.通过 getResources().getDisplayMetrics().density 获取的density,数值分别为1.0,2.0, 3.0。
density计算公式为density = dpi / 160。
即160/ 160,320/ 160,480/ 160 后分别得到1.0,2.0, 3.0。
这里可以再验证一下,在有root权限的情况下,可以临时设置一下densityDpi这个数值。例如我设置为200,此时再次运行程序,结果为density = 1.25(即200/160), densityDpi = 200,可以看到,这里在改变dpi的情况下,屏幕分辨率等数值是没有改变,density发生了改变。
输入
wm density 200
结果
Physical density: 160
Override density: 200
输出Log如下
06-26 14:32:20.819 2014-2014/com.demo.myapplication D/MainActivity: density = 1.25, densityDpi = 200 screenWidth = 1280 screenHeight = 800 width = 1280 height = 752
4.px
像素单位,图的尺寸单位,通常说的屏幕分辨率800*400,都是以px为单位。就简单记住是个单位吧。
可以简单参考这篇bitmap简单学习记录中的光栅图像部分。
更详细的可以去看计算机图形学相关的书籍。
5.dp/dip
dip即为dp。 虚拟像素单位。 Density Independent Pixels的缩写,以160dpi为基准。在160dpi设备 上,density为1,1dp=1px,在240dpi设备上,density为1.5,1dp=1.5px, 1 dp = density px , 以此类推。
Google公司为了解决分辨率过多的问题,在Android的开发文档中定义了px、dp、sp,方便开发者适配不同分辨率的Android设备。简单来说,dp是android定义的一种单位。例如给一个TextView控件定义大小,就可以用dp修饰。
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:text="Hello World!"/>
dp与px换算公式
px=dp*density
dp=px/density
当前设备 density =1.0,因此 100dp *1.0 = 100px。
- sp
scale-independent pixels(缩放无关像素)。
安卓开发用的字体大小单位。
它和dp很相似,但唯一的区别在于,Android系统允许用户自定义文字尺寸大小(小,正常,大,超大等),当文字尺寸是“正常”时,1sp=1dp=0.00625inch(英寸),当文字尺寸是“大”或“超大”时,1sp>1dp=0.00625inch (1inch = 0.0254m =2.54cm)
sp通常用来修饰textSize,即控件的字体大小。如下所示。
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:text="Hello World!"
android:textSize="20sp"/>
sp与px换算公式:
px=sp*density
sp=px/density
这里应该是文字尺寸是“正常”的情况下的换算公式,别的大小情况下暂时不考虑。
当前设备 density =1.0,因此 100sp *1.0 = 100px。
到了这里对以上6个名词有了一定的概念后,下面来看看实际使用需要面的的问题。
android中UI适配需要考虑的有2点:控件属性和横竖屏。
先以简单的基本的ImageView和TextView控件为例来进行思考。
首先来看ImageView。
ImageView的属性主要涉及到layout_width、layout_height以及drawable。
以android没有定义dp为前提来看直接使用px在不同设备上显示图片会导致的问题。
随便找的一个图片。
查看其属性,找到其尺寸大小。
图片大小为790 x 1822 px。
图片的px大小是固定的。
写一个ImageView控件如下所示。android:layout_width 和android:layout_width单位设置为px,并且将尺寸缩放一下,除以3再约等于一下,防止图片显示不全。
<ImageView
android:layout_width="280px"
android:layout_height="610px"
android:src="@drawable/test"/>
在3台设备上运行,结果分别如下。
显然,仅仅使用px想满足不同设备统一显示效果的需求,会比较难写。
下面来看看如何使用dp去适配。
如果使用dp的话,android中的dp在渲染前会将dp转为px。
我手上3个设备。density分别为1.0,2.0, 3.0。要想在3台设备上显示效果一样。
下面来看要如何实现在3台设备上正常显示图片,先不看目前普遍流行的例如今日头条UI适配法,sw适配做法等,来看看按最初的做法,应该如何去做适配。有时候一味依赖框架,反而会忘记基础。
android中屏幕尺寸和屏幕密度定义如下。
屏幕尺寸分为:small,normal,large,xlarge分别表示小,中,大,超大屏
屏幕密度分为:ldpi,mdpi,hdpi,xhdpi,它们的标准值分别是:120dpi,160dpi,240dpi,320dpi。
密度 dpi范围
ldpi(低) ~120dpi
mdpi(中) ~160dpi
hdpi(高) ~240dpi
xhdpi(超高) ~320dpi
xxhdpi(超超高) ~480dpi
xxxhdpi(超超超高) ~640dpi
放大倍数(即缩放因子density)如下
密度 放大倍数
ldpi 0.75
mdpi 1.0
hdpi 1.5
xhdpi 2.0
xxhdpi 3.0
xxxhdpi 4.0
以下部分出处:
https://developer.android.com/training/multiscreen/screendensities?hl=zh-CN
由于运行 Android 的设备具有多种屏幕密度,您应始终提供能够根据各种通用密度级别(低密度、中密度、高密度和超高密度)进行定制的位图资源。这有助于您在所有屏幕密度上获得良好的图形质量和性能。
如需生成这些图像,您应以矢量格式的原始资源为基础,按以下尺寸缩放比例生成每种屏幕密度对应的图像:
xhdpi:2.0
hdpi:1.5
mdpi:1.0(基准)
ldpi:0.75
这意味着,如果您为 xhdpi 设备生成了一幅 200x200 的图像,则应分别按 150x150、100x100 和 75x75 图像密度为 hdpi 设备、mdpi 设备和 ldpi 设备生成同一资源。
然后,将生成的图片文件置于 res/ 下的相应子目录中,系统将自动根据运行您的应用的设备的屏幕密度选取正确的文件:
MyProject/
res/
drawable-xhdpi/
awesomeimage.png
drawable-hdpi/
awesomeimage.png
drawable-mdpi/
awesomeimage.png
drawable-ldpi/
awesomeimage.png
之后,每当您引用 @drawable/awesomeimage 时,系统便会根据屏幕 dpi 选择相应的位图。
就是说,我手上有一个图片,像素大小为200x200 px(没有就打开windows自带画图软件新建一个)。
然后点击调整大小
生成后,添加一个文本,就写xhdpi 200x200了,然后保存,如下所示。
并且分别在drawable-hdpi、drawable-ldpi、drawable-mdpi和drawable-xxhdpi等目录下新建文字内容不同但是名称都为test的图片。
ImageView的android:layout_width和android:layout_height就都写200dp,因为设备2的高度是1280,density是2.0,200dp的话在该设备上就是400px,差不多占3分之一,肉眼可见度高。
layout文件如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/colorPrimary"
android:gravity="center">
<ImageView
android:id="@+id/test_image_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:src="@drawable/test"/>
</LinearLayout>
运行程序。
在density为1.0的设备上运行结果如下:
在density为2.0的设备上运行结果如下:
在density为3.0的设备上运行结果如下:
由于设备1为横屏,设备2,3位竖屏,因此这里是在高度上显示效果一致(基本高度上占除去导航栏和状态栏后屏高的三分之一),在宽度上显示效果不一致。
一般来说横屏的UI适配是需要新建一个layout布局文件的。
//以下文字部分出自android编程权威指南
创建水平模式布局具体步骤如下:
在项目工具窗口中,右键单击res目录后选择New → Android resource directory菜单项。创建资源目录界面列出了资源类型及其对应的资源特征。从资源类型(Resource type)列表中选择layout,保持Source Set的main选项不变。接下来选中待选资源特征列表中的Orientation,
然后单击>>按钮将其移动至已选资源特征区域。
将activity_main.xml文件从res/layout目录复制至res/layout-land目录。现在我们有了一个水平模式布局以及一个默认布局(竖直模式)。注意,两个布局文件必须具有相同的文件名,这样它们才能以同一个资源ID被引用。
通常来说,横屏和竖屏UI从设计上就不太一样,需要改变排版之类的东西。
为了与默认的布局文件相区别,我们简单修改一个水平模式布局文件,把layout-land下的activity_main.xml中的LinearLayout下的 android:gravity="center"去掉,我这里假设横屏的UI设计就是这样设计的,最后文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/colorPrimary">
<ImageView
android:id="@+id/test_image_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:src="@drawable/test"/>
</LinearLayout>
此时在3台设备上运行,可以发现仅仅设备1上运行结果发生了改变(因为设备1是横屏,设备2和3都是竖屏,android会自动适配land下的xml文件,如果有的话)。
//写到这里其实我已经断断续续花了1天多的时间。
到这里为止,应该对drawable的适配有了一定的了解了。
drawable适配流程总结可以参考以下部分。
以下部分出处:
玩转Android drawable图片适配
https://blog.csdn.net/myoungmeng/article/details/54090891
Android系统适配原则
Android为了更好地优化应用在不同屏幕密度下的用户体验,在项目的res目录下可以创建drawab-[density](density为6种通用密度名)目录,开发者在进行APP开发时,针对不同的屏幕密度,将图片放置于对应的drawable-[density]目录,Android系统会依据特定的原则来查找各drawable目录下的图片。
查找流程为:
1. 先查找和屏幕密度最匹配的文件夹。如当前设备屏幕密度dpi为160,则会优先查找drawable-mdpi目录;如果设备屏幕密度dpi为420,则会优先查找drawable-xxhdpi目录。
2. 如果在最匹配的目录没有找到对应图片,就会向更高密度的目录查找,直到没有更高密度的目录。例如,在最匹配的目录drawable-mdpi中没有查找到,就会查找drawable-hdpi目录,如果还没有查找到,就会查找drawable-xhdpi目录,直到没有更高密度的drawable-[density]目录。
3. 如果一直往高密度目录均没有查找,Android就会查找drawable-nodpi目录。drawable-nodpi目录中的资源适用于所有密度的设备,不管当前屏幕的密度如何,系统都不会缩放此目录中的资源。因此,对于永远不希望系统缩放的资源,最简单的方法就是放在此目录中;同时,放在该目录中的资源最好不要再放到其他drawable目录下了,避免得到非预期的效果。
4. 如果在drawable-nodpi目录也没有查找到,系统就会向比最匹配目录密度低的目录依次查找,直到没有更低密度的目录。例如,最匹配目录是xxhdpi,更高密度的目录和nodpi目录查找不到后,就会依次查找drawable-xhdp、drawable-hdpi、drawable-mdpi、drawable-ldpi。
举个例子,假如当前设备的dpi是320,系统会优先去drawable-xhdpi目录查找,如果找不到,会依次查找xxhdpi → xxxhdpi → hdpi → mdpi → ldpi。对于不存在的drawable-[density]目录直接跳过,中间任一目录查找到资源,则停止本次查找。
总结一下图片查找过程:优先匹配最适合的图片→查找密度高的目录(升序)→查找密度低的目录(降序)。
看完ImageView,接下来来看TextView。
TextView的基本属性有 text,textSize,layout_width和layout_height。
为方便观察,先给TextView增加一个外框。
通过shape来设置背景图片
首先一个textview_border.xml文件放在drawable文件夹里面
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="#ffffff" />
<stroke android:width="1dip" android:color="#4fa5d5"/>
</shape>
为要添加边框的TextView添加一个background
android:background="@drawable/textview_border"
如果不考虑margin和padding等属性或与其他控件同时使用的情况的时候,TextView自身的显示情况,与text,textSize,layout_width和layout_height等有关。
这里先不考虑layout_width和layout_height变化的情况。在layout_width和layout_height以及text一定的情况下,需要控制android:textSize去适应屏幕。
layout中定义如下。
<TextView
android:text="test1"
android:layout_width="150dp"
android:layout_height="150dp"
android:textSize="50px"
android:gravity="center"
android:background="@drawable/textview_border" />
<TextView
android:text="test2"
android:layout_width="150dp"
android:layout_height="150dp"
android:textSize="50dp"
android:gravity="center"
android:background="@drawable/textview_border" />
<TextView
android:text="test3"
android:layout_width="150dp"
android:layout_height="150dp"
android:textSize="50sp"
android:gravity="center"
android:background="@drawable/textview_border"/>
运行后结果分别如下所示
对比发现3张图实际显示的test2和test3全部都大小基本一致(拿手指对比测量,没有用尺子测)。这里可以看到sp很好地解决了不同density下字体显示大小一致的问题。
虽然3台设备density不一致,但是运行程序后显示的文字的物理大小完全一致。验证了dp和sp都是密度无关像素单位。1dp单位在设备屏幕上总是等于1/160英寸。
一般情况下dp=sp,但是由于android支持自定义字体尺寸(这里字体尺寸应该是在系统设置里面可以设置的),因此在某些情况下dp不等于sp,所以textSize推荐sp。
至此,通过ImageView和TextView的属性对px、dp、sp、density、dpi等名词应该都有有了一定的了解。
下面再来考虑更复杂一些的布局情况,因为一个xml布局文件中通常会存在多个控件。
下面请看这里:
android UI适配简单记录二
https://www.jianshu.com/p/47f37e003edf
参考链接:
android使用adb命令查看设备尺寸和密度https://www.cnblogs.com/zhaoqingyue/p/5887683.html
android利用adb修改手机的分辨率和dpi
https://www.cnblogs.com/Sir-Lin/p/7993828.html
Android 目前最稳定和高效的UI适配方案
https://www.jianshu.com/p/a4b8e4c5d9b0
分辨率,dpi,dp,与最终显示大小的四角关系
https://www.jianshu.com/p/ac325e1446df
dpi 、 dip 、分辨率、屏幕尺寸、px、density 关系以及换算https://www.cnblogs.com/yaozhongxiao/p/3842908.html
android获取屏幕密度dpi
https://blog.csdn.net/u013366008/article/details/50895441
Android屏幕密度(Density)和分辨率的关系
https://blog.csdn.net/feng88724/article/details/6599821
屏幕适配以及DisplayMetrics解析
https://blog.csdn.net/weixin_36194487/article/details/80404044
Android屏幕适配
https://www.jianshu.com/p/77e20195d931
android适配(一) 之dp、dip、dpi、px、sp简介及相关换算
https://blog.csdn.net/qq_23042121/article/details/53118853
两分钟理解Android中PX、DP、SP的区别
https://blog.csdn.net/donkor_/article/details/77680042
px、dp与sp的区别以及换算
https://www.cnblogs.com/libertycode/p/5247421.html
今日头条适配方案解读即常用适配方案总结
https://www.jianshu.com/p/d2150109217f
Android适配--最详细的限定符屏幕适配方案解析 附带values-Dimens文件生成工具
https://blog.csdn.net/qq_30993595/article/details/85280936
Android 屏幕适配方案
https://blog.csdn.net/lmj623565791/article/details/45460089
骚年你的屏幕适配方式该升级了!-今日头条适配方案
https://www.jianshu.com/p/55e0fca23b4f?utm_source=oschina-app
适配不同的屏幕
http://hukai.me/android-training-course-in-chinese/basics/supporting-devices/screens.html
http://developer.android.com/training/basics/supporting-devices/screens.html
Android 适配(drawable文件夹)图片适配(二)https://www.cnblogs.com/huihuizhang/p/9473698.html
玩转Android drawable图片适配
https://blog.csdn.net/myoungmeng/article/details/54090891
https://developer.android.com/guide/topics/resources/providing-resources.html#BestMatch
适配不同的屏幕
http://hukai.me/android-training-course-in-chinese/basics/supporting-devices/screens.html
http://developer.android.com/training/basics/supporting-devices/screens.html
android 为TextView添加边框
https://blog.csdn.net/jwzhangjie/article/details/9404823