前言
在iOS開發(fā)中,我們經(jīng)常需要給視圖添加陰影效果锈玉,最簡單的方法就是通過設(shè)置CALayer的shadowColor爪飘、shadowOpacity、shadowOffset和shadowRadius這幾個屬性可以很方便的為 UIView 添加陰影效果拉背。但是如果單用這幾個屬性會導(dǎo)致離屏渲染(Offscreen Rendering)师崎,而且CoreAnimation在每一幀繪制陰影的時候都需要遞歸遍歷所有sublayer的alpha通道從而精確的計(jì)算出陰影的輪廓,這是非常消耗性能的椅棺,從而導(dǎo)致了動畫的卡頓犁罩。
為了盡可能地減小離屏渲染帶來的性能影響,我們可以利用CALayer的另外一個屬性shadowPath两疚,這個屬性的官方文檔是這么描述的:
If you specify a value for this property, the layer creates its shadow using the specified path instead of the layer’s composited alpha channel. The path you provide defines the outline of the shadow. It is filled using the non-zero winding rule and the current shadow color, opacity, and blur radius.
可以看到設(shè)置了這個屬性以后CALayer在創(chuàng)建其shadow的時候不在遍歷sublayer的alpha通道床估,而是直接用這個屬性所指定的路徑作為陰影的輪廓,這樣就減少了非常多的計(jì)算量诱渤。
然而這里會有一個問題丐巫,shadowPath并不會跟隨CALayer的bounds屬性進(jìn)行變化,所以在layer的bounds產(chǎn)生變化以后需要手動更新shadowPath才能讓其適配新的bounds。
為了解決這個問題递胧,在使用AutoLayout以前碑韵,因?yàn)閎ounds都是手動計(jì)算出來的,我們可以很容易的直接設(shè)定新的shadowPath缎脾,而使用了AutoLayout以后祝闻,我們則只能在UIView的layoutSubivews
方法中才能獲得更新后的bounds。
而且文檔中還做了如下描述:
Unlike most animatable properties, this property (as with all CGPathRef animatable properties) does not support implicit animation.
這說明該變量是不支持隱式動畫的遗菠,也就是說當(dāng)我們直接設(shè)置CALayer的shadowPath屬性后联喘,系統(tǒng)并不會自動的提交隱式的CATransaction動畫。
為了解決了這個問題辙纬,我們需要通過CABasicAnimation顯示地指定shadowPath的動畫效果豁遭,同時為了和bounds的動畫效果保持一致,我們需要獲取bounds的動畫屬性牲平。
考慮了以上兩點(diǎn)問題以后堤框,我們就可以用如下方法實(shí)現(xiàn)讓CALayer的shadowPath跟隨bounds一起做動畫改變。
要特別注意一點(diǎn)的是纵柿,在iOS8以后bounds的隱式動畫默認(rèn)是開啟additive模式的蜈抓,而CALayer的shadowPath屬性并不支持additive模式,所以如果在前一個shadowPath動畫執(zhí)行完畢前如果提交了新的動畫昂儒,使用本方法將會看到shadowPath和bounds的動畫不一致的現(xiàn)象沟使。在Demo中可快速點(diǎn)擊改變Bounds的按鈕來復(fù)現(xiàn)該現(xiàn)象。
實(shí)現(xiàn)方法
為實(shí)現(xiàn)本文的思路渊跋,我們需要創(chuàng)建一個一個UIView的子類并且重寫其layoutSubivew
方法腊嗡。
// Subclass of UIView
- (void)layoutSubviews {
[super layoutSubviews];
if (self.shouldAnimateShadowPath) {
CAAnimation *animation = [self.layer animationForKey:@"bounds.size"];
if (animation) {
// 通過CABasicAnimation類來為shadowPath添加動畫
CABasicAnimation *shadowPathAnimation = [CABasicAnimation animationWithKeyPath:@"shadowPath"];
// 根據(jù)bounds的動畫屬性設(shè)置shadowPath的動畫屬性
shadowPathAnimation.timingFunction = animation.timingFunction;
shadowPathAnimation.duration = animation.duration;
// iOS8 bounds的隱式動畫默認(rèn)開啟了additive屬性,當(dāng)前一次bounds change的動畫還在進(jìn)行中時拾酝,
// 新的bounds change動畫將會被疊加在之前的上燕少,從而讓動畫更加順滑
// 然而shadowPath并不支持additive animation,所以當(dāng)多個動畫疊加蒿囤,將會看到shadowPath和bounds動畫不一致的現(xiàn)象
// shadowPathAnimation.additive = YES;
// 設(shè)置shadowAnimation的新值客们,未設(shè)置from,則from屬性將默認(rèn)為當(dāng)前shadowPath的狀態(tài)
shadowPathAnimation.toValue = [UIBezierPath bezierPathWithRect:self.layer.bounds];
// 將動畫添加至layer的渲染樹
[self.layer addAnimation:shadowPathAnimation forKey:@"shadowPath"];
}
// 根據(jù)蘋果文檔指出的材诽,顯式動畫只會影響動畫效果底挫,而不會影響屬性的的值,所以這兩為了持久化shadowPath的改變需要進(jìn)行屬性跟新
// 同時也處理了bounds非動畫改變的情況
self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath;
}
}
Demo
本文Demo地址為ShadowPathAnimationDemo
可以看到當(dāng)關(guān)閉shadowPath動畫脸侥,也就是不執(zhí)行上述代碼的時候建邓,當(dāng)view的bounds改變以后shadowPath還為原來的值,并未跟隨bounds進(jìn)行改變睁枕。
可以看到當(dāng)打開shadowPath動畫官边,也就是要執(zhí)行了上述代碼時沸手,當(dāng)view的bounds改變以后shadowPath能夠跟隨一起改變,而且動畫效果相同拒逮。
本文個人博客地址: http://wty.im/2016/09/26/let-shadow-path-animate-with-layer-bounds/
Github: https://github.com/wty21cn/