使用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
的最大好處就是能非同步繪製最大程度保持介面流暢,另外可以通過YYLabel
的exclusionPaths
屬性實現縮排動畫。
exclusionPaths
是YYText
的用於設定文字空白區域的陣列,可以存放多個UIBezierPath
型別的元素,即規定的空白區域。
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:想要動態修改YYLabel
的exclusionPaths
,則ignoreCommonProperties
屬性要為 NO。 如果設定為YES,文字顯示的屬性諸如text
、font
、textColor
、attributedText
、lineBreakMode
、exclusionPaths
等將不可用,這是為了提高效能,儘可能將控制元件屬性做靜態處理。
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;
}];
複製程式碼
這樣就可以實現我個人想要的最終效果了,如果還有別的狀態圖示,也是一樣的做法,例如直播狀態:
總結
-
YYLabel
的ignoreCommonProperties
要設定為NO; -
CADisplayLink
要在動畫開始前一刻才開啟,並且記得在結束後關閉; - 動畫過程中要跟蹤的是控制元件的
presentationLayer
,這個才有顯式資訊,本體是一步到位的; - 如果
YYLabel
設定了displaysAsynchronously
為YES,動畫開始前最好設為NO,否則動畫過程中label會不停地閃爍重新整理(非同步繪製後重新整理),動畫結束後才設回YES; - 如果多行顯示不全時結尾無法以省略號顯示,可以參考我上一篇文章:解決YYLabel多行顯示不全時結尾無法以省略號顯示的問題。
可惜的是剛完成這效果,產品就說標題那裡不需要狀態圖示了,也就是白做了~
今時今日YYKit還是很強大實用的?,感謝看到最後 Thanks♪(・ω・)ノ。