例如 Path 的列表滚动感觉非常流畅,没有 UITableView 的迟滞感(当然是指同样多子 view 的情况下)。想知道 Path 用的是什么方案?暂时只想到可能是完全纯自绘。其他还有吗?希望有相关经验者不吝赐教
按投票排序按时间排序
14 个回答
195赞同反对,不会显示你的姓名
徐哲 ,从有 SDK 那天开始开发
195 人赞同
如果你想要如丝般顺滑的效果,那么:
1、每次都看一下有没有能重用的 cell,而不是永远重新新建(这个是 UITableView 的常识)
2、Cell 里尽量不要用 UIView 而是全部自己用 drawRect 画(之前因为 iOS 有 bug,这样做会有性能上质的飞越。也有很多大神写过很多文章解释原理,有兴趣的自己去看看吧我就不做复制粘贴了。后来 iOS 也改掉了这个问题,这么做的效果就没那么明显了。)
3、图片载入放到后台进程去进行,滚出可视范围的载入进程要 cancel 掉
4、圆角、阴影之类的全部 bitmap 化,或者放到后台 draw 好了再拿来用
5、Cell 里要用的数据提前缓存好,不要现用现去读文件
6、数据量太大来不及一次读完的做一个 load more cell 出来,尽量避免边滚边读数据,这样就算是双核的 CPU 也难保不会抽
做到以上6条的话,应该就能做出很顺畅的滚动了(现在的 Twitter 官方客户端的原作者写过一篇文章,总结起来也无非就是我说的前3条,可以找来看看)。
Path 2.5 的那个滚动说实在的不是很顺畅,图片显示出来的时候都会抽一下,他们还有很大的改进余地。
对于3的补充说明:UIImageView 的载入是惰性的说法,是对的。但是大部分开发者都没有正确理解这一点。下面就详细解释一下:
[UIImage imageWithContentOfFile:] 出来的 UIImage 其实并没有真正把文件解码到内存,而是要等到用的时候(例如去显示或者去 scale)才会去做这件事情。但问题就在于 UIImageView 试图去 draw 图片的时候,它读文件、渲染也是在主线程里做的,所以你要读入的图片如果很大(比如 iPad3 上的 @2x 图),这一步就很容易会卡一下。这也就是为什么我说图片要放到后台进程去解码完之后(解码是可以后台做的!很多“大神”居然都不知道这一点),再拿来显示(显示只能在主线程做)的原因。
46赞同反对,不会显示你的姓名
李乐佳 ,乐视体育 技术经理
46 人赞同
2012年的WWDC的238 section讲的就是一些关于增强图形动画性能的tips,我做了一些笔记,我又整理了一下,楼主可以参考:
1、关于图片的加载:
- UIImageView 是由CALayer, UIImage->CGImage 构成的,CGImage 在加载的时候不会解码图像,只有在第一次用的时候才会解码图像(Lazy Loading)。所以,尽量用UIImageView 不要直接把图像画在 drawrect:
- iOS本身对于PNG文件进行了很多优化:例如(这个我怕翻不太准):
- Premultiply alpha, and byte-swap
- Turn off some PNG compression modes
- Allow concurrent decoding of a single image
- 此外,iOS6对Jpeg文件也已经优化了很多,但是不建议用Jpeg文件作为UI元素;永远不要用其他格式的图片作为UI元素,尽管iOS都支持。
- [UIImage imageNamed:] 的数据会缓存在内存中,而[UIImage imageWithContentOfFile:]则不会,所以需要慎重选择方法。这也是为什么有时候模拟器跑得很好,而真机跑的时候很悲剧的原因,模拟器设置的缓存内存比真机大很多。
- 在设置背景图片的时候不要在drawRect里
[self.image drawInRect: [self bounds] blendMode kCGBlendModeNormal alpha:1.0], 可以这样:myView.layer.content = (id)[self.image CGImage]; - iOS 6新功能 myView.layer.drawAsynchronously = YES 对于一个view里有很多需要draw的内容来说,很有用,但是有时候会很差,需要用time profile验证以后再尝试.
- 在需要用到SetNeedDisplay的时候,看能不能用setNeedsDisplayInRect: 代替,这个会节省很多开销!!!
2、关于Scrolling
- 学会用Instruments!!!
- 所有scrolling都需要 60 fps 小于 45fps用户依然能察觉。所以我们拥有的时间仅有: 16ms/frame
- 考虑优化功能的部分是在GPU还是CPU
1. CGDrawing 和 imageIO 是CPU
2. 渲染系统并不每一帧都工作在CPU上
3. 渲染本身是在GPU上的
4. 可以用OpenGL ES instruments 上的device utilization查看GPU使用情况
如果是100%左右的,肯定是GPU
如果是16%之类的,就应该是CPU
如果是GPU,有一个 Core Animation instruments可以查看
在scrolling 的 16ms 中,我们需要做的是
1. calculate new scrolling position
2. Prepare and commit animation
3. Render frame
减少blending,blending就是经常需要多重画的
self.layer.shouldRasterize = YES
在Time profiler 里,如果有很多时间被浪费在了spring board 里,spring board实际是render server的所在,所以,结论应该是,应用里有太多的layer了。
结论:
1、多在不同设备上测试动画、他们的区别可能在于GPU, CPU, Retina blabla
2、不同场景有不同的解决方法,到底是用drawRect? 还是用SubView?
3、测量、测试、迭代
工作时间,先贴上来,稍后再整理=。=
80赞同反对,不会显示你的姓名
钟天任 ,哟哟
80 人赞同
tableView滚动不流畅涉及的原因是方方面面的, 其中复杂的高度计算也是令滚动卡顿的多发原因之一, 如果你的cell的高度相当复杂, 而且你又不得不在viewController的tableView代理方法中处理这些高度逻辑, 这将导致viewController代码臃肿之余, 也会成为tableView卡顿的重要原因
为此wwdc有专门的一期讲述了如何合适的设置rowHeight的策略, 就是self-sized cell
如图所示, row123是已经在屏幕上被展示的cell, 而row4则是下一个会被展示的cell, 这时row4这个cell的rowHeight是你预先为他设置的estimated height, 又或者是UITableViewDelegate中返回的height.
当用户滚动的时候, 首先, cell就会被创建(或重用), 然后, cell会被调用调整size的方法, 接着, cell会根据tableView的size去调整自身的contentSize, 最后, cell被展示出来. 这就是self-sized cell的思路.
如下图所示, 整个控制器中即使没有任何UITableViewDelegate的方法, 也没有额外的关于高度的私有方法, 只是简单的赋值.也能达到根据文字长度自动调整高度的效果, 并且不会因为复杂的高度计算而导致卡顿.
使用这种策略, 所有的高度逻辑都被放在cell里面, 你在tableView返回cell的方法中, 只需要简单赋值给cell, 例如不同长度的文字, cell便会根据你所赋值的内容自动调整高度
以上的4步中, 最关键的是第二步. 如何让一个cell去size自己的大小呢?
首先你的cell要使用Autolayout布局, 以上面的demo为例, 帮label简单设置上下左右四个约束就可以了. 设置好了以后label能正常的显示, 但是tableView仍然没办法根据内容的长度来调整cell的高度.
以上是wwdc的原装代码, 使用了VFL这种蛋疼玩意, 其实用xib简单地拖四个到边缘约束也是没问题的
这是因为虽然cell已经具有自我布局的功能, 但是tableView还是以前那个tableView, 它仍然会去寻找自身的rowHeight属性或者返回height的代理方法来确认每一行的高度.
所以最后一步是在tableView中启动动态布局, 告诉tableView用新的方法来布局行高.
在tableView的初始化方法中加入以上代码, estimatedRowHeight是为cell提供一个预估的行高, 还有一个作用是根据预算行高和行数, 显示合理长度的scrollIndicator (灰色的滚动条) , 而UITableViewAutomaticDimension则是告诉tableView你将通过别的方法来算出行高, 而不是rowHeight或者delegate方法.
再运行一下, 你将得到一个在没有任何delagete方法设置行高的viewController中, 布局出根据内容有动态高度的tableView.
SelfSizedCellExample/SelfSizedCellExample.xcodeproj at master · vanvanj/SelfSizedCellExample · GitHub
最后附一个小demo
18赞同反对,不会显示你的姓名
臧其龙 ,你全力以赴都打不倒的男人.
18 人赞同
一句话终结此题,http://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/
3赞同反对,不会显示你的姓名
林博 ,iOS程序员,熟悉越狱开发。
3 人赞同
1、Path里面有多种Cell,使用了多个CellIdentifier,并将其放在同一个TableView里面来实现多样式的载入;
2、Path并没有避免透明背景的绘制,所以在透明背景的Label很多的情况下会比较卡,而当整个页面都是图片和评论(评论是白色背景的),流畅度会有明显的提升;
3、Path的图片比较大,多为延迟载入;
4、评论中小头像的圆角使用图片遮盖来实现,避免了过多的绘制圆角。
8赞同反对,不会显示你的姓名
赵永鹏 ,移动开发开发者
8 人赞同
- UITableViewCell里不要添加太多subview,最好只添加一个cellview。
- UITableViewCell 上的子View的opaque属性设为YES。其实默认也是不透明。UITableViewCell尽量不要包含透明的子View。
- 在cellview里,重写drawRect函数绘制UITableViewCell的内容。
- 在绘制字符串时,尽可能使用drawAtPoint: withFont:,而不要使用更复杂的drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withFont:(UIFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode; 如果要绘制过长的字符串,建议自己先截断,然后使用drawAtPoint: withFont:方法绘制。
- 在绘制图片时,尽量使用drawAtPoint,而不要使用drawInRect。drawInRect如果在绘制过程中对图片进行放缩,会特别消耗CPU。
- 如果绘制cell过程中,需要下载cell中的图片,建议在绘制cell一段时间后再开启图片下载任务。譬如先画一个默认图片,然后在0.5S后开始下载本cell的图片。
- 即使下载cell 图片是在子线程中进行,在绘制cell过程中,也不能开启过多的子线程。最好只有一个下载图片的子线程在活动。否则也会影响UITableViewCell的绘制,因而影响了UITableViewCell的滑动速度。(建议结合使用NSOpeartion和NSOperationQueue来下载图片,如果想尽可能找的下载图片,可以把[self.queuesetMaxConcurrentOperationCount:4];)
- 最好自己写一个cache,用来缓存UITableView中的UITableViewCell,这样在整个UITableView的生命周期里,一个cell只需绘制一次,并且如果发生内存不足,也可以有效的释放掉缓存的cell。
- 不要将tableview的背景颜色设置成一个图片。这回严重影响UITableView的滑动速度。在限时免费搜索里,我曾经翻过一个错误:self.tableView_.backgroundColor = [UIColorcolorWithPatternImage:[UIImageimageNamed:@"background.png"]]; 通过这种方式设置UITableView的背景颜色会严重影响UTIableView的滑动流畅性。修改成self.tableView_.backgroundColor = [UIColor clearColor];之后,fps从43上升到60左右。滑动比较流畅。
- cell的行高不是固定值,需要计算,则要尽可能缓存行高值,避免重复计算行高。这里指的是UITableViewDelegate里的行高函数。
如果做到以上10点,则UITableView 滑动的fps可以达到60 fps。滑动非常顺畅...发布于 2012-08-08 3 条评论 感谢
1赞同反对,不会显示你的姓名
匿名用户
1 人赞同
随手搬运。
iOS 保持界面流畅的技巧
这篇文章会非常详细的分析 iOS 界面构建中的各种性能问题以及对应的解决思路,同时给出一个开源的微博列表实现,通过实际的代码展示如何构建流畅的交互。
2赞同反对,不会显示你的姓名
知乎用户 ,iPhone Developer
2 人赞同
自己draw不会对滑动性能有明显提升,因为UIView本身是轻量级的,而且UIView自身的绘制已经被优化到了极致,大部分时候自己draw的不会比UIView自身画得快,跟绘图的时间相比,UIView自身的初始化的性能损耗可以忽略不记。
rasterize是必备的,但对第一次绘制的cell没有作用。而且rasterize再cell的外观改变后,就要重画,对于内容可变的cell,rasterize反而会降低性能。
补充几点:
1.中文的绘制比英文要低效很多,如果可能,尽可能的让中文少显示一些,比如说在cell中加入一个“展开”按钮,只有用户点击展开才能看到较大量的文字内容。
2.如果可以的化,要让cell和tableview opaque,透明的view比较损耗性能。
3.尽可能的使用1:1像素的图片,不要进行缩放,如果是与web端交互的应用,最好能专门为ios客户端设置拉取小图的接口。
1赞同反对,不会显示你的姓名
Suruiqiang ,Diaosi
1 人赞同
设定CALayer的ShouldRasterize = YES成了简易得到速度提升的小技巧。不过方便的事物总伴随着局限性。虽然apple官方开发文件仅仅很保守的提及了透明度效果无法进行Rasterize(光柵化),然而在真机上的执行却会造成图像破碎跟闪烁的結果。偏偏透明度的应用在時髦介面的设计上很常见。 所有 : 慎用ShouldRasterize。 简单的大招往往有副作用 且 未必适合所有场景, 尤其cell的内容不断变化。
0赞同反对,不会显示你的姓名
基本设计师 ,公众号:基本设计师
归纳:1. 异步加载 2.图形优化
0赞同反对,不会显示你的姓名
王怡飞 ,学习iOS开发中
最简单粗暴的方法,异步加载
0赞同反对,不会显示你的姓名
square ,翻了一座山发现只是个土坡
Path 开源了这部分的代码,有兴趣的可以研究研究
path/FastImageCache · GitHub
0赞同反对,不会显示你的姓名
yan jie ,奋斗着
请问, Path里面有多种Cell,使用了多个CellIdentifier,并将其放在同一个TableView里面来实现多样式的载入;
如果cell显示内容相近,比方说一个cell里面可能显示了图片并且多显示一个label , 一个cell里面没有显示图片并少显示一个label, 这种情况下,使用一个cell, 将里面的view依据数据来源做.hidden属性的设置, 还是做两个不同的cell(两个不同的CellIdentifier)效果好啊?
0赞同反对,不会显示你的姓名
佳佳 ,follow your heart
不详细展开了,请记住一个宗旨,所有所谓的为了流畅性的优化,都是将同步性行为改为异步性行为。