前言
最近在做一个直播项目,需要做一个用户评论的功能,需求就是有新消息时要从下至上滚动显示直至消失。类似弹幕,但又不尽相同,弹幕的显示是无序的,根据文本长度的不同移动速度也不相同。但我要实现的是连续的滚动显示。
需求
- 消息连续、匀速的从下至上滚动显示。
- 移动过程中可以响应点击事件。
思路
开始时我首先想到的是用UITableView,然后配合用CADisplayLink来定时调用UITableView.contentOffset.y -= 1 达到匀速向上滚动的效果。但在开发过程中发现了以下两个问题。
- 当某个cell消失的时候,并没有走tableView:didEndDisplayingCell:回调方法,但是当某个cell即将显示的时候却进入了tableView:willDisplayCell:回调。
- 当最后一个cell滚动至可见范围内,这时又收到一条新消息,调用insertRowsAtIndexPaths:插入一行时会导致刚才已经滚动到上面的内容强行下拉至底部再重新向上滚动。
基于以上两个问题,我决定使用自定义UIView来实现。我先定义一个FQLiveBarrageView,它有一个addBulletArray: 函数,它将负责处理发送过来的消息。因为同时收到的消息可能是一条,可能是多条,所以在这个函数里,我用一个FQLiveBarrageContentView来包装多条消息。FQLiveBarrageView还实现了FQLiveBarrageContentViewDelegate这个协议,这是用来处理动画状态改变的相关逻辑。
FQLiveBarrageView的主要代码如下:
- (void)addBulletArray:(NSArray *)bulletArray {
CGFloat y = 0, width = self.bounds.size.width;
FQLiveBarrageContentView *contentView = [[FQLiveBarrageContentView alloc] initWithFrame:CGRectMake(0, self.frame.size.height, width, 0)
referHeight:self.frame.size.height];
contentView.delegate = self;
[self addSubview:contentView];
[self.contentViewArray addObject:contentView];
for (int i = 0; i < bulletArray.count; i++) {
FQLiveBarrageViewCell *cell = [[FQLiveBarrageViewCell alloc] initWithFrame:CGRectMake(0, y, width, 0) viewModel:bulletArray[i]];
cell.delegate = self;
[contentView addSubview:cell];
y += cell.frame.size.height;
}
CGRect frame = contentView.frame;
frame.size.height = y;
contentView.frame = frame;
// 这个标记很重要,避免重复调用动画
if (self.contentViewArray.count == 1) {
[self.contentViewArray.firstObject startAnimation];
}
}
#pragma mark - FQLiveBarrageContentViewDelegate
- (void)liveBarrageContentView:(FQLiveBarrageContentView *)contentView statusDidChanged:(FQLiveBarrageContentViewStatus)status {
switch (status) {
case FQLiveBarrageContentViewStatus_Prepare:
break;
case FQLiveBarrageContentViewStatus_WillDisplay:
break;
case FQLiveBarrageContentViewStatus_Enter: {
NSUInteger index = [self.contentViewArray indexOfObject:contentView];
if (index + 1 < self.contentViewArray.count) {
FQLiveBarrageContentView *nextView = self.contentViewArray[index + 1];
if (nextView.status == FQLiveBarrageContentViewStatus_Prepare) {
[nextView startAnimation];
}
}
}
break;
case FQLiveBarrageContentViewStatus_End: {
[contentView removeFromSuperview];
[self.contentViewArray removeObject:contentView];
}
break;
}
}
FQLiveBarrageContentView这个类的任务就是处理动画,并且将动画的状态告诉FQLiveBarrageView。它的实现如下:
@implementation FQLiveBarrageContentView
- (instancetype)initWithFrame:(CGRect)frame referHeight:(CGFloat)referHeight {
if (self = [super initWithFrame:frame]) {
_status = FQLiveBarrageContentViewStatus_Prepare;
_referHeight = referHeight;
}
return self;
}
- (void)startAnimation {
[UIView animateWithDuration:1 / 60.0
delay:0.0
options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction
animations:^{
if (self.status == FQLiveBarrageContentViewStatus_Prepare) {
self.status = FQLiveBarrageContentViewStatus_WillDisplay;
}
CGRect frame = self.frame;
frame.origin.y -= 1.0;
self.frame = frame;
if (self.frame.origin.y <= (_referHeight - self.frame.size.height)) {
self.status = FQLiveBarrageContentViewStatus_Enter;
}
}
completion:^(BOOL finished) {
if (self.frame.origin.y <= -self.frame.size.height) {
self.status = FQLiveBarrageContentViewStatus_End;
} else {
[self startAnimation];
}
}];
}
- (void)setStatus:(FQLiveBarrageContentViewStatus)status {
_status = status;
if (self.delegate && [self.delegate respondsToSelector:@selector(liveBarrageContentView:statusDidChanged:)]) {
[self.delegate liveBarrageContentView:self statusDidChanged:status];
}
}
@end
还有一个FQLiveBarrageViewCell类,它主要就是展示单条消息的内容,这里就不贴代码了。文末会附上完整的Demo。
效果图:

其实如果每次收到的消息确定是单条的话,就不需要FQLiveBarrageContentView这个类来包装一层了,只要把它的实现放在FQLiveBarrageViewCell类里就可以了。
PS:在本文写完的时候,需求又改了,改成正常的评论效果了,即收到消息时,就显示,就像普通的聊天界面一样,只不过仍然是从底部开始显示,不需要像跑马灯一样一直滚动至顶部消失。这样的效果已经有人造好了轮子,我就不再赘述了。
网友评论