美文网首页Ui
android UI适配简单记录一

android UI适配简单记录一

作者: 梧叶已秋声 | 来源:发表于2019-06-26 17:40 被阅读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

    相关文章

      网友评论

        本文标题:android UI适配简单记录一

        本文链接:https://www.haomeiwen.com/subject/qpuqcctx.html