智能手机发展到今天,屏幕尺寸变的越来越多,iPhone从最初的3.5寸屏幕,到后来推出的4寸屏,直到苹果推出iPhone 6 和 iPhone 6Plus,也宣告着苹果阵营被彻底攻破,进入了屏幕尺寸碎片化的时代。只为某一个屏幕尺寸设计的日子已经不在存在。为了适配所有的屏幕,设计师必须考虑各种屏幕尺寸。但是又不可能为每个尺寸都设计一遍。那么我们又该如何面对屏幕碎片化的困境?
苹果给出的答案是AutoLayout。让你能用一个设计来适配所有屏幕,理论上讲从iPhone4适配到iPad pro都可以。它希望你忘记某个具体的尺寸。实际上你可以随意拖出一个任意尺寸的画布进行设计,标注好后就可以交给工程师开发。
首先我们先看一下,苹果的开发软件Xcode上是让你怎么进行页面布局的。
中间那块白色的正方形就是画布,如果你是使用storyboard布局的话(iOS的布局方式有很多种,storyboard只是其中一种,我在后面会讲),你可以将你设计好的控件放到这个画布上去,根据你标注的尺寸定义好它们的位置关系,接下来AutoLayout就会自动适配各个屏幕了。听上去好像很神奇。
有些人会有疑问:我是以iPhone6的尺寸为基础进行设计和标注的,怎么可能在一个正方形上根据我标注的尺寸定好它们的位置关系,放到这个正方形上,我的标注不是全乱了吗?答案是,是,也是不是。如果你在设计和标注时只为iPhone6设计,把适配的问题都抛给了工程师,很有可能最后出来的结果不是你想要的。相反,即使你是在iPhone6上进行设计,但是你并没有把思维局限在某个尺寸上,那么你的标注放在正方形上也不会乱。
确切的讲,如果你是以约束为基础(constraint-based)来设计的界面,那么无论屏幕怎么变化,你的设计也会跟着进行调整。
下面我就来讲讲AutoLayout到底是如何工作的,以及该如何用约束的思想来进行设计。
对于iOS开发来讲,通常会使用的是两种布局方式。一种是使用代码设置每个视图(View)的Frame来进行定位。另一个则是使用AutoLayout进行布局(可以在storyboard上,也可以使用代码)。假设我们在iPhone6(375*667)的尺寸下放了有两个视图,A和B。
Tips:在iOS开发中使用的单位是point,也就是@1x下的尺寸。如果你是以iPhone6(750*1334Px)的尺寸进行设计的,那么里面的尺寸都要除以2才能用。所以建议大家在@1x的环境下设计。本文不做特殊说明,没标单位的标注,默认单位都为point。
设置Frame进行定位的方法不是本文讨论的重点,我只在这里简单介绍一下。首先在iOS里的坐标系和我们平时用的有点不同。它的坐标原点在左上角。每个视图都有自己独立的坐标系。见下图。
通常我们定义一个Frame的代码(Swift)是这样的:
let frame = CGRectMake(x:CGFloat, y:CGFloat, width:CGFloat, height:CGFloat)
当然你不需要看懂它,只需要知道它要你提供四个参数:x,y,width,height。x ,y 是你要定位的视图的原点相对于包含它的视图的(superView)坐标系的坐标。width,height 当然就是你要定位的视图的宽度和高度了。
总结一下就是你需要提供位置信息(location)以及尺寸(size)信息。
所以上图中A,和 B的Frame应该是这样的:
let frameA = CGRectMake(60, 60, 255, 160)
let frameB = CGRectMake(60, 280, 255, 160)
AutoLayout布局背后的逻辑则完全不同于上一种方式。同一个图,AutoLayout可以用下图这种方式来表示。
AutoLayout是通常是通过定义一系列的约束(constrains)来进行定位的。和Frame定位一样,它同样需要你提供位置和尺寸信息,但是和Frame不同的是,它不是让你直接提供x,y,width,height,而是通过你给的约束来推断出相应的尺寸和位置。只有当能从你给的约束关系中推断出位置和大小信息,而且还没有冲突时,才能通过。
如上图中的视图A,我们通过上下左右四个约束定好了它的位置。我们提供了它的高度,但是我们并没有给出它的宽度。之前说过,要确定一个视图的布局,你需要提供位置以及尺寸信息。而这里我们却没有提供宽度。应为宽度不是一个固定的数值,需要AutoLayout自己通过现有的约束来推断出视图A的宽度 = 375(屏幕宽度)— 60—60 = 255,这样当屏幕宽度变化时,视图A的宽度也会随之变化。
AutoLayout能够根据你在相应视图上设置的约束,自动计算出的你定义的视图的位置和大小。
当周边环境变化时,它也随之改变,这就是AutoLayout能适配不同屏幕的秘诀。
AutoLayout的应用并不局限于应对外部环境的变化(屏幕大小变化),即使在同一个屏幕内,当页面内容开始变化时,AutoLayout也能做出相应的调整。假设现在因为某种原因,视图A的高度变为了210。那么就会出现以下两种结果:
用Frame定位方式,因为视图A,B的位置信息是独立的,A的变化,并不会影响B的位置,所以当A变高时,A,B之间高度方向的距离就被压缩了,可以想象,当A继续变高时,A和B之间就会出现叠加的情况,这显然不是我们希望看到的效果。
而AutoLayout则不同,由于B高度方向的位置是相对于A的,所以当A变高时,B的位置也会跟着变化。这当然是我们希望看到的结果。
由此可见,AutoLayout的应用场景不局限于适配不同的屏幕尺寸,即使在软件运行的过程中,当页面变化时它也能够跟着调整。
需要提一下的是,使用Frame定位的方式也是能实现上面这种AutoLayout的效果的,反之我们也能使用AutoLayout来实现像Frame这种A,B不相互影响效果,我在这里不细说,有兴趣的可以自己去思考一下。举这个例子,只是为了说明绝对定位和相对定位的区别。
相对于Frame通常要你提供的x,y,width,height,AutoLayout可用的属性则多的多,常用的属性有这些:
Left
Right
Top
Bottom
Leading
Trailing
Width
Height
CenterX
CenterY
Baseline
查看所有属性可点击这里:
这些属性也大概可以分为两类:大小(size)如width ,height ;位置(Location)如Leading,Trailing,Top,Button。
有了这些属性,我们不仅能够定义不同视图之间的距离,让它们对齐,定义不同视图之间的相对尺寸,甚至可以定义一个视图的长宽比。
值得注意的是其中的Leading 和Trailing,并不等于Left和Right。Leading代表的是阅读开始位置。通常我们都是习惯从左边到右来阅读,这种情况下Leading就是Left,Trailing就是Right。但并不是所有的国家都是依照这个方式的,比如中国古代的书,就是从右到左的,这时候Leading就在Right了。如果你的App用户是国际化的,需要注意这个问题。通常情况下,我们都使用Leading,Trailiing。
假设我们有以上两个View,现在要求他们间隔距离为8,如上图所示。那么他们之间的约束关系就可以这么表示:
ViewB.Leading = 1.0 x ViewA.Trailing + 8
其中1.0 是一个系数(Multiplier),这种情况下,这个系数为1.0
下面就讲一个具体的例子来说明一下AutoLayout应用。
假设我设计了一个这样的界面(375*667),接下来要交给工程师进行开发。我的要求如下:
1.三个视图离页面两边的距离都为37.5
2.每个视图之间的距离,包括视图离页面顶端和底端的距离都要一样,为40
3.三个视图的宽度和高度要一样。
我按照这些要求进行了标注,以下就是我想达到的效果以及我的标注:
先分析一下我们的标注。
首先我要求每个视图离页面两边的距离都为37.5。在标注里我很明确地说明了这个问题。
其次,我要求每个视图之间的距离,包括视图离页面顶端和底端的距离都要一样,为40。在标注中我也很明确地说明了这一点。
最后,我要求每个视图的宽度和高度要一样。在标注里,每个视图的高度都标了169,符合我一样高度的要求。我给第一个视图标上了宽度300,其他两个和第一个首尾对齐,也是300,符合我一样宽度的要求。
乍一看这个标注没有问题。但是这里我们确忽略了一个很重要的问题。这个图我是以iPhone6的尺寸来设计的,但是实际情况,它并不只会出现在iPhone6的屏幕上。按照上面的标注,标注的总宽度 = 37.5*2 + 300 = iPhone6的屏幕宽度(375)。但是如果换一个手机屏幕,这个等式就不成立了。同样,高度也是这个问题。这样就和实际情况产生了冲突。
Tips:当你的标注在宽度方向,或高度方向的数值加起来等于某一个具体的屏幕尺寸时,你就需要去重新去检查你的标注了,需要说明那些尺寸是固定的,哪些尺寸是可变的。
显然,工程师无法满足你标注的所有尺寸要求。如果他只看到你给的标注文件,而不知道你的三点要求。那么这个标注在宽度方向大概就出现了以下几种可能性:
1.保持两边各37.5的边距(margin)要求,每个视图的宽度根据屏幕的宽度来伸缩
2.每个视图的宽度保持300不变,两边的边距根据屏幕的宽度来伸缩
3.视图的宽度和边距随着屏幕的宽度变化一起缩放
从我们之前的要求来看,我们希望的是第一种可能性的,只是我们的标注没有很好的说明这个点。工程师需要靠自己的理解来选择其中一种,有时候他的选择可能并不是我们想要的结果。
我们该如何标注,来很好的表达我的要求?正确的标注应该是这样的:
首先,我去掉了宽度的标注,就像上个例子说的一样,我们需要AutoLayout自己来计算出它的宽度,也就意味着我们是希望每个视图的宽度随着屏幕的宽度变化而变化。其次,我去掉高度的具体数值,只标了一个h。同样,因为屏幕的高度是不固定的,所以我无法给出具体的高度,但是我又要求每个视图的高度是一样的。通过给每个视图标注高度h,来表示他们的高度一样。
之前我说过,我第一次给出的标注大概可以有三种不同的理解,那么我们又该如何来表达这三种可能性呢?下面我就来说一下,如何使用AutoLayout的方式标注,来很明确地来表现以上三种可能性。
第一个视图的标注,保持两边各37.5的边距(margin)要求,每个视图的宽度根据屏幕的宽度来伸缩,这个上面说过。
第二个,我标注了宽度,没标边距。但是我们要求它水平方向居中,通过这个约束来确定它水平方向的位置。这样边距就可以根据屏幕的宽度变化而变化。
第三个,我标注了宽度为80%的屏幕宽度,这样它就能做到和屏幕一起缩放。
我在Xcode上添加好了上面要求的约束,并选择在不同的模拟器上运行。这样就可以看到我们设计的页面在不同设备上运行的效果了。以下就是它在不同屏幕尺寸下显示的效果:
可以看到,在iPhone6上一模一样的三个视图,应为我们设置了不同的约束,在不同的设备上运行就出现了不一样的效果。因为AutoLayout是根据你设置的约束,自动计算出的你定义的视图的位置和大小。
为了适配更多的屏幕,我们在设计的时候需要用相对定位的思维来布局。
这次就讲到这里,下一篇我会介绍更多AutoLayout相关知识,以及如何使用Xcode来添加约束,做出你自己设计的页面。
Via: DDC