iOS-列表頁的過渡動(dòng)畫

實(shí)現(xiàn)效果

項(xiàng)目開發(fā)過程中,我們?cè)谟行╉撁嬲故局霸⑷瑁枰恍╉撁嫘Ч麃磉^渡粮坞。例如:網(wǎng)絡(luò)數(shù)據(jù)加載出來之前蚊荣,頁面的展示;復(fù)雜的業(yè)務(wù)邏輯處理完成之前的頁面顯示莫杈;上傳文件未完成之前的頁面樣式等等互例。當(dāng)然,需求不同筝闹,其展示樣式也就千變?nèi)f化媳叨。今天來實(shí)現(xiàn)一下上圖所示的功能。
本篇文章关顷,只作為個(gè)人學(xué)習(xí)使用糊秆。
具體實(shí)現(xiàn)代碼如下:
創(chuàng)建UIView的category
UIView+Animated.h文件

#import <UIKit/UIKit.h>

typedef enum {
    TABViewLoadAnimationDefault = 0, //默認(rèn)沒有動(dòng)畫
    TABViewLoadAnimationShort,  //動(dòng)畫先變短再變長(zhǎng)
    TABViewLoadAnimationLong  //動(dòng)畫先變長(zhǎng)再變短
}TABViewLoadAnimationStyle; //view動(dòng)畫類型枚舉

@interface UIView (Animated)

@property (nonatomic) TABViewLoadAnimationStyle loadStyle;

@end

UIView+Animated.m文件

#import "UIView+Animated.h"
#import <objc/runtime.h>

@implementation UIView (Animated)

- (TABViewLoadAnimationStyle)loadStyle {
    NSNumber *value = objc_getAssociatedObject(self, @selector(loadStyle));
    return value.intValue;
}

- (void)setLoadStyle:(TABViewLoadAnimationStyle)loadStyle {
    objc_setAssociatedObject(self, @selector(loadStyle), @(loadStyle), OBJC_ASSOCIATION_ASSIGN);
}

@end

創(chuàng)建UITableView的category
UITableView+Animated.h文件

#import <UIKit/UIKit.h>

typedef enum {
    TABTableViewAnimationDefault = 0, //沒有動(dòng)畫,默認(rèn)
    TABTableViewAnimationStart,  //開始動(dòng)畫
    TABTableViewAnimationEnd  //結(jié)束動(dòng)畫
}TABTableViewAnimationStyle; //table動(dòng)畫狀態(tài)枚舉

@interface UITableView (Animated)

@property (nonatomic) TABTableViewAnimationStyle animatedStyle;

@end

UITableView+Animated.m文件

#import "UITableView+Animated.h"
#import <objc/runtime.h>

@implementation UITableView (Animated)

+ (void)load {
    
    //Ensure that the exchange method executed only once.
    //保證交換方法只執(zhí)行一次
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        
        //Gets the viewDidLoad method to the class,whose type is a pointer to a objc_method structure.
        //獲取到這個(gè)類的viewDidLoad方法议双,它的類型是一個(gè)objc_method結(jié)構(gòu)體的指針
        Method  originMethod = class_getInstanceMethod([self class], @selector(setDelegate:));
        
        //Get the method you created.
        //獲取自己創(chuàng)建的方法
        Method newMethod = class_getInstanceMethod([self class], @selector(tab_setDelegate:));
        
        IMP newIMP = method_getImplementation(newMethod);
        
        BOOL isAdd = class_addMethod([self class], @selector(tab_setDelegate:), newIMP, method_getTypeEncoding(newMethod));
        
        if (isAdd) {
            class_replaceMethod([self class], @selector(setDelegate:), newIMP, method_getTypeEncoding(newMethod));
        }else {
            //exchange
            method_exchangeImplementations(originMethod, newMethod);
        }
    });
}

- (void)tab_setDelegate:(id<UITableViewDelegate>)delegate {
    
    //Ensure that the exchange method executed only once.
    //保證交換方法只執(zhí)行一次
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        
        SEL oldSelector = @selector(tableView:numberOfRowsInSection:);
        SEL newSelector = @selector(tab_tableView:numberOfRowsInSection:);
        Method oldMethod_del = class_getInstanceMethod([delegate class], oldSelector);
        Method oldMethod_self = class_getInstanceMethod([self class], oldSelector);
        Method newMethod = class_getInstanceMethod([self class], newSelector);
        
        // 若未實(shí)現(xiàn)代理方法痘番,則先添加代理方法
        BOOL isSuccess = class_addMethod([delegate class], oldSelector, class_getMethodImplementation([self class], newSelector), method_getTypeEncoding(newMethod));
        if (isSuccess) {
            class_replaceMethod([delegate class], newSelector, class_getMethodImplementation([self class], oldSelector), method_getTypeEncoding(oldMethod_self));
        } else {
            
            // 若已實(shí)現(xiàn)代理方法,則添加 hook 方法并進(jìn)行交換
            BOOL isVictory = class_addMethod([delegate class], newSelector, class_getMethodImplementation([delegate class], oldSelector), method_getTypeEncoding(oldMethod_del));
            if (isVictory) {
                class_replaceMethod([delegate class], oldSelector, class_getMethodImplementation([self class], newSelector), method_getTypeEncoding(newMethod));
            }
        }
    });
    
    [self tab_setDelegate:delegate];
}

#pragma mark -  TABTableViewDelegate

- (NSInteger)tab_tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (tableView.animatedStyle == TABTableViewAnimationStart) {
        return 5;
    }
    return [self tab_tableView:tableView numberOfRowsInSection:section];
}

#pragma mark -  Getter / Setter

- (TABTableViewAnimationStyle)animatedStyle {
    NSNumber *value = objc_getAssociatedObject(self, @selector(animatedStyle));
    
    //動(dòng)畫過程中設(shè)置為不可滾動(dòng)平痰,暫時(shí)不要修改汞舱,滾動(dòng)會(huì)造成一定的問題
    if (value.intValue == 1) {
        self.scrollEnabled = NO;
    }else {
        self.scrollEnabled = YES;
    }
    return value.intValue;
}

- (void)setAnimatedStyle:(TABTableViewAnimationStyle)animatedStyle {
    objc_setAssociatedObject(self, @selector(animatedStyle), @(animatedStyle), OBJC_ASSOCIATION_ASSIGN);
}

@end

創(chuàng)建LGJViewAnimated 繼承自NSObject

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "UIView+Animated.h"

@interface LGJViewAnimated : NSObject

+ (void)startOrEndAnimated:(UITableViewCell *)cell;

/**
 加載CALayer,設(shè)置動(dòng)畫,同時(shí)啟動(dòng)

 @param view 需要?jiǎng)赢嫷膙iew
 @param color 動(dòng)畫顏色
 */
+ (void)initLayerWithView:(UIView *)view withColor:(UIColor *)color;

/**
 根據(jù)動(dòng)畫類型設(shè)置對(duì)應(yīng)基礎(chǔ)動(dòng)畫

 @param style 動(dòng)畫類型
 @return 動(dòng)畫
 */
+ (LGJViewAnimated *)scaleXAnimation:(TABViewLoadAnimationStyle)style;

@end


#import "LGJViewAnimated.h"
#import "TABMethod.h"
#import "UITableView+Animated.h"

@implementation LGJViewAnimated

+ (void)startOrEndAnimated:(UITableViewCell *)cell {
    
    UITableView *superView = (UITableView *)cell.superview;
    
    switch (superView.animatedStyle) {
            
        case TABTableViewAnimationStart:
            
            //添加并開啟動(dòng)畫
            for (int i = 0; i < cell.contentView.subviews.count; i++) {
                UIView *v = cell.contentView.subviews[i];
                if ( v.loadStyle != TABViewLoadAnimationDefault ) {
                    [TABViewAnimated initLayerWithView:v withColor:kBackColor];
                }
            }
            break;
            
        case TABTableViewAnimationEnd:
            
            if ( cell.contentView.subviews.count > 0 ) {
                
                //移除動(dòng)畫圖層
                for (int i = 0; i < cell.contentView.subviews.count; i++) {
                    
                    UIView *v = cell.contentView.subviews[i];
                    
                    if ( v.layer.sublayers.count > 0 ) {
                        
                        NSArray<CALayer *> *subLayers = v.layer.sublayers;
                        NSArray<CALayer *> *removedLayers = [subLayers filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id  _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
                            
                            if ([evaluatedObject isKindOfClass:[CALayer class]]) {
                                
                                //找出CALayer是你需要移除的,這里根據(jù)背景色來判斷的
                                CALayer *layer = (CALayer *)evaluatedObject;
                                if (CGColorEqualToColor(layer.backgroundColor,kBackColor.CGColor)) {
                                    return YES;
                                }
                                return NO;
                            }
                            return NO;
                        }]];
                        
                        [removedLayers enumerateObjectsUsingBlock:^(CALayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                            [obj removeFromSuperlayer];
                        }];
                    }
                }
            }
            break;
            
        default:
            break;
    }
}

+ (void)initLayerWithView:(UIView *)view withColor:(UIColor *)color {
    
    CALayer *layer = [[CALayer alloc]init];
    layer.frame = CGRectMake(0, 0, view.frame.size.width, view.frame.size.height);
    layer.backgroundColor = kBackColor.CGColor;
    layer.anchorPoint = CGPointMake(0, 0);
    layer.position = CGPointMake(0, 0);
    // 添加一個(gè)基本動(dòng)畫
    [layer addAnimation:[self scaleXAnimation:view.loadStyle] forKey:@"scaleAnimation"];
    
    [view.layer addSublayer:layer];
}

+ (CABasicAnimation *)scaleXAnimation:(TABViewLoadAnimationStyle)style {
    
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.scale.x"];
    anim.removedOnCompletion = NO;
    anim.duration = 0.4;
    anim.autoreverses = YES;  //往返都有動(dòng)畫
    anim.repeatCount = MAXFLOAT;  //執(zhí)行次數(shù)
    
    switch (style) {
            
        case TABViewLoadAnimationShort:{
            anim.toValue = @0.6;
        }
        break;
            
        case TABViewLoadAnimationLong:{
            anim.toValue = @1.6;
        }
        break;
            
        default:{
            anim.toValue = @0.6;
        }
    }
    
    return anim;
}

@end

以上就是具體實(shí)現(xiàn)工作過程宗雇,那么怎么用呢兵拢?很簡(jiǎn)單往扔,我們只需要在需要此功能列表的cell 的layoutSubviews方法的最后加入代碼

    //運(yùn)行動(dòng)畫/移除動(dòng)畫
    [TABViewAnimated startOrEndAnimated:self];

在需要開始動(dòng)畫的時(shí)刻執(zhí)行

        _mainTV.animatedStyle = TABTableViewAnimationStart;  //開啟動(dòng)畫

在需要結(jié)束動(dòng)畫的時(shí)刻執(zhí)行

    //停止動(dòng)畫
    _mainTV.animatedStyle = TABTableViewAnimationEnd;

即可入挣。
另外霹购,在動(dòng)畫頁面顯示的時(shí)候盡量不要做給cell賦值的操作

    //在加載動(dòng)畫的時(shí)候宦芦,即未獲得數(shù)據(jù)時(shí)嘹履,不要走加載控件數(shù)據(jù)的方法
    if (_mainTV.animatedStyle != TABTableViewAnimationStart) {
        [cell initWithData:dataArray[indexPath.row]];
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末腻扇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子砾嫉,更是在濱河造成了極大的恐慌幼苛,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件焕刮,死亡現(xiàn)場(chǎng)離奇詭異舶沿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)配并,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門括荡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人溉旋,你說我怎么就攤上這事畸冲。” “怎么了观腊?”我有些...
    開封第一講書人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵邑闲,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我梧油,道長(zhǎng)苫耸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任儡陨,我火速辦了婚禮褪子,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘迄委。我一直安慰自己褐筛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開白布叙身。 她就那樣靜靜地躺著渔扎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪信轿。 梳的紋絲不亂的頭發(fā)上晃痴,一...
    開封第一講書人閱讀 52,184評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音财忽,去河邊找鬼倘核。 笑死,一個(gè)胖子當(dāng)著我的面吹牛即彪,可吹牛的內(nèi)容都是我干的紧唱。 我是一名探鬼主播活尊,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼漏益!你這毒婦竟也來了蛹锰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤绰疤,失蹤者是張志新(化名)和其女友劉穎铜犬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體轻庆,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡癣猾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了余爆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纷宇。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖龙屉,靈堂內(nèi)的尸體忽然破棺而出呐粘,到底是詐尸還是另有隱情,我是刑警寧澤转捕,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布作岖,位于F島的核電站,受9級(jí)特大地震影響五芝,放射性物質(zhì)發(fā)生泄漏痘儡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一枢步、第九天 我趴在偏房一處隱蔽的房頂上張望沉删。 院中可真熱鬧,春花似錦醉途、人聲如沸矾瑰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽殴穴。三九已至,卻和暖如春货葬,著一層夾襖步出監(jiān)牢的瞬間采幌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工震桶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留休傍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓蹲姐,卻偏偏與公主長(zhǎng)得像磨取,于是被迫代替她去往敵國(guó)和親人柿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359