基于BarrageRender自定義彈幕動(dòng)畫

基于BarrageRender自定義彈幕動(dòng)畫

BarrageRender目前已更新到2.0.1,自定義彈幕的機(jī)制有改變,本篇文章的代碼在2.0.1下不起作用,如果想按照本文中用的方法,請(qǐng)使用1.9.1版本的BarrageRender。后續(xù)會(huì)更新2.0.1版本下的自定義彈幕

BarrageRender 是iOS上一個(gè)非常出名的彈幕渲染開源框架,其可以讓我們?cè)贏pp中非常方便的集成彈幕功能,其作者在代碼中提供了兩種方式的彈幕動(dòng)畫,BarrageFloatSpriteBarrageWalkSprite。可以說移動(dòng)和浮動(dòng)這兩種動(dòng)畫方式基本上已經(jīng)滿足了大部分App的需求,但是仍然有部分App需要在彈幕的展現(xiàn)形式上更加的自由,例如各大直播平臺(tái)的禮物彈幕。筆者將在這篇文章中分享自己在BarrageRender的基礎(chǔ)上編寫自定義禮物彈幕的過程。

博客地址

先展示效果

彈幕效果

再介紹BarrageWalkSprite原理

BarrageWalkSprite和本文將要實(shí)現(xiàn)的自定義Sprite有一定的關(guān)聯(lián)性,所以就通過分析BarrageWalkSprite的源碼來展示BarrageRender渲染彈幕的原理,另外一個(gè)BarrageFloatSprite的渲染方式稍有不同,但是如果你能搞清楚BarrageWalkSprite的原理,理解FloatSprite的渲染方式也是很輕松的。

彈幕的初始位置

BarrageRender在BarrageDispatcher的調(diào)度下觸發(fā)activeWithContext方法,而在此方法中,BarrageRender調(diào)用了Sprite的originInBounds:withSprite方法來確定每個(gè)精靈的初始位置

- (void)activeWithContext:(NSDictionary *)context
 {
     CGRect rect = [[context objectForKey:kBarrageRendererContextCanvasBounds]CGRectValue];
     NSArray * sprites = [context objectForKey:kBarrageRendererContextRelatedSpirts];
     NSTimeInterval timestamp = [[context objectForKey:kBarrageRendererContextTimestamp]doubleValue];
     _timestamp = timestamp;
     _view = [self bindingView];
     [self configView];
     [_view sizeToFit];
     if (!CGSizeEqualToSize(_mandatorySize, CGSizeZero)) {
         _view.frame = CGRectMake(0, 0, _mandatorySize.width, _mandatorySize.height);
     }
     _origin = [self originInBounds:rect withSprites:sprites];
     _view.frame = CGRectMake(_origin.x, _origin.y, self.size.width, self.size.height);
 }

BarrageWalkSpirte在originInBounds:withSprite方法中,根據(jù)當(dāng)前屏幕上已經(jīng)存在的Sprite來計(jì)算自己的初始位置。

    
- (CGPoint)originInBounds:(CGRect)rect withSprites:(NSArray *)sprites
{
   // 獲取同方向精靈
   NSMutableArray * synclasticSprites = [[NSMutableArray alloc]initWithCapacity:sprites.count];
   for (BarrageWalkSprite * sprite in sprites) {
       if (sprite.direction == _direction && sprite.side == self.side) { // 找尋同道中人
           [synclasticSprites addObject:sprite];
       }
   }
   
   static BOOL const AVAERAGE_STRATEGY = YES; // YES:條紋平均精靈策略(體驗(yàn)會(huì)好一些); NO:最快時(shí)間策略
   NSTimeInterval stripMaxActiveTimes[STRIP_NUM]={0}; // 每一條網(wǎng)格 已有精靈中最后退出屏幕的時(shí)間
   NSUInteger stripSpriteNumbers[STRIP_NUM]={0}; // 每一條網(wǎng)格 包含精靈的數(shù)目
   NSUInteger stripNum = MIN(STRIP_NUM, MAX(self.trackNumber, 1)); // between (1,STRIP_NUM)
   CGFloat stripHeight = rect.size.height/stripNum; // 水平條高度
   CGFloat stripWidth = rect.size.width/stripNum; // 豎直條寬度
   BOOL oritation = _direction == BarrageWalkDirectionL2R || _direction == BarrageWalkDirectionR2L; // 方向, YES代表水平彈幕
   BOOL rotation = self.side == [self defaultSideWithDirection:_direction];
   /// 計(jì)算數(shù)據(jù)結(jié)構(gòu),便于應(yīng)用算法
   NSUInteger overlandStripNum = 1; // 橫跨網(wǎng)格條數(shù)目
   if (oritation) { // 水平
       overlandStripNum = (NSUInteger)ceil((double)self.size.height/stripHeight);
   }
   else // 豎直
   {
       overlandStripNum = (NSUInteger)ceil((double)self.size.width/stripWidth);
   }
   /// 當(dāng)前精靈需要的時(shí)間,左邊碰到邊界, 不是真實(shí)的活躍時(shí)間
   NSTimeInterval maxActiveTime = oritation?rect.size.width/self.speed:rect.size.height/self.speed;
   NSUInteger availableFrom = 0;
   NSUInteger leastActiveTimeStrip = 0; // 最小時(shí)間的行
   NSUInteger leastActiveSpriteStrip = 0; // 最小網(wǎng)格的行
   
   for (NSUInteger i = 0; i < stripNum; i++) {
       //尋找當(dāng)前行里包含的sprites
       CGFloat stripFrom = i * (oritation?stripHeight:stripWidth);
       CGFloat stripTo = stripFrom + (oritation?stripHeight:stripWidth);
       if (!rotation) {
           CGFloat preStripFrom = stripFrom;
           stripFrom = (oritation?rect.size.height:rect.size.width) - stripTo;
           stripTo = (oritation?rect.size.height:rect.size.width) - preStripFrom;
       }
       CGFloat lastDistanceAllOut = YES;
       for (BarrageWalkSprite * sprite in synclasticSprites) {
           CGFloat spriteFrom = oritation?sprite.origin.y:sprite.origin.x;
           CGFloat spriteTo = spriteFrom + (oritation?sprite.size.height:sprite.size.width);
           if ((spriteTo-spriteFrom)+(stripTo-stripFrom)>MAX(stripTo-spriteFrom, spriteTo-stripFrom)) { // 在條條里
               stripSpriteNumbers[i]++;
               NSTimeInterval activeTime = [sprite estimateActiveTime];
               if (activeTime > stripMaxActiveTimes[i]){ // 獲取最慢的那個(gè)
                   stripMaxActiveTimes[i] = activeTime;
                   CGFloat distance = oritation?fabs(sprite.position.x-sprite.origin.x):fabs(sprite.position.y-sprite.origin.y);
                   lastDistanceAllOut = distance > (oritation?sprite.size.width:sprite.size.height);
               }
           }
       }
       if (stripMaxActiveTimes[i]>maxActiveTime || !lastDistanceAllOut) {
           availableFrom = i+1;
       }
       else if (i - availableFrom >= overlandStripNum - 1){
           break; // eureka!
       }
       if (i <= stripNum - overlandStripNum) {
           if (stripMaxActiveTimes[i] < stripMaxActiveTimes[leastActiveTimeStrip]) {
               leastActiveTimeStrip = i;
           }
           if (stripSpriteNumbers[i] < stripSpriteNumbers[leastActiveSpriteStrip]) {
               leastActiveSpriteStrip = i;
           }
       }
   }
   if (availableFrom > stripNum - overlandStripNum) { // 那就是沒有找到嘍
       availableFrom = AVAERAGE_STRATEGY?leastActiveSpriteStrip:leastActiveTimeStrip; // 使用最小個(gè)數(shù) or 使用最短時(shí)間
   }
   
   CGPoint origin = CGPointZero;
   if (oritation) { // 水平
       _destination.y = origin.y = (rotation?stripHeight*availableFrom:rect.size.height-stripHeight * availableFrom-self.size.height)+rect.origin.y;
       origin.x = (self.direction == BarrageWalkDirectionL2R)?rect.origin.x - self.size.width:rect.origin.x + rect.size.width;
       _destination.x = (self.direction == BarrageWalkDirectionL2R)?rect.origin.x + rect.size.width:rect.origin.x - self.size.width;
   }
   else
   {
       _destination.x = origin.x = (rotation?stripWidth*availableFrom:rect.size.width-stripWidth*availableFrom -self.size.width)+rect.origin.x;
       origin.y = (self.direction == BarrageWalkDirectionT2B)?rect.origin.y - self.size.height:rect.origin.y + rect.size.height;
       _destination.y = (self.direction == BarrageWalkDirectionT2B)?rect.origin.y + rect.size.height:rect.origin.y - self.size.height;
   }
   return origin;
}

代碼雖然很長,但是主要就是為了實(shí)現(xiàn)下面幾個(gè)邏輯:

1. BarrageWalkSprite先獲取了同方向的所有精靈
2. 根據(jù)屏幕軌道的frame范圍找到每一個(gè)軌道內(nèi)的所有精靈
3. 在同一軌道內(nèi)的所有精靈中找到存活時(shí)間最長的精靈(速度最慢)
4. 判斷速度最慢的那個(gè)精靈的尾部是否已經(jīng)完全進(jìn)入彈幕顯示區(qū)域
5. 如果速度最慢的精靈尾部已經(jīng)進(jìn)入彈幕顯示區(qū)域,則可以確定自己的可以緊跟在后面出現(xiàn),如果還沒有完全進(jìn)入彈幕顯示區(qū)域,則繼續(xù)在下一個(gè)軌道獲取合適的位置
6. 根據(jù)計(jì)算得到的自己可以出現(xiàn)的軌道,加上該軌道上最后一個(gè)精靈的位置,得到自己的起始位置

彈幕的運(yùn)動(dòng)軌跡

BarrageRender繪制每個(gè)精靈的運(yùn)動(dòng)軌跡的方式非常簡單,在BarrageRender中,內(nèi)置的時(shí)鐘引擎BarrageClock負(fù)責(zé)在間隔時(shí)間內(nèi)調(diào)用所有已經(jīng)激活精靈基類BarrageSprite中的updateWithTime方法。

- (void)initClock
 {
     __weak id weakSelf = self;
     _clock = [BarrageClock clockWithHandler:^(NSTimeInterval time){
         BarrageRenderer * strongSelf = weakSelf;
         strongSelf->_time = time;
         [strongSelf update];
     }];
 }
 
 /// 每個(gè)刷新周期執(zhí)行一次
 - (void)update
 {
     [_dispatcher dispatchSprites]; // 分發(fā)精靈
     for (BarrageSprite * sprite in _dispatcher.activeSprites) {
         [sprite updateWithTime:_time];
     }
 }

而在BarrageSprite的updateWithTime方法中, 每個(gè)精靈重新更改了自身的frame屬性,以此來達(dá)到動(dòng)畫位移的效果。其中_valid屬性是Sprite存活的唯一標(biāo)志,標(biāo)記為NO之后,Sprite就會(huì)從隊(duì)列中徹底移除

  //BarrageSprite
- (void)updateWithTime:(NSTimeInterval)time
 {
     _valid = [self validWithTime:time];
     _view.frame = [self rectWithTime:time];
 }

BarrageWalkSprite通過屬性speed來實(shí)時(shí)改變自己的frame位置,同時(shí)計(jì)算剩下的destination和speed來算出自己的存活時(shí)間以用來標(biāo)記valid屬性

//BarrageWalkSprite

- (BOOL)validWithTime:(NSTimeInterval)time
  {
      return [self estimateActiveTime] > 0;
  }
  
- (NSTimeInterval)estimateActiveTime
  {
      CGFloat activeDistance = 0;
      switch (_direction) {
          case BarrageWalkDirectionR2L:
              activeDistance = self.position.x - _destination.x;
              break;
          case BarrageWalkDirectionL2R:
              activeDistance = _destination.x - self.position.x;
              break;
          case BarrageWalkDirectionT2B:
              activeDistance = _destination.y - self.position.y;
              break;
          case BarrageWalkDirectionB2T:
              activeDistance = self.position.y - _destination.y;
          default:
              break;
      }
      return activeDistance/self.speed;
  }

- (CGRect)rectWithTime:(NSTimeInterval)time
  {
      CGFloat X = self.destination.x - self.origin.x;
      CGFloat Y = self.destination.y - self.origin.y;
      CGFloat L = sqrt(X*X + Y*Y);
      NSTimeInterval duration = time - self.timestamp;
      CGPoint position = CGPointMake(self.origin.x + duration * self.speed * X/L, self.origin.y + duration * self.speed * Y/L);
      return CGRectMake(position.x, position.y, self.size.width, self.size.height);
  }
  

彈幕終點(diǎn)

BarrageWalkSprite的終點(diǎn)計(jì)算很簡單,彈幕的顯示的距離加上Sprite自身的寬度就是整個(gè)精靈需要位移的距離,這個(gè)destination的計(jì)算已經(jīng)體現(xiàn)在了起點(diǎn)位置的獲取當(dāng)中

CGPoint origin = CGPointZero;
if (oritation) { // 水平
   _destination.y = origin.y = (rotation?stripHeight*availableFrom:rect.size.height-stripHeight * availableFrom-self.size.height)+rect.origin.y;
   origin.x = (self.direction == BarrageWalkDirectionL2R)?rect.origin.x - self.size.width:rect.origin.x + rect.size.width;
   _destination.x = (self.direction == BarrageWalkDirectionL2R)?rect.origin.x + rect.size.width:rect.origin.x - self.size.width;
}
else
{
   _destination.x = origin.x = (rotation?stripWidth*availableFrom:rect.size.width-stripWidth*availableFrom -self.size.width)+rect.origin.x;
   origin.y = (self.direction == BarrageWalkDirectionT2B)?rect.origin.y - self.size.height:rect.origin.y + rect.size.height;
   _destination.y = (self.direction == BarrageWalkDirectionT2B)?rect.origin.y + rect.size.height:rect.origin.y - self.size.height;
}
return origin;

自定義Sprite

BarrageBubblingSprite的運(yùn)動(dòng)軌跡和BarrageWalkSprite有很多重合之處,所以自定義的BarrageBubblingSprite直接繼承BarrageWalkSprite以獲取其direction,side,speed,trackNumber等多個(gè)屬性,當(dāng)然還需要另外加上加速度speedUp和停留時(shí)間stay屬性

@interface BarrrageBubblingSprite : BarrageWalkSprite

@property (nonatomic,assign) CGFloat speedUp; //加速度

@property (nonatomic,assign) CGFloat stay; //到達(dá)終點(diǎn)后的停留時(shí)間

@end

起點(diǎn)位置

BubblingSprite的起點(diǎn)位置的獲取邏輯和WalkSprite的起點(diǎn)邏輯類似,不同的地方在于:

  1. 即使軌道內(nèi)最慢的那個(gè)精靈已經(jīng)完全進(jìn)入彈幕顯示區(qū)域,只要該精靈仍然存活,就不能緊跟其后,而是要另外找尋其他軌道
  2. 當(dāng)所有軌道都已經(jīng)有精靈占據(jù)的時(shí)候,找到存活時(shí)間最短的那個(gè)精靈,通過將其的stay屬性設(shè)置為0讓其直接消失,然后讓自己占據(jù)該精靈所在軌道
- (CGPoint)originInBounds:(CGRect)rect withSprites:(NSArray *)sprites
{
    // 獲取同方向精靈
    NSMutableArray * synclasticSprites = [[NSMutableArray alloc]initWithCapacity:sprites.count];
    for (BarrageWalkSprite * sprite in sprites) {
        if (sprite.direction == self.direction && sprite.side == self.side) { // 找尋同道中人
            [synclasticSprites addObject:sprite];
        }
    }
    
    NSUInteger stripNum = MIN(STRIP_NUM, MAX(self.trackNumber, 1)); // between (1,STRIP_NUM)
    CGFloat stripHeight = rect.size.height/stripNum; // 水平條高度
    CGFloat stripWidth = rect.size.width/stripNum; // 豎直條寬度
    BOOL oritation = self.direction == BarrageWalkDirectionL2R || self.direction == BarrageWalkDirectionR2L; // 方向, YES代表水平彈幕
    BOOL rotation = self.side == [self defaultSideWithDirection:self.direction];
    /// 計(jì)算數(shù)據(jù)結(jié)構(gòu),便于應(yīng)用算法
    NSUInteger overlandStripNum = 1; // 橫跨網(wǎng)格條數(shù)目
    if (oritation) { // 水平
        overlandStripNum = (NSUInteger)ceil((double)self.size.height/stripHeight);
    }
    else // 豎直
    {
        overlandStripNum = (NSUInteger)ceil((double)self.size.width/stripWidth);
    }

    NSUInteger availableFrom = 0;
    BarrrageBubblingSprite* lastTimeSprite = self;
    NSInteger lastSpriteIndex = 0;
    
    
    for (NSUInteger i = 0; i < stripNum; i++) {
        //尋找當(dāng)前行里包含的sprites
        CGFloat stripFrom = i * (oritation?stripHeight:stripWidth);
        CGFloat stripTo = stripFrom + (oritation?stripHeight:stripWidth);
        if (!rotation) {
            CGFloat preStripFrom = stripFrom;
            stripFrom = (oritation?rect.size.height:rect.size.width) - stripTo;
            stripTo = (oritation?rect.size.height:rect.size.width) - preStripFrom;
        }
        CGFloat exsitSprite = NO;
        for (BarrrageBubblingSprite * sprite in synclasticSprites) {
            CGFloat spriteFrom = oritation?sprite.origin.y:sprite.origin.x;
            CGFloat spriteTo = spriteFrom + (oritation?sprite.size.height:sprite.size.width);
            if ((spriteTo-spriteFrom)+(stripTo-stripFrom)>MAX(stripTo-spriteFrom, spriteTo-stripFrom)) { // 在條條里
                exsitSprite = YES;
              
                if (sprite.timestamp < lastTimeSprite.timestamp){
                    lastTimeSprite = sprite;
                    lastSpriteIndex = i;
                }
                break;
            }
        }
        if (exsitSprite) {
            availableFrom = i+1;
        }else{ //第一行就是空的
            break;
        }
        
    }
    if (availableFrom == stripNum) { // 超出最大的軌道數(shù),擠掉最上層精靈
        availableFrom = lastSpriteIndex;
        lastTimeSprite.stay = 0;
    }
    
    CGPoint origin = CGPointZero;
    if (oritation) { // 水平
        _destination.y = origin.y = (rotation?stripHeight*availableFrom:rect.size.height-stripHeight * availableFrom-self.size.height)+rect.origin.y;
        origin.x = (self.direction == BarrageWalkDirectionL2R)?rect.origin.x - self.size.width:rect.origin.x + rect.size.width;
        _destination.x = (self.direction == BarrageWalkDirectionL2R)?rect.origin.x + rect.size.width - self.size.width :rect.origin.x + self.size.width;
    }
    else
    {
        _destination.x = origin.x = (rotation?stripWidth*availableFrom:rect.size.width-stripWidth*availableFrom -self.size.width)+rect.origin.x;
        origin.y = (self.direction == BarrageWalkDirectionT2B)?rect.origin.y - self.size.height:rect.origin.y + rect.size.height;
        _destination.y = (self.direction == BarrageWalkDirectionT2B)?rect.origin.y + rect.size.height - self.size.height:rect.origin.y + self.size.height;
    }
    return origin;

運(yùn)動(dòng)軌跡

BarrageBubblingSprite的運(yùn)動(dòng)軌跡和BarrageWalkSprite的運(yùn)動(dòng)軌跡不同的地方在于,BarrageWalkSprite是勻速前進(jìn),二BarrageBubblingSprite是加速前進(jìn),這樣,在計(jì)算某個(gè)時(shí)段Sprite的位置就需要考慮加速度的存在。

- (CGRect)rectWithTime:(NSTimeInterval)time{
    CGFloat X = self.destination.x - self.origin.x;
    CGFloat Y = self.destination.y - self.origin.y;
        
    CGFloat L = sqrt(X*X + Y*Y);
    NSTimeInterval duration = time - self.timestamp;
    CGPoint position = CGPointMake(self.origin.x + duration * self.speed * X/L, self.origin.y + duration * self.speed * Y/L);
    if (position.x >= self.destination.x) {
        position.x = self.destination.x;
    }else{
        self.destinationStamp = time;
        self.speed = duration*self.speedUp;
    }
    if(position.y >= self.destination.y) {
        position.y = self.destination.y;
      
    }else{
        self.destinationStamp = time;
        self.speed = duration*self.speedUp;
    }
    return CGRectMake(position.x, position.y, self.size.width, self.size.height);
}

在存活時(shí)間上,與BarrageWalkSprite不同的地方在于,BarrageWalkSprite在位移到終點(diǎn)的時(shí)候消失,而BarrageBubblingSprite在到達(dá)終點(diǎn)之后仍然需要停留stay的時(shí)間。這里引入了currentStamp和destinationStamp時(shí)間戳用于來計(jì)算stay時(shí)間是否已經(jīng)到達(dá)。

//計(jì)算精靈的剩余存活時(shí)間

- (double)countTimeByDistance:(CGFloat)distance{
    CGFloat a = 0.5*self.speedUp;
    CGFloat b = self.speed;
    CGFloat c = -distance;
    CGFloat delt = sqrt(b*b - 4*a*c);
    double t = (-b+delt)/(2*a);
    return t;
}

- (NSTimeInterval)estimateActiveTime
{
    CGFloat activeDistance = 0;
    switch (self.direction) {
        case BarrageWalkDirectionR2L:
            activeDistance = self.position.x - _destination.x;
            break;
        case BarrageWalkDirectionL2R:
            activeDistance = _destination.x - self.position.x;
            break;
        case BarrageWalkDirectionT2B:
            activeDistance = _destination.y - self.position.y;
            break;
        case BarrageWalkDirectionB2T:
            activeDistance = self.position.y - _destination.y;
        default:
            break;
    }
    NSTimeInterval leftTime = 0.0;
    CGFloat time = [self countTimeByDistance:activeDistance];
    if (time > 0){
        leftTime = time + self.stay;
    }else{
        leftTime = self.stay - (self.currentStamp - self.destinationStamp);
    }
    return leftTime;
}

- (BOOL)validWithTime:(NSTimeInterval)time{
    self.currentStamp = time;
    return  [self estimateActiveTime] > 0;
}

自定義彈幕樣式

類似BarrageWalkImageSprite,我們也通過繼承BarrageSpirte的bindingView 來將自定義的彈幕view返回給BarrageRender

完整代碼

BarrageRender-BubblingSprite

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,698評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,202評(píng)論 3 426
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,742評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,580評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,297評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,688評(píng)論 1 327
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,693評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,875評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,438評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,183評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,384評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,931評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,612評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,022評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,297評(píng)論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,093評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,330評(píng)論 2 377

推薦閱讀更多精彩內(nèi)容

  • 版本記錄 前言 在我們做直播等視頻類app的時(shí)候,總是有顯示和發(fā)送彈幕的要求,彈幕可以方便用戶進(jìn)行溝通和互動(dòng),增加...
    刀客傳奇閱讀 5,184評(píng)論 8 16
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,702評(píng)論 25 708
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,162評(píng)論 4 61
  • 到了這個(gè)年紀(jì),物質(zhì)或者能力都不如同齡人甚至不如比自己小的人,可能是因?yàn)樽约翰粔蚺Γ沲闪颂鄽q月,可能是自己的福...
    花店父子閱讀 229評(píng)論 0 0
  • 一個(gè)腦袋落在地上,花開鏢局的人一驚,因?yàn)榭∶郎倌赀@一寒光四射的紫金刀著實(shí)有威力:一只饑餓的鳥正從這里飛過,不成想被...
    我是馮雷閱讀 331評(píng)論 2 3