正式開始前,我們可以先看一下下面幾段代碼:
1 //1.按鈕存在背景圖片 ==========》 觸發了離屏渲染 2 UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom]; 3 btn1.frame = CGRectMake(100, 30, 100, 100); 4 btn1.layer.cornerRadius = 50; 5 [self.view addSubview:btn1]; 6 7 [btn1 setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal]; 8 btn1.clipsToBounds = YES; 9 10 //2.按鈕不存在背景圖片 11 UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeCustom]; 12 btn2.frame = CGRectMake(100, 180, 100, 100); 13 btn2.layer.cornerRadius = 50; 14 btn2.backgroundColor = [UIColor blueColor]; 15 [self.view addSubview:btn2]; 16 btn2.clipsToBounds = YES; 17 18 //3.UIImageView 設置了圖片+背景色; ==========》 觸發了離屏渲染 19 UIImageView *img1 = [[UIImageView alloc]init]; 20 img1.frame = CGRectMake(100, 320, 100, 100); 21 img1.backgroundColor = [UIColor blueColor]; 22 [self.view addSubview:img1]; 23 img1.layer.cornerRadius = 50; 24 img1.layer.masksToBounds = YES; 25 img1.image = [UIImage imageNamed:@"btn.png"]; 26 27 //4.UIImageView 只設置了圖片,無背景色; 28 UIImageView *img2 = [[UIImageView alloc]init]; 29 img2.frame = CGRectMake(100, 480, 100, 100); 30 [self.view addSubview:img2]; 31 img2.layer.cornerRadius = 50; 32 img2.layer.masksToBounds = YES; 33 img2.image = [UIImage imageNamed:@"btn.png"];
//5.UIView 多個貼在 self.view 上
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(100, 600, 100, 100)];
view1.backgroundColor = [UIColor blackColor];
view1.layer.cornerRadius = 50;
view1.clipsToBounds = YES;
[self.view addSubview:view1];
UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(100, 600, 60, 60)];
view2.backgroundColor = [UIColor whiteColor];
view2.layer.cornerRadius = 30;
view2.clipsToBounds = YES;
[self.view addSubview:view2];
運行,開啟模擬器的查看離屏渲染觸發功能:可看到 1 和 3 兩段代碼觸發了離屏渲染()。他們都開了圓角,卻並沒有都觸發離屏渲染,這是為什么呢?
一、渲染簡圖 - 普通 & 離屏
普通渲染:普通情況下,我們的APP的渲染流程是,CPU/GPU 合作,不斷的將需要渲染的內容數據存到幀緩存區(Frame Buffer),顯示則是不斷地從幀緩存區取出數據進行顯示。多個view 每個都獨立 一層一層貼 在主view上。
離屏渲染:觸發離屏渲染時,數據不會直接存到幀緩存區,而是先放在 另外開辟了一塊緩存區空間 --> 離屏緩存區(offscreen Buffer),在不斷地將數據存到離屏緩存區,之后將幾個圖層進行合並疊加,然后通過幀緩存區,再進行顯示。一個view上多個圖層。
二、離屏渲染
1)為什么離屏渲染呢?例子:我們要顯示一個 button --> 背景粉色、有顆球的背景圖、圓角4 --> 這些圖層是不能一個個單獨的顯示在屏幕,需要三個疊加,那么疊加處理前 就要有個地方先存放它們 --> offscreen Buffer --> 三個都處理好了,然后一起疊加、處理 --> 到幀緩存區 --> 顯示。 (這里的圖層疊加並非是一層層獨立 view 的疊加,而是它們三個在同一層layer上)
張圖:
“不能單獨的顯示在屏幕”是設么意思呢?在腦海里想象一下,你想要做幅畫,它要長成下面描述的樣子,首先一層綠色的樹背景、背景上有棵大樹干、樹根旁有只貓,最后要把這個圖裁成圓形的。我們是不是要至少畫三個元素(綠色、樹、貓)合在一起才能呈現出這幅畫,然后一剪刀將其裁成的圓。畫紙便是我們的一層 view。“一張畫紙上畫多種元素” 區別於 “每張畫紙畫一種元素多張畫紙疊加”。
你可能要說那他為什么非要觸發離屏渲染呢?離屏渲染又要消耗我們的性能,我不可以直接一層一層的 view 擺放在屏幕上嗎?當然可以,但這個的前提是你要向 UI 要張圓形的圖片嘛,我們暫不考慮這個,沒有現成的圖的情況下我們是需要自己裁的。
2)離屏渲染為什么會引起性能問題呢?
1、要額外存儲空間,大量的離屏渲染會使內存壓力會比較大
2、offscreen Buffer --> frame Buffer 這個過程是需要一定時間的
3、離屏緩存區的空間是有他的大小限制的:屏幕的2.5倍(像素點)
3)離屏渲染的大量使用,會造成性能問題,可能會引起掉幀卡頓現象,那么為什么要用離屏渲染呢?
1、不得不使用:我們所需要的的一些特殊效果特效,並不能一次性一個圖層就得到想要的結果的,必須要多個圖層一起實現,不得不。
2、效率:特殊效果的復用。多次出現的一個動畫特效,渲染結果保存在離屏緩存區里面,用的時候直接取。
到這里,我們再考慮下為什么切圓角沒有都觸發離屏渲染呢?
圖層只有一層時(一個顏色or一張背景圖...),我們對其渲染切圓角后直接就可以將其顯示在屏幕中了,不需要有另外的圖片或者其他什么和他混在一起的效果圖,那么何必將其放在離屏緩存區消耗空間呢。
三、觸發離屏渲染的常見場景
1)系統自動觸發:
1、裁剪layer:例:切圓角 -- view.layer.masksToBounds = YES;(等同於 view.clipsToBounds = YES; )// 觸發離屏渲染原因
圓角效果的幾點實現:
a、按上面代碼直接切
b、貝塞爾曲線畫圓角
c、找UI要切好的圖
2、陰影
3、高斯模糊
4、帶透明度的圖層的重合: 重合部分的顏色是必須要混合計算的
蘋果毛玻璃效果(高斯模糊)的渲染流程圖:
例子:對一張圖片進行模糊處理:
渲染內容-Render Content(存offscreen Buffer) --> 抓取捕獲圖片內容-capture content(存) --> 垂直模糊-vertical blur(存) --> 水平模糊-horizonal blur(存) --> 合成compositiong Pass(四合一) --> 顯示
2)主動觸發:我們在復用時
光柵化:shouldRasterize --> When the value of this property is YES, the layer is rendered as a bitmap in its local coordinate space and then composited to the destination with any other content
當設為YES時會開啟離屏渲染。
shouldRasterize 光柵化使用建議:
1、如果layer不能被復用,不要開啟
2、layer不是靜態的,要時時變化頻繁修改,開啟離屏渲染反而影響效率
3、離屏渲染緩存內容是有時間限制的,當存的內容在100ms內沒有被復用,便會被清理掉,無法復用
4、離屏渲染空間限制,2.5倍的屏幕,超過屏幕像素大小的2.5倍后,也不能繼續復用了