公司有個需求,點擊關注,標題處要有個已關注的圖標提示,標題文本要根據是否已關注做出位置調整。數組
這種需求能夠經過富文本設置首行縮進距離 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
使用YYLabel
的最大好處就是能異步繪製最大程度保持界面流暢,另外能夠經過YYLabel
的exclusionPaths
屬性能夠實現縮進動畫。post
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
等將不可用,這是爲了提升性能,儘量將控件屬性作靜態處理。優化
配合CADisplayLink
,用於動畫過程當中跟蹤已關注圖標的位置變化,不過動畫過程監聽的並非圖標控件自身的屬性,而是圖標控件的layer.presentationLayer
。動畫
presentationLayer是用於實時獲取動畫過程當中的layout信息,若是控件不是在動畫過程當中,該屬性爲nil(系統的動畫API都是經過這個「假」的presentationLayer來呈現動畫的,本體是直接就到了最終位置的,若是是POP這個庫的動畫本體纔是實時變化的)。ui
- (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
YYLabel
的ignoreCommonProperties
要設置爲NO;CADisplayLink
要在動畫開始前一刻纔開啓,而且記得在結束後關閉;presentationLayer
,這個纔有顯式信息,本體是一步到位的;YYLabel
設置了displaysAsynchronously
爲YES,動畫開始前最好設爲NO,不然動畫過程當中label會不停地閃爍刷新(異步繪製後刷新),動畫結束後才設回YES;惋惜的是剛完成這效果,產品就說標題那裏不須要狀態圖標了,也就是白作了~
今時今日YYKit仍是很強大實用的👍,感謝看到最後 Thanks♪(・ω・)ノ。