iOS直播系列之《很会飞的弹幕》

254 阅读3分钟

前言

此弹幕来源于直播,所以名为 LiveBarrage 。

弹幕效果:

弹幕效果图.gif

弹幕君说:
  • 我会飞~~~(gun~,你咋不上天!!);
  • 我的大小你做主;
  • 我飞的速度你来定,让我飞多快我飞多快(PS:恩!真听话);
  • 我的衣服你来买(弹幕样式自定义);
  • 我们弹幕家族自带磁场从来不会叠加碰撞的哦(⊙o⊙);
  • 你点我我就告诉你你点的是我而不是我爸(superView)也不会是我的兄弟姐妹 O(∩_∩)O~:

弹幕家谱.png

  • 直播弹幕 ** ZBLiveBarrage ** 最懂你!!

目录结构:

目录结构.png

技术剖析:

这里只分析弹幕实现具体逻辑,详细代码请下载项目根据以下分析理解。

  • 插入弹幕到数组 通过函数- (void)insertBarrages:(NSArray <ZBLiveBarrageCell *> *)barrages 向实例 ZBLiveBarragedadaArray 属性添加 ZBLiveBarrageCell 弹幕数组。

  • 创建弹幕

- (void)creatBarrage
{
    if (self.dataArray.firstObject) {

        // 取出弹幕数组里第一条未展示的弹幕
        ZBLiveBarrageCell *barrageView = self.dataArray.firstObject;

        // 通过 函数 zb_canBarrageSendInTheChannel 判断这条弹幕是否有可用跑道让其展示
        NSInteger row = [self zb_canBarrageSendInTheChannel:barrageView];

        // 若果有可用跑到
        if (row >= 0) {
              barrageView  开始执行 animateWithDuration 在 当前跑道 row 展示弹幕
         }

     }

    //  再次执行 creatBarrage 方法
    [self performSelector:@selector(creatBarrage) withObject:nil afterDelay:0.1f];
}
  • 判断最新弹幕是否有可用跑道让其展示 channelArraychannelCount 是对应的,channelCount 是外界用来设置弹幕轨道数的属性,channelArray 是用来存放每条轨道上最后一条弹幕,如果没有弹幕经过轨道默认赋值 NSNumber 实例。
- (NSInteger)zb_canBarrageSendInTheChannel:(ZBLiveBarrageCell *)newBarrage
{
    // 遍历轨道数组
    for (id object  in _channelArray) {

        if ([object isKindOfClass:[NSNumber class]]) {

            //  如果最后一条没有最后一条弹幕,返回当前跑到
            return row;

        }else if ([object isKindOfClass:[ZBLiveBarrageCell class]]) {

            // 获取最后一条弹幕
            ZBLiveBarrageCell *oldBarrage = (ZBLiveBarrageCell*)object;

            // 通过 zb_canBarrageSendInTheChannel: newBullet: 函数 实现新弹幕与当前跑道上最后一条弹幕的 碰撞检测
            if ([self zb_canBarrageSendInTheChannel:oldBarrage newBullet:newBarrage]) {

                return row;
            }
        }
    }

    return -1;
}
  • 碰撞检测 - (BOOL)zb_canBarrageSendInTheChannel:(ZBLiveBarrageCell *)oldBarrage newBullet:(ZBLiveBarrageCell *)newBarrage 返回值为 BOOL 是否有可能碰撞,思路: 我们暂且称当前轨道最后一条弹幕为【老弹幕】,将要展示的弹幕为【新弹幕】
if (老弹幕还没完全显示在屏幕中) {

    return NO;

}else if (老弹幕的宽度为 0 时) {

    // 刚刚添加的控件,有可能取到frame的值全为0,也要返回NO
    return NO;

} else if  (如果老弹幕新弹幕的展示时间相同 && 老弹幕的宽度 > 新弹幕的宽度)  {

    //  比较弹幕的宽度(也就是比较速度),如果弹幕在屏幕中停留的时间都一样,【新弹幕】宽度小于【老弹幕】就是永远追不上上一条弹幕,返回YES
    return YES;

} else {

        // time为新弹幕从出现到屏幕最左边的时间(此时弹幕整体都在屏幕内,并不是弹幕消失的时间)
        CGFloat time = 屏幕宽度/(屏幕宽度+【新弹幕的宽度)*【新弹幕的展示时间;
        // endX为此时老弹幕的frame的x值
        CGFloat endX = 老弹幕的 x - time/(老弹幕展示时间)*(屏幕宽度 + 新弹幕的宽度);
        if (endX < -【新弹幕的宽度) {
            // 若此时老弹幕已经完全从屏幕中消失,返回YES
            return YES;
        }
    }
    return NO;
}

  • 弹幕点击事件 因为视图在动画过程中不能响应手势,所以只能通过计算来响应用户的手势点击事件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint clickPoint  = [touch locationInView:self];
    for (ZBLiveBarrageCell *barrageView in [self subviews])
    {
        if ([barrageView.layer.presentationLayer hitTest:clickPoint])
        {
          // 来到这里说明此条弹幕被点击
        }
            break;
        }
    }
}
  • 代理事件
/**
 *  弹幕点击事件回调
 */
- (void)zb_barrageView:(ZBLiveBarrage *)barrageView didSelectedCell:(ZBLiveBarrageCell *)cell;
/**
 *  当前插入的弹幕模型数组全部展示完成回调
 */
- (void)zb_barrageViewCompletedCurrentAnimations;
/**
 *  弹幕即将显示时回调
 */
- (void)zb_barrageView:(ZBLiveBarrage *)barrageView willDisplayCell:(ZBLiveBarrageCell *)cell;
/**
 *  弹幕显示完成回调
 */
- (void)zb_barrageView:(ZBLiveBarrage *)barrageView didEndDisplayingCell:(ZBLiveBarrageCell *)cell;

小结


这一套弹幕实现核心代码在于弹幕碰撞监测那部分,是不是很简单那?

注释很详细的 Demo 点击这里

软件在能够复用前必须先能用。 ——Ralph Johnson