iOS开发中,屏幕适配很多使用masonary这个库,但是使用过程中需要知道这些在屏幕适配的过程中与图片相关的工作。
一、基本概念
首先,理解几个概念: Points, Rendered Pixels, Physical Pixels, Physical Device。
- Points: 是iOS开发中引入的抽象单位,称作点。开发过程中所有基于坐标系的绘制都是以 point 作为单位,在iPhone 2G,3G,3GS的年代,point 和屏幕上的像素是完全一一对应的,即 320 480 (points), 也是 320 480 (pixels)
- Rendered Pixels: 渲染像素, 以 point 为单位的绘制最终都会渲染成 pixels,这个过程被称为光栅化。基于 point 的坐标系乘以比例因子可以得到基于像素的坐标系,高比例因子会使更多的细节展示,目前的比例因子会是 1x,2x,3x
- Physical Pixels: 物理像素,就是设备屏幕实际的像素
- Physical Device: 设备屏幕的物理长度,使用英寸作为单位。比如iPhone 4屏幕是3.5英寸,iPhone 5 是4英寸,iphone 6是4.7英寸,这里的数字是指手机屏幕对角线的物理长度。实际上会是Physical Pixels的像素值(而不是Rendered Pixels的像素值)会渲染到该屏幕上, 屏幕会有 PPI(pixels-per-inch) 的特性,PPI 的值告诉你每英寸会有多少像素渲染。
渲染像素Rendered Pixels是理论值,真正的物理像素Physical Pixels会小于或等于这个理论值
那么,iOS 开发中,上述单位会有什么对应关系呢?列表回答:
机型 | 屏幕宽高(point) | 渲染像素(pixel) | 物理像素(pixel) | 屏幕对角线长度(英寸) | 屏幕模式 |
---|---|---|---|---|---|
iPhone 2G, 3G, 3GS | 320 * 480 | 320 * 480 | 320 * 480 | 3.5(163PPI) | 1x |
iPhone 4, 4s | 320 * 480 | 640 * 960 | 640 * 960 | 3.5 (326PPI) | 2x |
iPhone 5, 5s | 320 * 568 | 640 * 1136 | 640 * 1136 | 4 (326PPI) | 2x |
iPhone 6, 6s, 7 | 375 * 667 | 750 * 1334 | 750 * 1334 | 4.7 (326PPI) | 2x |
iPhone 6 Plus, 6s Plus, 7 Plus | 414 * 736 | 1242 * 2208 | 1080 * 1920 | 5.5 (401PPI) | 3x |
所以代码中输出屏幕分辨率大小的时候,其实就是输出的是点
NSLog(@"%f,%f,%f",[UIScreen mainScreen].bounds.size.width,[UIScreen mainScreen].bounds.size.height,[UIScreen mainScreen].scale);
//在6s的模拟器中输出的值
375.000000,667.000000,2.000000
关于上述关系 PaintCode 绘制图形进行了详细说明,可以看The Ultimate Guide To iPhone Resolutions
由上可以看出,所谓的屏幕模式,描述的就是屏幕中一个点有多少个 Rendered Pixels 渲染,对于2倍屏(又称 Retina 显示屏),会有 2 2 = 4 个像素的面积渲染,对于3倍屏(又称 Retina HD 显示屏),会有 3 3 = 9 个像素的面积渲染。
关于 Points 和 Pixels 的描述,参考官方文档: Points Versus Pixels.
iOS 开发中,所有控件的坐标以及控件大小都是以点为单位的,假如我在屏幕上需要展示一张 20 20 (单位:point)大小的图片,那么设计师应该怎么给我图呢?这里就会用到屏幕模式的概念,如果屏幕是 2x,那么就需要提供 40 40 (单位: pixel)大小的图片,如果屏幕是 3x,那么就提供 60 * 60 大小的图片,且图片的命名需要遵守以下规范:
Standard: <ImageName><device_modifier>.<filename_extension>
High resolution: <ImageName>@2x<device_modifier>.<filename_extension>
High HD resolution: <ImageName>@3x<device_modifier>.<filename_extension>
- ImageName: 图片名字,根据场景命名
- device_modifier: 可选,可以是 ~ipad 或者 ~iphone, 当需要为 iPad 和 iPhone 分别指定一套图时需要加上此字段
- filename_extension: 图片后缀名,iOS中使用 png 图片
例如:
- MyImage.png - 1x 显示屏自动加载的图片版本
- MyImage@2x.png - 2x 显示屏自动加载的图片版本
- MyImage@3x.png - 3x 显示屏自动加载的图片版本
- MyImage@2x~iphone.png - 2x iPhone 和 iPod touch 显示屏自动加载的图片版本
- MyImage@3x~iphone.png - 3x iPhone and iPod 显示屏自动加载的图片版本
2x屏幕的设备会自动加载 xxx@2x.png 命名的图片资源,3x屏幕的设备会自动加载 xxx@3x.png 的图片, 现在基本没有 1x屏幕的设备了,可以不用提供这个分辨率的图片了。
二、设计和开发之间的多屏适配问题
现在APP设计开发必须考虑适配大、中、小三种屏幕。所以如何做到交付一套设计稿解决适配大中小三屏的问题?设计和开发之间采用什么协作模式?
一个基本思路是:
- 选择一种尺寸作为设计和开发基准;
- 定义一套适配规则,自动适配剩下两种尺寸;
- 特殊适配效果给出设计效果。
其中看到手机淘宝的设计方案,和我的想法差不多,今天才看到,以前走了弯路。
第一步,视觉设计阶段,设计师按宽度750px(iPhone 6)做设计稿,除图片外所有设计元素用矢量路径来做。设计定稿后在750px的设计稿上做标注,输出标注图。同时等比放大1.5倍生成宽度1125px的设计稿,在1125px的稿子里切图。
第二步,输出两个交付物给开发工程师:一个是程序用到的@3x切图资源,另一个是宽度750px的设计标注图。
第三步,开发工程师拿到750px标注图和@3x切图资源,完成iPhone 6(375pt)的界面开发。此阶段不能用固定宽度的方式开发界面,得用自动布局(auto layout),方便后续适配到其它尺寸。
第四步,适配调试阶段,基于iPhone 6的界面效果,分别向上向下调试iPhone 6 plus(414pt)和iPhone 5S及以下(320pt)的界面效果。由此完成大中小三屏适配。
2.1、为什么选择iPhone 6作为基准尺寸?
当面对大中小三种屏幕需要适配的时候,很容易想到先做好一种屏幕,再去适配剩下两种屏幕。第一个决定是到底以哪种屏幕作为设计和开发的基准尺寸。
我们选择中间尺寸的iPhone 6(750px/375pt)作为基准,基于几个原因:
1、从中间尺寸向上和向下适配的时候界面调整的幅度最小。375pt下的设计效果适配到414pt和320pt偏差不会太大。假设以414pt为基准做出很优雅的设计,到320pt可能元素之间比例就不是那么回事了,比如图片和文字之间视觉比例可能失调。
2、iPhone 6 plus有两种显示模式,标准模式分辨率为1242x2208,放大模式分辨率为1125x2001(即iPhone 6的1.5倍)。可见官方系统里iPhone 6和iPhone 6 plus分辨率之间就存在1.5倍的倍率关系。很多情况下这两种尺寸可以用1.5倍直接等比适配。
3、1242x2208这个奇葩的数值是苹果官方都不愿意公开宣传的一个分辨率,不便于记忆和计算栅格。640x1136虽然是广泛应用的一个分辨率,但是大屏时代依然以小尺寸为设计基准显然不合时宜,设计师会停留在小屏的视角做设计。
所以,iPhone6的750x1334是最适合基准尺寸。只交付一套设计稿,默认用什么规则来适配?前文提到适配策略是先选择iPhone 6作为基准设计尺寸,然后通过一套适配规则自动适配到另外两种尺寸。
这套适配规则总结起来就一句话:文字流式,控件弹性,图片等比缩放。
控件弹性指的是,navigation、cell、bar等适配过程中垂直方向上高度不变;水平方向宽度变化时,通过调整元素间距或元素右对齐的方式实现自适应。这样屏幕越大,在垂直方向上可以显示更多内容,发挥大屏幕的优势。
按照上述默认适配规则,大中小三种屏幕显示效果均相同。有时候想在大屏幕显示更多内容,需要设计出特殊适配效果。比如App store首页焦点图,从iPhone 6适配到iPhone 6 plus时焦点图尺寸和排版做了特殊处理。底下应用列表也从一排3+个变成一排4+个,真正实现了大屏幕显示更多内容的理念。这些就需要设计师给出相应设计稿。
三、布局处理
在使用masonary自动布局时,可以根据6s的屏幕设计,设置一个比例系数,比如
//以6/6s为准宽度缩小系数
#define kJLXWidthScale [UIScreen mainScreen].bounds.size.width/375.0
//高度缩小系数
#define kJLXHeightScale [UIScreen mainScreen].bounds.size.height/667.0
这样在布局的的时候,可以考虑使用上这个系数设置高度
UIButton *createrButton = [[UIButton alloc] init];
[self.view addSubview:createrButton];
UIEdgeInsets padding = UIEdgeInsetsMake(0, 10, 65, 10);
[createrButton setBackgroundImage:[UIImage imageNamed:@"common_button_pink"] forState:UIControlStateNormal];
[createrButton mas_makeConstraints:^(MASConstraintMaker *make){
make.left.equalTo(self.view.mas_left).with.offset(padding.left);
make.height.equalTo(@(60*kJLXHeightScale));
make.bottom.equalTo(self.view.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(self.view.mas_right).with.offset(-padding.right);
}];
这样在5s小屏手机上面,按钮的高度就会根据比例系数来动态调整大小。
四、图片处理
在游戏中,经常会碰到九宫格的可拉伸图片,在ios开发中也可以使用,比如截取一部分拉伸,其他不变
UIImage *img = [UIImage imageNamed:@"popup"];
img = [img resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 55) resizingMode:UIImageResizingModeStretch];
self.resizableImgView.image = img;
如果是平铺
可这么实现
UIImage *img = [UIImage imageNamed:@"about"];
img = [img resizableImageWithCapInsets:UIEdgeInsetsMake(0, 11.5, 0, 11) resizingMode:UIImageResizingModeTile];
self.resizableImgView.image = img;
五、状态栏和导航栏的大小
状态栏和导航栏可以通过代码来获取
// 状态栏(statusbar)
CGRect rectStatus = [[UIApplication sharedApplication] statusBarFrame];
NSLog(@"status width - %f", rectStatus.size.width); // 宽度
NSLog(@"status height - %f", rectStatus.size.height); // 高度
// 导航栏(navigationbar),这个需要你的controller加到了navigationController上面,并且navigationBar不能设置为隐藏,否则获取到的宽高是0
CGRect rectNav = self.navigationController.navigationBar.frame;
NSLog(@"nav width - %f", rectNav.size.width); // 宽度
NSLog(@"nav height - %f", rectNav.size.height); // 高度
打印结果
status width - 320.000000
status height - 20.000000
nav width - 320.000000
nav height - 44.000000
六、参考文章
- The Ultimate Guide To iPhone Resolutions
- Points Versus Pixels.
- 手机淘宝的设计方案
- 浅谈iOS屏幕适配
- iOS获取状态栏和导航栏尺寸(宽度和高度)
版权属于:东哥笔记 - DongGe.org
本文链接:https://dongge.org/blog/487.html
自2017年12月26日起,『转载以及大段采集进行后续编辑』须注明本文标题和链接!否则禁止所有转载和采集行为!
16 条评论
此文, 相见恨晚 !!!
一起进步
是的,写错了,谢谢指正୧(๑•̀⌄•́๑)૭
有个疑问。比如说两个控件的上下间距是15,适配的时候需要乘以高度系数吗?还是直接就是15?
这个主要就是看需求的,不乘的好处就是两个空间的位置是固定的,因为2倍图和3倍图都是自动加载的,所以不乘的话整个看起来和设计图一样都很协调,坏处就是如果设计图给的大了,小屏手机就有可能控件超出屏幕了。乘的好处就是位置是动态的,相对位置都可以显示,但是坏处就是小屏手机上面图是自动加载的,间距小,就会出现控件很挤的情况。除非是设置宽高控件都变小了,这样就是相当于屏幕等比例缩放了,坏处就是按钮什么都变小了,小屏幕会显得更小
嗯嗯。确实是个纠结的问题。首先我们对于cell的处理是垂直方向上的高度保持不变,通过控制间距和对齐方式实现自适应<对于单行显示的文字,有时候不得不改变字体的大小去做适应>。但是对于非cell内部的独立的控件而言,相对于父控件左右的间距,我都会乘以一个宽度系数比去适配宽度,然后高度乘以一个高度系数比。对比于cell,可能设计上两者的高度是一致的,但是这样处理之后,在小屏上就会形成一大一小的情况。
是的,对于整个布局来说,虽然乘以比例理想上会是等比例缩放的,但是这样会做很多工作,包括控件比如按钮的长宽,有点得不偿失,所以现在我更倾向于不乘比例,就写死数值,如果碰到小屏幕手机,那某个超出的页面就使用scrollview,小屏幕手机滚动就行了
嗯嗯。博主对于控件比如按钮的长宽的做法是:按钮相对于父控件左右的间距,乘以一个宽度系数比去适配宽度,然后高度不乘系数,如此去适配的吗?然后控件的上下间距也是固定的?
恩,但是宽度其实也可以不用去适配的,因为很少设计会把左右宽度撑满的,几乎全是左右留白的
感谢博主悉心指导。
一般是按照基准宽度(比如375) 计算出宽度系数scale =(screen_width / 375) 来适配子控件的宽高,间距一般情况下不适配,保持和设计图一致
只用scale =(screen_width / 375)比例适配宽高吗
嗯,宽度是固定的,设计很多都是宽度固定750的,不同手机差别不大
不错,写的很全!!!