Ui

android UI适配简单记录一

2019-06-26  本文已影响0人  梧叶已秋声

有关UI适配的屏幕相关概念很多,如分辨率、density、dpi、dip、dp、sp和px。
孤立地去看这些概念有点难理解,因此我找了3台不同的android设备,希望通过实物,以及一个UI适配的实例去学习这些知识点。
首先,可以使用adb查看屏幕分辨率和屏密度

wm size  //查看屏幕分辨率
wm density //查看屏密度
1 2 3

以下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
image.png

输出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为单位。就简单记住是个单位吧。

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。

  1. 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在不同设备上显示图片会导致的问题。
随便找的一个图片。


test.jpg

查看其属性,找到其尺寸大小。


image.png

图片大小为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台设备上运行,结果分别如下。

1 2 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自带画图软件新建一个)。
然后点击调整大小


image.png image.png

生成后,添加一个文本,就写xhdpi 200x200了,然后保存,如下所示。


test.png

并且分别在drawable-hdpi、drawable-ldpi、drawable-mdpi和drawable-xxhdpi等目录下新建文字内容不同但是名称都为test的图片。

image.png test.png test.png test.png test.png

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的设备上运行结果如下:


1

在density为2.0的设备上运行结果如下:


2

在density为3.0的设备上运行结果如下:


3

由于设备1为横屏,设备2,3位竖屏,因此这里是在高度上显示效果一致(基本高度上占除去导航栏和状态栏后屏高的三分之一),在宽度上显示效果不一致。
一般来说横屏的UI适配是需要新建一个layout布局文件的。

//以下文字部分出自android编程权威指南
创建水平模式布局具体步骤如下:
在项目工具窗口中,右键单击res目录后选择New → Android resource directory菜单项。创建资源目录界面列出了资源类型及其对应的资源特征。从资源类型(Resource type)列表中选择layout,保持Source Set的main选项不变。接下来选中待选资源特征列表中的Orientation,
然后单击>>按钮将其移动至已选资源特征区域。

image.png image.png image.png

将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.png

//写到这里其实我已经断断续续花了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]目录直接跳过,中间任一目录查找到资源,则停止本次查找。

总结一下图片查找过程:优先匹配最适合的图片→查找密度高的目录(升序)→查找密度低的目录(降序)。
image.png

看完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"/>

运行后结果分别如下所示


1 2 3

对比发现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

上一篇下一篇

猜你喜欢

热点阅读