公司有个需求,点击关注,标题处要有个已关注的图标提示,标题文本要根据是否已关注做出位置调整。数组
这种需求能够经过富文本设置首行缩进距离 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♪(・ω・)ノ。