IOS實(shí)現(xiàn)碎片化動(dòng)畫詳解
碎片化效果圖

遮罩視圖
在UIView中有一個(gè)maskView屬性,這個(gè)屬性是我們今天實(shí)現(xiàn)動(dòng)畫的最重要的變量。這個(gè)屬性在iOS8之后開始使用,用來表示視圖的遮罩。什么是遮罩呢?我想了很久都沒有找到合適的比喻來介紹這個(gè)。簡單來說,一個(gè)UIView的對(duì)象,可以通過設(shè)置alpha來改變這個(gè)視圖的透明度,遮罩的實(shí)現(xiàn)效果也是一樣的。唯一的差別在于前者是通過修改0~1之間的值來改變透明效果,作為遮罩的視圖對(duì)象的backgroundColor、alpha、transform等等屬性都會(huì)影響到被遮蓋的視圖的透明效果。
例如下面這段代碼:
UIView * viewContainer = [[UIView alloc] initWithFrame: CGRectMake(0, 0, 200, 200)]; viewContainer.backgroundColor = [UIColor blueColor]; UIView * contentView = [[UIView alloc] initWithFrame: CGRectMake(20, 20, 160, 160)]; contentView.backgroundColor = [UIColor redColor]; [viewContainer addSubview: contentView]; UIView * maskView = [[UIView alloc] initWithFrame: CGRectMake(100, 100, 35, 80)]; maskView.backgroundColor = [UIColor yellowColor]; contentView.maskView = maskView;
遮罩視圖決定了視圖的顯示內(nèi)容
上面的代碼小小的改動(dòng)一下,我們分別修改一下maskView和contentView的透明度,看看在遮罩透明度改變之后紅色的視圖會(huì)發(fā)生什么變化:

修改透明度.png
通過實(shí)驗(yàn)我們可以看到修改視圖自身的透明度或者修改maskView的透明度達(dá)成的效果是一樣的。換句話說,遮蓋視圖對(duì)于視圖自身的影響直接決定在透明度和顯示尺寸這兩個(gè)可視的屬性。
那么,遮蓋視圖除了alpha屬性外,還有什么屬性影響了視圖本身的顯示效果呢?
顏色
上面的透明度效果得出了一個(gè)結(jié)論。視圖本身的顯示效果取決于maskView的透明程度。在顏色不含透明空間的時(shí)候,視圖是不存在透明效果的。但是假設(shè)我們設(shè)置遮罩視圖的顏色透明度時(shí):
maskView.backgroundColor = [UIColor colorWithWhite: 1 alpha: 0.5]; //任意顏色
顯示的效果跟直接設(shè)置alpha = 0.5的效果是一樣的。在繪制像素到屏幕上中可以獲知顏色渲染和alpha屬性存在的關(guān)聯(lián)
maskView的子視圖
maskView.backgroundColor = [UIColor clearColor]; UIView * sub1 = [[UIView alloc] initWithFrame: CGRectMake(0, 0, 20, 34)]; sub1.backgroundColor = [UIColor blackColor]; UIView * sub2 = [[UIView alloc] initWithFrame: CGRectMake(15, 18, 33, 40)]; sub2.backgroundColor = [UIColor blackColor]; [maskView addSubview: sub1]; [maskView addSubview: sub2];
要了解maskView的子視圖對(duì)遮罩效果的影響,我們需要排除遮罩視圖自身的干擾,因此maskView的背景顏色要設(shè)置成透明色

子視圖對(duì)于遮罩的影響
可以看到,在遮罩自身透明的情況下,子視圖也可以實(shí)現(xiàn)部分遮罩視圖的效果。因此如果我們改變這些子視圖的透明度的時(shí)候,遮罩效果也同樣會(huì)發(fā)生改變
動(dòng)畫實(shí)現(xiàn)
回到上面展示的動(dòng)畫效果,我們可以看到圖片被分割成多個(gè)長方形的小塊逐漸消失。其中,垂直方向分為上下兩份,橫向大概有15份左右。因此我們需要現(xiàn)在maskView上面添加2*15個(gè)子視圖,均勻分布。為了保證在動(dòng)畫的時(shí)候我們能依次實(shí)現(xiàn)子視圖的隱藏,我們需要給子視圖加上標(biāo)識(shí):
UIView * maskView = [[UIView alloc] initWithFrame: contentView.bounds];
const NSInteger horizontalCount = 15;
const NSInteger verticalCount = 2;
const CGFloat fadeWidth = CGRectGetWidth(maskView.frame) / horizontalCount;
const CGFloat fadeHeight = CGRectGetHeight(maskView.frame) / verticalCount;
for (NSInteger line = 0; line < horizontalCount; line ++) {
for (NSInteger row = 0; row < verticalCount; row++) {
CGRect frame = CGRectMake(line*fadeWidth, row*fadeHeight, fadeWidth, fadeHeight);
UIView * fadeView = [[UIView alloc] initWithFrame: frame];
fadeView.tag = [self viewTag: line*verticalCount+row];
fadeView.backgroundColor = [UIColor whiteColor];
[maskView addSubview: fadeView];
}
}
contentView.maskView = maskView;
那么在動(dòng)畫開始的時(shí)候,我們需要依次遍歷maskView上面的所有子視圖,并且讓他們依次執(zhí)行動(dòng)畫:
for (NSInteger line = 0; line < horizontalCount; line ++) {
for (NSInteger row = 0; row < verticalCount; row++) {
NSInteger idx = line*verticalCount+row;
UIView * fadeView = [contentView.maskView viewWithTag: [self viewWithTag: idx];
[UIView animateWithDuration: fadeDuration delay: interval*idx options: UIViewAnimationOptionCurveLinear animations: ^{
fadeView.alpha = 0;
} completion: nil];
}
}
我們在實(shí)現(xiàn)動(dòng)畫的同時(shí),都應(yīng)該考慮如何把動(dòng)畫封裝出來方便以后復(fù)用。上面的碎片化動(dòng)畫完全可以作為UIView的category進(jìn)行封裝,以此來降低入侵性,實(shí)現(xiàn)低耦合的要求:
#define LXDMAXDURATION 1.2 #define LXDMINDURATION .2 #define LXDMULTIPLED .25 @interface UIView (LXDFadeAnimation) /*! * @brief 視圖是否隱藏 */ @property (nonatomic, assign, readonly) BOOL isFade; /*! * @brief 是否處在動(dòng)畫中 */ @property (nonatomic, assign, readonly) BOOL isFading; /*! * @brief 垂直方塊個(gè)數(shù)。默認(rèn)為3 */ @property (nonatomic, assign) NSInteger verticalCount; /*! * @brief 水平方塊個(gè)數(shù)。默認(rèn)為18 */ @property (nonatomic, assign) NSInteger horizontalCount; /*! * @brief 方塊動(dòng)畫之間的間隔0.2~1.2。默認(rèn)0.7 */ @property (nonatomic, assign) NSTimeInterval intervalDuration; /*! * @brief 每個(gè)方塊隱藏的動(dòng)畫時(shí)間0.05~0.3,最多為動(dòng)畫時(shí)長的25%。默認(rèn)為0.175 */ @property (nonatomic, assign) NSTimeInterval fadeAnimationDuration; - (void)configurateWithVerticalCount: (NSInteger)verticalCount horizontalCount: (NSInteger)horizontalCount interval: (NSTimeInterval)interval duration: (NSTimeInterval)duration; - (void)reverseWithComplete: (void(^)(void))complete; - (void)animateFadeWithComplete: (void(^)(void))complete; - (void)reverseWithoutAnimate; @end
在iOS中,在category中聲明的所有屬性編譯器都不會(huì)自動(dòng)綁定getter和setter方法,這意味著我們需要重寫這兩種方法,而且還不能使用下劃線+變量名的方式直接訪問變量。因此我們需要導(dǎo)入objc/runtime.h文件使用動(dòng)態(tài)時(shí)提供的objc_associateObject機(jī)制來為視圖動(dòng)態(tài)增加屬性:
- (BOOL)isFade
{
return [objc_getAssociatedObject(self, kIsFadeKey) boolValue];
}
// other getAssociatedObject method
- (void)setIsFade: (BOOL)isFade
{
objc_setAssociatedObject(self, kIsFadeKey, @(isFade), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// other setAssociatedObject method
有了碎片化隱藏視圖的動(dòng)畫,同樣需要一個(gè)還原的動(dòng)畫效果:
NSInteger fadeCount = self.verticalCount * self.horizontalCount;
for (NSInteger idx = fadeCount - 1; idx >= 0; idx--) {
UIView * subview = [self.maskView viewWithTag: [self subViewTag: idx]];
[UIView animateWithDuration: self.fadeAnimationDuration delay: self.intervalDuration * (fadeCount - 1 - idx) options: UIViewAnimationOptionCurveLinear animations: ^{
subview.alpha = 1;
} completion: nil];
}
現(xiàn)在我們還要考慮一個(gè)問題:假設(shè)用戶點(diǎn)擊某張圖片的時(shí)候就根據(jù)視圖是否隱藏狀態(tài)來開始隱藏/顯示的動(dòng)畫,當(dāng)用戶多次點(diǎn)擊的時(shí)候,我們應(yīng)該判斷是否已經(jīng)處在動(dòng)畫狀態(tài),如果是,那么不繼續(xù)執(zhí)行動(dòng)畫代碼。另外,在動(dòng)畫開始之前,我們需要把標(biāo)識(shí)動(dòng)畫狀態(tài)的isFading設(shè)為YES,但是由于每個(gè)方塊隱藏都存在一個(gè)動(dòng)畫,動(dòng)畫的結(jié)束時(shí)間應(yīng)該怎么判斷呢?已知fadeView的個(gè)數(shù)是count,那么當(dāng)最后一個(gè)方塊隱藏即是第count個(gè)動(dòng)畫完成的時(shí)候,整個(gè)碎片化動(dòng)畫就結(jié)束了。所以我們需要借助一個(gè)臨時(shí)變量來記錄:
__block NSInteger timeCount = 0;
//......
[UIView animateWithDuration: self.fadeAnimationDuration delay: self.intervalDuration * (fadeCount - 1 - idx) options: UIViewAnimationOptionCurveLinear animations: ^{
subview.alpha = 1;
} completion: ^(BOOL finished) {
if (++timeCount == fadeCount) {
self.isFade = NO;
self.isFading = NO;
if (complete) { complete(); }
}
}];
//......
得到動(dòng)畫結(jié)束的時(shí)間后,我們就可以增加一個(gè)block提供給調(diào)用者在動(dòng)畫結(jié)束時(shí)進(jìn)行其他的處理。
輪播碎片動(dòng)畫
在知道了碎片動(dòng)畫的實(shí)現(xiàn)之后,我要做一個(gè)酷炫的廣告輪播頁。同樣采用category的方式來實(shí)現(xiàn)。現(xiàn)在放上效果圖:

廣告輪播頁
那么實(shí)現(xiàn)一個(gè)廣告頁輪播需要哪些步驟呢?
1、在當(dāng)前動(dòng)畫的圖片下面插入一個(gè)UIImageView來展示下一張圖片。如果可以,盡量復(fù)用這個(gè)imageView
2、添加UIPageControl來標(biāo)識(shí)圖片的下標(biāo)
因此我提供了一個(gè)接口傳入圖片數(shù)組執(zhí)行動(dòng)畫:
// 獲取動(dòng)態(tài)綁定臨時(shí)展示的UIImageView
- (UIImageView *)associateTempBannerWithImage: (UIImage *)image
{
UIImageView * tempBanner = objc_getAssociatedObject(self, kTempImageKey);
if (!tempBanner) {
tempBanner = [[UIImageView alloc] initWithFrame: self.frame];
objc_setAssociatedObject(self, kTempImageKey, tempBanner, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self.superview insertSubview: tempBanner belowSubview: self];
}
tempBanner.image = image;
return tempBanner;
}
此外,pageControl一開始我加在執(zhí)行動(dòng)畫的imageView上面,但是在動(dòng)畫執(zhí)行到一半的時(shí)候,pageControl也會(huì)隨著局部隱藏動(dòng)畫隱藏起來。因此根據(jù)imageView當(dāng)前的坐標(biāo)重新計(jì)算出合適的尺寸范圍:
- (void)associatePageControlWithCurrentIdx: (NSInteger)idx
{
UIPageControl * pageControl = objc_getAssociatedObject(self, kPageControlKey);
if (!pageControl) {
pageControl = [[UIPageControl alloc] initWithFrame: CGRectMake(self.frame.origin.x, CGRectGetHeight(self.frame) - 37 + self.frame.origin.y, CGRectGetWidth(self.frame), 37)];
[self.superview addSubview: pageControl];
pageControl.numberOfPages = self.bannerImages.count;
objc_setAssociatedObject(self, kPageControlKey, pageControl, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
pageControl.currentPage = idx;
}
由于每次圖片碎片化動(dòng)畫執(zhí)行完成之后,都需要再次執(zhí)行相同的碎片動(dòng)畫代碼。而動(dòng)畫結(jié)束是通過block執(zhí)行,即我們需要在block中嵌套使用同一個(gè)block,因此首先我們需要把這段執(zhí)行代碼聲明成一個(gè)block變量。另外,需要一個(gè)聲明一個(gè)idx在每次碎片動(dòng)畫完成的時(shí)候更新圖片,用__block修飾來讓我們在回調(diào)中修改這個(gè)值:
- (void)fadeBanner
NSParameterAssert(self.superview);
UIImageView * tempBanner = [self associateTempBannerWithImage: [UIImage imageNamed: self.bannerImages[1]]];
self.stop = NO;
__block NSInteger idx = 0;
__weak typeof(self) weakSelf = self;
[self associatePageControlWithCurrentIdx: idx];
void (^complete)() = ^{
NSInteger updateIndex = [weakSelf updateImageWithCurrentIndex: ++idx tempBanner: tempBanner];
idx = updateIndex;
[weakSelf associatePageControlWithCurrentIdx: idx];
};
// 保存block并執(zhí)行動(dòng)畫
objc_setAssociatedObject(self, kCompleteBlockKey, complete, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self animateFadeWithComplete: ^{
if (!self.stop) {
complete();
}
}];
}
// 更新展示的圖片,并且返回下一次要展示的圖片下標(biāo)
- (NSInteger)updateImageWithCurrentIndex: (NSInteger)idx tempBanner: (UIImageView *)tempBanner
{
if (idx >= self.bannerImages.count) { idx = 0; }
self.image = [UIImage imageNamed: self.bannerImages[idx]];
[self reverseWithoutAnimate];
NSInteger nextIdx = idx + 1;
if (nextIdx >= self.bannerImages.count) { nextIdx = 0; }
tempBanner.image = [UIImage imageNamed: self.bannerImages[nextIdx]];
[self animateFadeWithComplete: ^{
if (!self.stop) {
void (^complete)() = objc_getAssociatedObject(self, kCompleteBlockKey);
complete();
}
}];
return idx;
}
代碼中需要注意的是,我在上面使用objc_Associate的機(jī)制保存了這個(gè)完成回調(diào)的block,這個(gè)是必要的。假設(shè)你不喜歡把更新圖片的代碼封裝出來,直接把這一步驟放到上面的complete聲明中,依舊還是要?jiǎng)討B(tài)保存起來,否則這個(gè)block執(zhí)行到第三次圖片碎片的時(shí)候就會(huì)被釋放從而導(dǎo)致崩潰
別忘了在每次圖片切換完成之后,將所有的子視圖遮罩還原,并且更新圖片顯示
- (void)reverseWithoutAnimate
{
if (self.isFading) {
NSLog(@"It's animating!");
return;
}
for (UIView * subview in self.maskView.subviews) {
subview.alpha = 1;
}
}
總結(jié)
以上就是關(guān)于IOS實(shí)現(xiàn)碎片化動(dòng)畫的全部內(nèi)容,希望本文的內(nèi)容對(duì)大家開發(fā)IOS動(dòng)畫的時(shí)候能有所幫助。
- iOS動(dòng)畫特效之立方體翻轉(zhuǎn)
- iOS微信第三方登錄實(shí)現(xiàn)
- 微信支付開發(fā)IOS圖文教程案例
- 手把手教你實(shí)現(xiàn)微信小視頻iOS代碼實(shí)現(xiàn)
- jQuery實(shí)現(xiàn)切換頁面過渡動(dòng)畫效果
- 多圖展示滑動(dòng)過渡效果
- Android實(shí)現(xiàn)圖片反轉(zhuǎn)、翻轉(zhuǎn)、旋轉(zhuǎn)、放大和縮小
- javafx實(shí)現(xiàn)圖片3D翻轉(zhuǎn)效果方法實(shí)例
- IOS實(shí)戰(zhàn)之自定義轉(zhuǎn)場動(dòng)畫詳解
- IOS實(shí)現(xiàn)微信朋友圈相冊評(píng)論界面的翻轉(zhuǎn)過渡動(dòng)畫
相關(guān)文章
IOS 開發(fā)之查看大圖的實(shí)現(xiàn)代碼
這篇文章主要介紹了IOS 開發(fā)之查看大圖的實(shí)現(xiàn)代碼的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-10-10
iOS開發(fā)之tableView點(diǎn)擊下拉擴(kuò)展與內(nèi)嵌collectionView上傳圖片效果
這篇文章主要介紹了iOS開發(fā)之tableView點(diǎn)擊下拉擴(kuò)展與內(nèi)嵌collectionView上傳圖片效果的相關(guān)資料,需要的朋友可以參考下2016-04-04
在iOS10系統(tǒng)中微信后退無法發(fā)起ajax請(qǐng)求的問題解決辦法
這篇文章主要介紹了在iOS10系統(tǒng)中微信后退無法發(fā)起ajax請(qǐng)求的問題解決辦法,一般可以通過延時(shí)發(fā)送請(qǐng)求解決,下面通過本文給大家分享下解決辦法,需要的朋友參考下吧2017-01-01
iOS基于UIScrollView實(shí)現(xiàn)滑動(dòng)引導(dǎo)頁
這篇文章主要為大家詳細(xì)介紹了iOS基于UIScrollView實(shí)現(xiàn)滑動(dòng)引導(dǎo)頁的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
IOS 陀螺儀開發(fā)(CoreMotion框架)實(shí)例詳解
這篇文章主要介紹了IOS 陀螺儀開發(fā)實(shí)例詳解的相關(guān)資料,介紹了螺旋儀參數(shù)意義及CoreMotion框架,需要的朋友可以參考下2016-10-10

