1. 程式人生 > Android開發 >使用YYLabel+CADisplayLink實現文字首行縮排的動畫效果

使用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,這樣雖然能達到目的,可是沒有過渡,看上去很生硬,使用者體驗沒那麼好,我個人想要的效果是文字也能跟著控制元件一起過渡變化

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

最終效果

1. YYLabel - exclusionPaths

使用YYLabel的最大好處就是能非同步繪製最大程度保持介面流暢,另外可以通過YYLabelexclusionPaths屬性實現縮排動畫。

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這個庫的動畫本體才是實時變化的)。

新增、移除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,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;
}];
複製程式碼

這樣就可以實現我個人想要的最終效果了,如果還有別的狀態圖示,也是一樣的做法,例如直播狀態:

最終效果

總結

  1. YYLabelignoreCommonProperties要設定為NO;
  2. CADisplayLink要在動畫開始前一刻才開啟,並且記得在結束後關閉;
  3. 動畫過程中要跟蹤的是控制元件的presentationLayer,這個才有顯式資訊,本體是一步到位的;
  4. 如果YYLabel設定了displaysAsynchronously為YES,動畫開始前最好設為NO,否則動畫過程中label會不停地閃爍重新整理(非同步繪製後重新整理),動畫結束後才設回YES;
  5. 如果多行顯示不全時結尾無法以省略號顯示,可以參考我上一篇文章:解決YYLabel多行顯示不全時結尾無法以省略號顯示的問題

可惜的是剛完成這效果,產品就說標題那裡不需要狀態圖示了,也就是白做了~

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