小课堂(尺寸适配)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小课堂(尺寸适配)相关的知识,希望对你有一定的参考价值。

参考技术A

众所周知,移动端适配的问题一个长期且艰巨的任务。市面上的适配方案也是五花八门,而且在不断的更新。但是大部分的方案依旧在被使用,那就证明在不同需求下不同的方案是具有其实用性的。那本文将会简单的讨论下笔者尝试过并且运用在生产环境中的几种常见的方案。

直到目前位置,笔者尝试过的移动端的尺寸适配的方案也有好几种,从最初的百分比单位,到 rem ,再到 vh,vw ,再到结合 flex 布局去使用这些适配单位,依旧在不断的去优化跟尝试新的方案,也试着将这几种融合起来去使用。运用到生产环境后,取得了一些小成效,同时也发现了一些小缺点。下面将会分别对这几个方案进行简单的讨论,分享下笔者使用时所遇到的一些问题以及使用场景。

百分比单位是学习前端的时候最快接触到的一个尺寸单位, % 会基于父元素的尺寸进行分配。如果父元素是一个 200px * 400px 的盒子的话,子元素设置 width: 80% ,则宽度会设置成 160px ,高度的话同理,百分比基于父元素的尺寸去做计算。对于父元素的尺寸的定好的情况,百分比是非常好用的。那么如果父元素没有固定尺寸,是根据子元素的尺寸去撑大的情况呢?那么我们就会发现实际上显示出来的高度是为 0 ,而宽度不为 0 ,不设置尺寸的父元素,其宽度会继承其上一级的宽度,而高度则不会,默认为 0 。那么我们子元素所设置的百分比,宽度的值是正确的,高度为 0 。因为百分比基于父元素的尺寸去计算,父元素宽度不为 0 ,则可以计算出非零数值,而高度为 0 ,计算出来只能为 0 。因此在父元素没有固定尺寸,需要通过子元素去撑大的情况,高度使用百分比会得到 0 。所以高度使用百分比应该谨慎使用。那么如果我们使用百分比去做适配不同尺寸的手机屏幕,保证在所有尺寸的手机屏幕上面都能以正确的比例去复刻UI设计的话,那么我们应该在根元素那里设置好具体尺寸,那样百分比才可以基于这个尺寸一级一级的计算下去。

其实我们可以很直观的发现,如果我们的UI需求是可无限加载的滚动列表的话,那么根元素上面就无法设置具体的高度,那么百分比在高度上面就会失效。那么我们只能设置具体高度给列表项,但是这样无法保证列表项在不同尺寸上面所保持的比例跟UI设计是一致。这就是百分比单位适配的一个很明显的缺点。但是如果UI设计上不要求单项比例与UI保持一致的话,那么依旧可以使用百分比单位。

那么,既然百分比单位无法满足我们的需求的话,我们就开始寻找新的方案。这时候很容易就会发现到大家一直在说的 rem 单位, rem 也是web支持的众多尺寸单位中的一个,与其很像的是 em ,那么 em 跟 rem 有什么区别呢? em 是基于父元素的 font-size 属性,而 rem 则是基于根元素的 font-size 属性,如果父元素设置了 font-size: 50px ,那么子元素的 1em == 50px 。而且如果子元素不设置 font-size ,会默认继承父元素的 font-size ,那么子元素的子元素的同样满足 1em == 50px 。那么可以直观的理解得知,要是子元素设置了自己的 font-size ,那么就不再满足上面的等式。所以如果当父元素需要设置自己的 font-size ,而其子元素有想要基于祖父元素 (父元素的父元素) 去做 em 去设置尺寸的话,就会处于一个尴尬的情况。

那么 rem 又有什么不同呢?上面的时候我们提到了 rem 是基于根元素的 font-size 的大小,如果根元素设置了 font-size: 50px ,则我们在其任何子元素中或者嵌套的多级子元素中,使用 width: 1rem ,会得到 width: 50px 的反馈,而且无视于父元素的 font-size 是设置了什么值。即解决了 em 单位在上述提及的尴尬情况。但是需要注意的是,因为 font-size 是可以逐级继承传递下去的,所以最好在根元素下的第一级元素上将 font-size 设置回默认值。之所以这么做的原因是,如果在逐级嵌套的过程中,出现了文本节点,但是却又没有设置对应的 font-size ,那么该文本节点的字体大小将会是根元素所设置的 font-size 大小,就会出现字体很大的情况。假如大部分文本节点的所需字体大小均为同一尺寸的话,那么在每一个文本节点的地方再重新设置 font-size 的值,其实会很不便。所以在根元素下的第一级的子元素设置好 font-size 的大小,那所有的文本节点就会继承这个 font-size 的值,而不是根元素的。因此就可以不用在每个文本节点处去设置字体大小。

简单的介绍完了 em & rem 这两个单位,我们发现 em & rem 都有其适合的使用场景,但是相比之下 rem 可能更适合于去做整体的适配方案。那么我们应该怎么去确定 1rem 的大小呢?在网上的资料中有多种确定的方案,其中主要就是两种核心计算方式。
第一种就是,根据尺寸范围去确定,也就是在移动端中的多种尺寸中,划分出多个范围,在每个范围中确定一个固定的数值。其具体实现就是结合媒体查询,在不同范围内的屏幕尺寸设置一个固定的值,这样去做适配。
另外一种就是,根据屏幕宽度跟设计稿宽度去做计算,得到一个数值。这样计算在所有的尺寸均能得到一个对应的唯一的值。其具体实现则是通过js去获取到当成设备的屏幕宽度然后去跟设计稿的宽度进行转换,计算得出一个值,然后在通过js动态设置到根元素中。

这两种的思路大体就是上述所讲的,然而具体的转换计算则不同公司有不同的想法。 具体的转换可以点击这里 。笔者自己采用方案是第二种,根据屏幕宽度跟设计稿宽度去转换,那么下面会介绍下笔者是怎么转换的。
笔者的转换计算方式其实与网易手机端的计算方式是一致的,不过想出这个转换计算方式并没有像网易那样理解了那么多,纯粹只是这个样算会很方便而已。因为拿到手的设计稿是750宽度的,一开始试过 750px 转成 75rem 的,那么实际设备的 1rem 大小就是通过使用屏幕宽度去除 75 得到 1rem(实际设备) ,也就是 1rem(实际设备) = w(屏幕宽度)/75 ,但是后来试了发现 1rem=10px(设计稿) 的这个计算,在 chrome 上面会无效, chrome 最小只能到 12px ,而实际计算出来的 1rem 远小于 12px 的,因为手机的宽度大部分在 360 到 480 之间,这样子计算出来的 1rem(实际设备) 肯定小于 12px 的。后来就换成 1rem=100px(设计稿) ,也就是 750px=7.5rem 。之所有将 1rem=10px或者1rem=100px ,纯粹只是因为这样算,在切设计稿的尺寸的时候很方便,如果设计稿是有一个元素要 130px * 80px ,那么只需要设置 1.3rem * 0.8rem 。在计算上面只需要除100,可以直接在切图的时候就能计算出来,也不需要使用计算器去一个个去算,后来是这套方案使用了好一段时间后,才看到上面链接的那个博客介绍到网易手机端也是这么做的,而且考虑的远比我要想的多得多,才发现原来被我瞎猫碰上死耗子。

这套方案很长一段时间都是我做移动端适配的主选方案,直到我遇到了在 ios 的 webview 上面发现动态加载网络图片使用 rem 的时候,图片显示不出来,排查后发现是通过js接口调用后获得的网络图片加载的时候就无法正确的显示设置好的高度。这是我才开始考虑有没有更好的东西去结合 rem 去做得更好。因为之前做微信端的h5的时候,从来没有遇到过使用rem会出现比例错误的情况,在我看来 rem 仍然是在大部分界面上都能很好的做到适配的。不过在使用的过程中也发现了一些小问题,因为 rem 的单位计算是用屏幕宽度去处于 7.5 这个基数,得到的 1rem(实际设备) 的值不能保证是整数,所以设计稿上面一些小的间距,比如小于10px的那种,在css中我们会写 0.06rem 这种情况,但是因为 1rem 本身就不是整数,所以在进行浮点数计算的时候,会出现细微的差距。但是如果间距采用 px 去做,那么 rem 就会变的不准,尤其是在宽度上面,本身 7.5rem 就是一个屏幕宽度,但是加上 px 后就能计算出除去 px 后剩下的是多少个 rem 。

然后开始全面拥抱弹性布局(flex),在使用flex布局后,发现结合flex跟rem可以做的更好。因为如果间距采用px,某些元素采用rem,那么剩下的可以直接flex拉伸铺满,就不用担心除去px后还剩多少个rem的问题。在移动端上面基本上都是兼容了flex这个特性,所以可以直接使用,不过需要加下对应的内核前缀即可。flex的使用很大程度优化了在布局上面的编写。

后面又发现了 vw & vh 这一对, vw 其实就是基于 window.innerWidth , vh 就是基于 window.innerHeight 。 100vw 就等于屏幕可视范围的宽度, 100vh 就等于屏幕可视范围的高度,在移动端的话就是 webview 的尺寸。而且发现到 vw 就很像将 750px=100rem(设计稿) 的这种情况,这种情况下的 1rem 的大小就跟 1vw 的大小是一致的,所以vw的使用上基本与rem的使用相似。而且一屏的宽度等于100vw也很像百分比,但是vw却是基于屏幕宽度的,而不会想百分比那样基于父元素,所以在横向的适配上做到很不错的效果。但是 vh 呢?因为有时候需要做一个 H5 界面是要一屏的宽高,这时候 vh 就发挥出它的作用了,正常情况, 100vh 就是一屏的高度。但是在使用vh的过程遇到过一个问题,就像一个一屏的H5注册页,里面有 input 标签,需要用户填入信息。将背景图的高度设置为 100vh ,当输入框聚焦后,手机键盘弹起,这时候背景图就会被压缩,因为手机键盘弹起后,屏幕的可视高度就是一屏的高度减去手机键盘的高度,因此 100vh 就会变成这个高度而不再是一屏的高度。这个问题就是使用 vw跟vh 的时候遇到过的唯一问题。暂时还没想出好的解决方法。如果不会出现键盘弹出的情况且又需要一屏的高度的时候,很推荐使用 vh 。而 vw 则暂时还没遇到过问题。

适配方面尝试过好几种方法,每种方法都有其优点跟缺点,但是根据使用场景去选择跟结合来使用,就可以做到相当不错的适配效果了。

1.3.2 适配不同的屏幕

Android用尺寸和分辨率这两种常规属性对不同的设备屏幕加以分类。我们应该想到自己的app会被安装在各种屏幕尺寸和分辨率的设备中。这样,app中就应该包含一些可选资源,针对不同的屏幕尺寸和分辨率,来优化其外观。

  • 有4种普遍尺寸:小(small),普通(normal),大(large),超大(xlarge)
  • 4种普遍分辨率:低精度(ldpi), 中精度(mdpi), 高精度(hdpi), 超高精度(xhdpi)

声明针对不同屏幕所用的layout和bitmap,必须把这些可选资源放置在独立的目录中,这与适配不同语言时的做法类似。

同样要注意屏幕的方向(横向或纵向)也是一种需要考虑的屏幕尺寸变化,因此许多app会修改layout,来针对不同的屏幕方向优化用户体验。

创建不同的layout

为了针对不同的屏幕去优化用户体验,我们需要为每一种将要支持的屏幕尺寸创建唯一的XML文件。每一种layout需要保存在相应的资源目录中,目录以-<screen_size>为后缀命名。例如,对大尺寸屏幕(large screens),一个唯一的layout文件应该保存在res/layout-large/中。

Note:为了匹配合适的屏幕尺寸Android会自动地测量我们的layout文件。所以不需要因不同的屏幕尺寸去担心UI元素的大小,而应该专注于layout结构对用户体验的影响。(比如关键视图相对于同级视图的尺寸或位置)

例如,这个工程包含一个默认layout和一个适配大屏幕的layout:

MyProject/
    res/
        layout/
            main.xml
        layout-large/
            main.xml

layout文件的名字必须完全一样,为了对相应的屏幕尺寸提供最优的UI,文件的内容不同。

如平常一样在app中简单引用:

@Override
 protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.main);
}

系统会根据app所运行的设备屏幕尺寸,在与之对应的layout目录中加载layout。更多关于Android如何选择恰当资源的信息,详见Providing Resources

另一个例子,这一个工程中有为适配横向屏幕的layout:

MyProject/
    res/
        layout/
            main.xml
        layout-land/
            main.xml

默认的,layout/main.xml文件用作竖屏的layout。

如果想给横屏提供一个特殊的layout,也适配于大屏幕,那么则需要使用largeland修饰符。

 MyProject/
    res/
        layout/              # default (portrait)
            main.xml
        layout-land/         # landscape
            main.xml
        layout-large/        # large (portrait)
            main.xml
        layout-large-land/   # large landscape
            main.xml

Note:Android 3.2及以上版本支持定义屏幕尺寸的高级方法,它允许我们根据屏幕最小长度和宽度,为各种屏幕尺寸指定与密度无关的layout资源。这节课程不会涉及这一新技术,更多信息详见Designing for Multiple Screens

创建不同的bitmap

我们应该为4种普遍分辨率:低,中,高,超高精度,都提供相适配的bitmap资源。这能使我们的app在所有屏幕分辨率中都能有良好的画质和效果。

要生成这些图像,应该从原始的矢量图像资源着手,然后根据下列尺寸比例,生成各种密度下的图像。

  • xhdpi: 2.0
  • hdpi: 1.5
  • mdpi: 1.0 (基准)
  • ldpi: 0.75

这意味着,如果针对xhdpi的设备生成了一张200x200的图像,那么应该为hdpi生成150x150,为mdpi生成100x100, 和为ldpi生成75x75的图片资源。

然后,将这些文件放入相应的drawable资源目录中:

MyProject/
    res/
        drawable-xhdpi/
            awesomeimage.png
        drawable-hdpi/
            awesomeimage.png
        drawable-mdpi/
            awesomeimage.png
        drawable-ldpi/
            awesomeimage.png

任何时候,当引用@drawable/awesomeimage时系统会根据屏幕的分辨率选择恰当的bitmap。

Note:低密度(ldpi)资源是非必要的,当提供了hdpi的图像,系统会把hdpi的图像按比例缩小一半,去适配ldpi的屏幕。

更多关于为app创建图标assets的信息和指导,详见Iconography design

以上是关于小课堂(尺寸适配)的主要内容,如果未能解决你的问题,请参考以下文章

python小课堂专栏python小课堂33 - 初识原生爬虫优化

python小课堂专栏python小课堂31 - 初识原生爬虫

python小知识课堂

课堂小测

Linux小课堂开课了

促动技术小课堂--未来探索(Future Search)