使用YYLabel+CADisplayLink實現文本首行縮進的動畫效果

公司有個需求,點擊關注,標題處要有個已關注的圖標提示,標題文本要根據是否已關注做出位置調整。數組

關注先後

這種需求能夠經過富文本設置首行縮進距離 parag.firstLineHeadIndent 來進行調整:異步

NSMutableParagraphStyle *parag = [[NSMutableParagraphStyle alloc] init];
parag.firstLineHeadIndent = _isFollowed ? 100 : 0;
NSDictionary *attDic = @{NSFontAttributeName: font,
                         NSForegroundColorAttributeName: WTVPUGCProfilePlayView.videoTitleColor,
                         NSParagraphStyleAttributeName: parag};
NSAttributedString *attStr = [[NSAttributedString alloc] initWithString:videoTitle attributes:attDic];
self.titleLabel.attributedText = attStr;
複製代碼

因爲關注按鈕點擊後應該要有相應的狀態更新,若是使用這種作法進行刷新,直接從新設置attributedText,這樣雖然能達到目的,但是沒有過渡,看上去很生硬,用戶體驗沒那麼好,我我的想要的效果是文字也能跟着控件一塊兒過渡變化ide

這裏介紹一下使用YYLabel+CADisplayLink來實現該效果: oop

最終效果

1. YYLabel - exclusionPaths

使用YYLabel的最大好處就是能異步繪製最大程度保持界面流暢,另外能夠經過YYLabelexclusionPaths屬性能夠實現縮進動畫。post

exclusionPathsYYText的用於設置文本空白區域的數組,能夠存放多個UIBezierPath類型的元素,即規定的空白區域。 性能

紅色框就是exclusionPaths設置的區域
對於首行縮進,只要建立一個原點爲(0, 0),寬度爲已關注圖標的最大x值+間距,高度不超過行高(寬高都不能爲0,必須大於0,不然無效果)的 UIBezierPath,丟進數組,設置一下 exclusionPaths便可實現:

// 刷新方法
- (void)updateTitleLabelExclusionPaths {
    if (self.pursueView) {  // 已關注
        // 1.獲取圖標最大x值+間距
        CGFloat w = self.pursueView.jp_maxX + _subviewSpace; 
        // 2.刷新 exclusionPaths。
        self.titleLabel.exclusionPaths = @[[UIBezierPath bezierPathWithRect:CGRectMake(0, 0, w, 1)]];
    } else {  // 沒有關注
        // 移除 exclusionPaths
        self.titleLabel.exclusionPaths = nil; 
    }
}
複製代碼

PS:想要動態修改YYLabelexclusionPaths,則ignoreCommonProperties屬性要爲 NO。 若是設置爲YES,文本顯示的屬性諸如textfonttextColorattributedTextlineBreakModeexclusionPaths等將不可用,這是爲了提升性能,儘量將控件屬性作靜態處理。優化

2. CADisplayLink

配合CADisplayLink,用於動畫過程當中跟蹤已關注圖標的位置變化,不過動畫過程監聽的並非圖標控件自身的屬性,而是圖標控件的layer.presentationLayer動畫

presentationLayer是用於實時獲取動畫過程當中的layout信息,若是控件不是在動畫過程當中,該屬性爲nil(系統的動畫API都是經過這個「假」的presentationLayer來呈現動畫的,本體是直接就到了最終位置的,若是是POP這個庫的動畫本體纔是實時變化的)。ui

添加、移除CADisplayLink:
- (void)addLink {
    [self removeLink];
    // 執行updateTitleLabelExclusionPaths進行刷新
    self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateTitleLabelExclusionPaths)];
    [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)removeLink {
    if (self.link) {
        [self.link invalidate];
        self.link = nil;
    }
}
複製代碼
另外刷新方法得修改一下:
- (void)updateTitleLabelExclusionPaths {
    if (self.pursueView) {  // 已關注
        // 1.獲取圖標最大x值+間距
        CGFloat w = _subviewSpace
        if (self.link) {  // 若是CADisplayLink存在,說明是在動畫過程當中
            w += self.pursueView.layer.presentationLayer.jp_maxX;
        } else {
            w += self.pursueView.jp_maxX;
        }
        // 2.刷新 exclusionPaths。
        self.titleLabel.exclusionPaths = @[[UIBezierPath bezierPathWithRect:CGRectMake(0, 0, w, 1)]];
    } else {  // 沒有關注
        // 移除 exclusionPaths
        self.titleLabel.exclusionPaths = nil; 
    }
}
複製代碼
點擊關注/取關按鈕觸發的動畫方法:
CGFloat alpha = 0;
CGFloat x = 0;
if (isFollowed) {
    if (!self.followedView) [self createFollowedView];
    alpha = 1;
} else {
    x -= (self.followedView.jp_width + _subviewSpace); // 非關注就挪開
}
    
self.pursueView = self.followedView; // 標記跟蹤的圖標

// 非關注 --> 已關注 的初始化
if (isFollowed) {
    self.followedView.alpha = 0;
    self.followedView.jp_x = x - (self.followedView.jp_width + _subviewSpace);
}

// 0.動畫過程當中得關閉displaysAsynchronously屬性,由於這是異步繪製,若是爲YES則label會不停地閃爍刷新
self.titleLabel.displaysAsynchronously = NO;

// 1.動畫開始前一刻添加CADisplayLink,開始跟蹤
[self addLink];

// 2.開始動畫
[UIView animateWithDuration:0.45 delay:0 usingSpringWithDamping:0.8 initialSpringVelocity:1.0 options:kNilOptions animations:^{
    self.followedView.alpha = alpha;
    self.followedView.jp_x = x;
} completion:^(BOOL finished) {
    // 3.移除CADisplayLink,中止跟蹤
    [self removeLink];
    // 4.最終調整
    [self updateTitleLabelExclusionPaths];
    // 5.從新開啓異步繪製(滑動優化)
    self.titleLabel.displaysAsynchronously = YES;
}];
複製代碼

這樣就能夠實現我我的想要的最終效果了,若是還有別的狀態圖標,也是同樣的作法,例如直播狀態: spa

最終效果

總結

  1. YYLabelignoreCommonProperties要設置爲NO;
  2. CADisplayLink要在動畫開始前一刻纔開啓,而且記得在結束後關閉;
  3. 動畫過程當中要跟蹤的是控件的presentationLayer,這個纔有顯式信息,本體是一步到位的;
  4. 若是YYLabel設置了displaysAsynchronously爲YES,動畫開始前最好設爲NO,不然動畫過程當中label會不停地閃爍刷新(異步繪製後刷新),動畫結束後才設回YES;
  5. 若是多行顯示不全時結尾沒法以省略號顯示,能夠參考我上一篇文章:解決YYLabel多行顯示不全時結尾沒法以省略號顯示的問題

惋惜的是剛完成這效果,產品就說標題那裏不須要狀態圖標了,也就是白作了~

今時今日YYKit仍是很強大實用的👍,感謝看到最後 Thanks♪(・ω・)ノ。

相關文章
相關標籤/搜索