使用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, 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;
}];

这样就可以实现我个人想要的最终效果了,如果还有别的状态图标,也是一样的做法,例如直播状态:


最终效果

总结

  1. YYLabelignoreCommonProperties要设置为NO;
  2. CADisplayLink要在动画开始前一刻才开启,并且记得在结束后关闭;
  3. 动画过程中要跟踪的是控件的presentationLayer,这个才有显式信息,本体是一步到位的;
  4. 如果YYLabel设置了displaysAsynchronously为YES,动画开始前最好设为NO,否则动画过程中label会不停地闪烁刷新(异步绘制后刷新),动画结束后才设回YES;
  5. 如果多行显示不全时结尾无法以省略号显示,可以参考我上一篇文章:解决YYLabel多行显示不全时结尾无法以省略号显示的问题

可惜的是刚完成这效果,产品就说标题那里不需要状态图标了,也就是白做了~
今时今日YYKit还是很强大实用的👍,感谢看到最后 Thanks♪(・ω・)ノ。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341