小说绘上架版本

This commit is contained in:
xtfei2011
2021-02-07 11:24:08 +08:00
commit ee5c1c8b12
1762 changed files with 115892 additions and 0 deletions
@@ -0,0 +1,16 @@
//
// CALayer+OCBarrage.h
// OCBarrage
//
// Created by QMTV on 2017/8/29.
// Copyright © 2017年 LFC. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
@interface CALayer (OCBarrage)
- (UIImage *)convertContentToImageWithSize:(CGSize)contentSize;
@end
@@ -0,0 +1,24 @@
//
// CALayer+OCBarrage.m
// OCBarrage
//
// Created by QMTV on 2017/8/29.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "CALayer+OCBarrage.h"
@implementation CALayer (OCBarrage)
- (UIImage *)convertContentToImageWithSize:(CGSize)contentSize {
UIGraphicsBeginImageContextWithOptions(contentSize, 0.0, [UIScreen mainScreen].scale);
//self为需要截屏的UI控件 即通过改变此参数可以截取特定的UI控件
[self renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image= UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end
@@ -0,0 +1,20 @@
//
// OCBarrage.h
// OCBarrage
//
// Created by QMTV on 2017/8/25.
// Copyright © 2017年 LFC. All rights reserved.
//
#ifndef OCBarrage_h
#define OCBarrage_h
#import "OCBarrageHeader.h"
#import "OCBarrageManager.h"
#import "OCBarrageRenderView.h"
#import "OCBarrageDescriptor.h"
#import "OCBarrageCell.h"
#import "OCBarrageTextDescriptor.h"
#import "OCBarrageTextCell.h"
#endif /* OCBarrage_h */
@@ -0,0 +1,43 @@
//
// OCBarrageCell.h
// TestApp
//
// Created by QMTV on 2017/8/21.
// Copyright © 2017年 LFC. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "CALayer+OCBarrage.h"
#import "OCBarrageDescriptor.h"
NS_ASSUME_NONNULL_BEGIN
@protocol OCBarrageCellDelegate;
@interface OCBarrageCell : UIView
@property (nonatomic, assign, getter=isIdle) BOOL idle;//是否是空闲状态
@property (nonatomic, assign) NSTimeInterval idleTime;//开始闲置的时间, 闲置超过5秒的, 自动回收内存
@property (nonatomic, strong, nullable) OCBarrageDescriptor *barrageDescriptor;
@property (nonatomic, strong, readonly, nullable) CAAnimation *barrageAnimation;
@property (nonatomic, assign) int trackIndex;
- (void)addBarrageAnimationWithDelegate:(id<CAAnimationDelegate>)animationDelegate;
- (void)prepareForReuse;
- (void)clearContents;
- (void)updateSubviewsData;
- (void)layoutContentSubviews;
- (void)convertContentToImage;
- (void)sizeToFit;//设置好数据之后调用一下自动计算bounds
- (void)removeSubViewsAndSublayers;//默认删除所有的subview和sublayer; 如果需要选择性的删除可以重写这个方法.
- (void)addBorderAttributes;
@end
@protocol OCBarrageCellDelegate <NSObject, CAAnimationDelegate>
@optional
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,113 @@
//
// OCBarrageCell.m
// TestApp
//
// Created by QMTV on 2017/8/21.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageCell.h"
@implementation OCBarrageCell
- (instancetype)init {
self = [super init];
if (self) {
_trackIndex = -1;
}
return self;
}
- (void)prepareForReuse {
[self.layer removeAnimationForKey:kBarrageAnimation];
_barrageDescriptor = nil;
if (!_idle) {
_idle = YES;
}
_trackIndex = -1;
}
- (void)setBarrageDescriptor:(OCBarrageDescriptor *)barrageDescriptor {
_barrageDescriptor = barrageDescriptor;
}
- (void)clearContents {
self.layer.contents = nil;
}
- (void)convertContentToImage {
}
- (void)sizeToFit {
CGFloat height = 0.0;
CGFloat width = 0.0;
for (CALayer *sublayer in self.layer.sublayers) {
CGFloat maxY = CGRectGetMaxY(sublayer.frame);
if (maxY > height) {
height = maxY;
}
CGFloat maxX = CGRectGetMaxX(sublayer.frame);
if (maxX > width) {
width = maxX;
}
}
if (width == 0 || height == 0) {
CGImageRef content = (__bridge CGImageRef)self.layer.contents;
if (content) {
UIImage *image = [UIImage imageWithCGImage:content];
width = image.size.width/[UIScreen mainScreen].scale;
height = image.size.height/[UIScreen mainScreen].scale;
}
}
self.bounds = CGRectMake(0.0, 0.0, width, height);
}
- (void)removeSubViewsAndSublayers {
NSEnumerator *viewEnumerator = [self.subviews reverseObjectEnumerator];
UIView *subView = nil;
while (subView = [viewEnumerator nextObject]){
[subView removeFromSuperview];
}
NSEnumerator *layerEnumerator = [self.layer.sublayers reverseObjectEnumerator];
CALayer *sublayer = nil;
while (sublayer = [layerEnumerator nextObject]){
[sublayer removeFromSuperlayer];
}
}
- (void)addBorderAttributes {
if (self.barrageDescriptor.borderColor) {
self.layer.borderColor = self.barrageDescriptor.borderColor.CGColor;
}
if (self.barrageDescriptor.borderWidth > 0) {
self.layer.borderWidth = self.barrageDescriptor.borderWidth;
}
if (self.barrageDescriptor.cornerRadius > 0) {
self.layer.cornerRadius = self.barrageDescriptor.cornerRadius;
}
}
- (void)addBarrageAnimationWithDelegate:(id<CAAnimationDelegate>)animationDelegate {
}
- (void)updateSubviewsData {
}
- (void)layoutContentSubviews {
}
- (CAAnimation *)barrageAnimation {
return [self.layer animationForKey:kBarrageAnimation];
}
@end
@@ -0,0 +1,34 @@
//
// OCBarrageDescriptor.h
// TestApp
//
// Created by QMTV on 2017/8/23.
// Copyright © 2017年 LFC. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "OCBarrageHeader.h"
@class OCBarrageCell;
NS_ASSUME_NONNULL_BEGIN
@interface OCBarrageDescriptor : NSObject
@property (nonatomic, assign, nullable) Class barrageCellClass;
@property (nonatomic, assign) OCBarragePositionPriority positionPriority;//显示位置normal型的渲染在low型的上面, height型的渲染在normal上面
@property (nonatomic, assign) CGFloat animationDuration;//动画时间, 时间越长速度越慢, 时间越短速度越快
@property (nonatomic, assign) CGFloat fixedSpeed;//固定速度, 可以防止弹幕在有空闲轨道的情况下重叠, 取值0.0~100.0, animationDuration与fixedSpeed只能选择一个, fixedSpeed设置之后可以不用设置animationDuration
//@property (nonatomic, copy, nullable) OCBarrageTouchAction touchAction DEPRECATED_MSG_ATTRIBUTE("use OCBarrageCellTouchedAction instead");
@property (nonatomic, copy, nullable) OCBarrageTouchAction touchAction;
@property (nonatomic, copy, nullable) OCBarrageCellTouchedAction cellTouchedAction;//新属性里回传了被点击的cell, 可以在代码块里更改被点击的cell的属性, 比如之前有用户需要在弹幕被点击的时候修改被点击的弹幕的文字颜色等等. 用来替代旧版本的touchAction
@property (nonatomic, strong, nullable) UIColor *borderColor; // Default is no border
@property (nonatomic, assign) CGFloat borderWidth; // Default is 0
@property (nonatomic, assign) CGFloat cornerRadius; // Default is 8
@property (nonatomic, assign) NSRange renderRange;//渲染范围, 最终渲染出来的弹幕的Y坐标最小不小于renderRange.location, 最大不超过renderRange.length-barrageCell.height
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,24 @@
//
// OCBarrageDescriptor.m
// TestApp
//
// Created by QMTV on 2017/8/23.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageDescriptor.h"
@implementation OCBarrageDescriptor
- (instancetype)init {
self = [super init];
if (self) {
}
return self;
}
@end
@@ -0,0 +1,32 @@
//
// OCBarrageHeader.h
// TestApp
//
// Created by QMTV on 2017/8/23.
// Copyright © 2017年 LFC. All rights reserved.
//
#ifndef OCBarrageHeader_h
#define OCBarrageHeader_h
#define kBarrageAnimation @"kBarrageAnimation"
@class OCBarrageDescriptor;
@class OCBarrageCell;
typedef void(^OCBarrageTouchAction)(__weak OCBarrageDescriptor *descriptor);
typedef void(^OCBarrageCellTouchedAction)(__weak OCBarrageDescriptor *descriptor, __weak OCBarrageCell *cell);
typedef NS_ENUM(NSInteger, OCBarragePositionPriority) {
OCBarragePositionLow = 0,
OCBarragePositionMiddle,
OCBarragePositionHigh,
OCBarragePositionVeryHigh
};
typedef NS_ENUM(NSInteger, OCBarrageRenderPositionStyle) {//新加的cell的y坐标的类型
OCBarrageRenderPositionRandomTracks = 0, //将OCBarrageRenderView分成几条轨道, 随机选一条展示
OCBarrageRenderPositionRandom, // y坐标随机
OCBarrageRenderPositionIncrease, //y坐标递增, 循环
};
#endif /* OCBarrageHeader_h */
@@ -0,0 +1,30 @@
//
// OCBarrageView.h
// TestApp
//
// Created by QMTV on 2017/8/22.
// Copyright © 2017年 LFC. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "OCBarrageRenderView.h"
NS_ASSUME_NONNULL_BEGIN
@interface OCBarrageManager : NSObject {
OCBarrageRenderView *_renderView;
}
@property (nonatomic, strong, readonly) OCBarrageRenderView *renderView;
@property (nonatomic, assign, readonly) OCBarrageRenderStatus renderStatus;
- (void)start;
- (void)pause;
- (void)resume;
- (void)stop;
- (void)renderBarrageDescriptor:(OCBarrageDescriptor *)barrageDescriptor;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,65 @@
//
// OCBarrageView.m
// TestApp
//
// Created by QMTV on 2017/8/22.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageManager.h"
@implementation OCBarrageManager
- (void)dealloc {
NSLog(@"%s", __func__);
[_renderView stop];
}
- (instancetype)init {
self = [super init];
if (self) {
_renderView = [[OCBarrageRenderView alloc] init];
}
return self;
}
- (void)start {
[self.renderView start];
}
- (void)pause {
[self.renderView pause];
}
- (void)resume {
[self.renderView resume];
}
- (void)stop {
[self.renderView stop];
}
- (void)renderBarrageDescriptor:(OCBarrageDescriptor *)barrageDescriptor {
if (!barrageDescriptor) {
return;
}
if (![barrageDescriptor isKindOfClass:[OCBarrageDescriptor class]]) {
return;
}
OCBarrageCell *barrageCell = [self.renderView dequeueReusableCellWithClass:barrageDescriptor.barrageCellClass];
if (!barrageCell) {
return;
}
barrageCell.barrageDescriptor = barrageDescriptor;
[self.renderView fireBarrageCell:barrageCell];
}
#pragma mark ------ getter
- (OCBarrageRenderView *)renderView {
return _renderView;
}
@end
@@ -0,0 +1,55 @@
//
// OCBarrageContentView.h
// TestApp
//
// Created by QMTV on 2017/8/22.
// Copyright © 2017年 LFC. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "OCBarrageCell.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, OCBarrageRenderStatus) {
OCBarrageRenderStoped = 0,
OCBarrageRenderStarted,
OCBarrageRenderPaused
};
@interface OCBarrageRenderView : UIView <CAAnimationDelegate> {
NSMutableArray<OCBarrageCell *> *_animatingCells;
NSMutableArray<OCBarrageCell *> *_idleCells;
dispatch_semaphore_t _animatingCellsLock;
dispatch_semaphore_t _idleCellsLock;
dispatch_semaphore_t _trackInfoLock;
OCBarrageCell *_lastestCell;
UIView *_lowPositionView;
UIView *_middlePositionView;
UIView *_highPositionView;
UIView *_veryHighPositionView;
BOOL _autoClear;
OCBarrageRenderStatus _renderStatus;
NSMutableDictionary *_trackNextAvailableTime;
}
@property (nonatomic, strong, readonly) NSMutableArray<OCBarrageCell *> *animatingCells;
@property (nonatomic, strong, readonly) NSMutableArray<OCBarrageCell *> *idleCells;
@property (nonatomic, assign) OCBarrageRenderPositionStyle renderPositionStyle;
@property (nonatomic, assign, readonly) OCBarrageRenderStatus renderStatus;
- (nullable OCBarrageCell *)dequeueReusableCellWithClass:(Class)barrageCellClass;
- (void)fireBarrageCell:(OCBarrageCell *)barrageCell;
- (BOOL)trigerActionWithPoint:(CGPoint)touchPoint;
@property (nonatomic, copy) void(^animationStopBlock)(void);
- (void)start;
- (void)pause;
- (void)resume;
- (void)stop;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,581 @@
//
// OCBarrageContentView.m
// TestApp
//
// Created by QMTV on 2017/8/22.
// Copyright © 2017年 LFC. All rights reserved.
//
#define kNextAvailableTimeKey(identifier, index) [NSString stringWithFormat:@"%@_%d", identifier, index]
#import "OCBarrageRenderView.h"
#import "OCBarrageTrackInfo.h"
@implementation OCBarrageRenderView
- (void)dealloc {
NSLog(@"%s", __func__);
}
- (instancetype)init {
self = [super init];
if (self) {
_animatingCellsLock = dispatch_semaphore_create(1);
_idleCellsLock = dispatch_semaphore_create(1);
_trackInfoLock = dispatch_semaphore_create(1);
_lowPositionView = [[UIView alloc] init];
[self addSubview:_lowPositionView];
_middlePositionView = [[UIView alloc] init];
[self addSubview:_middlePositionView];
_highPositionView = [[UIView alloc] init];
[self addSubview:_highPositionView];
_veryHighPositionView = [[UIView alloc] init];
[self addSubview:_veryHighPositionView];
self.layer.masksToBounds = YES;
_trackNextAvailableTime = [NSMutableDictionary dictionary];
}
return self;
}
- (nullable OCBarrageCell *)dequeueReusableCellWithClass:(Class)barrageCellClass {
OCBarrageCell *barrageCell = nil;
dispatch_semaphore_wait(_idleCellsLock, DISPATCH_TIME_FOREVER);
for (OCBarrageCell *cell in self.idleCells) {
if ([NSStringFromClass([cell class]) isEqualToString:NSStringFromClass(barrageCellClass)]) {
barrageCell = cell;
break;
}
}
if (barrageCell) {
[self.idleCells removeObject:barrageCell];
barrageCell.idleTime = 0.0;
} else {
barrageCell = [self newCellWithClass:barrageCellClass];
}
dispatch_semaphore_signal(_idleCellsLock);
if (![barrageCell isKindOfClass:[OCBarrageCell class]]) {
return nil;
}
return barrageCell;
}
- (OCBarrageCell *)newCellWithClass:(Class)barrageCellClass {
OCBarrageCell *barrageCell = [[barrageCellClass alloc] init];
if (![barrageCell isKindOfClass:[OCBarrageCell class]]) {
return nil;
}
return barrageCell;
}
- (void)start {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
return;
}
break;
case OCBarrageRenderPaused: {
[self resume];
return;
}
break;
default: {
_renderStatus = OCBarrageRenderStarted;
}
break;
}
}
- (void)pause {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
_renderStatus = OCBarrageRenderPaused;
}
break;
case OCBarrageRenderPaused: {
return;
}
break;
default: {
return;
}
break;
}
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
NSEnumerator *enumerator = [self.animatingCells reverseObjectEnumerator];
OCBarrageCell *cell = nil;
while (cell = [enumerator nextObject]){
CFTimeInterval pausedTime = [cell.layer convertTime:CACurrentMediaTime() fromLayer:nil];
cell.layer.speed = 0.0;
cell.layer.timeOffset = pausedTime;
}
dispatch_semaphore_signal(_animatingCellsLock);
}
- (void)resume {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
return;
}
break;
case OCBarrageRenderPaused: {
_renderStatus = OCBarrageRenderStarted;
}
break;
default: {
return;
}
break;
}
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
NSEnumerator *enumerator = [self.animatingCells reverseObjectEnumerator];
OCBarrageCell *cell = nil;
while (cell = [enumerator nextObject]){
CFTimeInterval pausedTime = cell.layer.timeOffset;
cell.layer.speed = 1.0;
cell.layer.timeOffset = 0.0;
cell.layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [cell.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
cell.layer.beginTime = timeSincePause;
}
dispatch_semaphore_signal(_animatingCellsLock);
}
- (void)stop {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
_renderStatus = OCBarrageRenderStoped;
}
break;
case OCBarrageRenderPaused: {
_renderStatus = OCBarrageRenderStoped;
}
break;
default: {
return;
}
break;
}
if (_autoClear) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(clearIdleCells) object:nil];
}
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
NSEnumerator *animatingEnumerator = [self.animatingCells reverseObjectEnumerator];
OCBarrageCell *animatingCell = nil;
while (animatingCell = [animatingEnumerator nextObject]){
CFTimeInterval pausedTime = [animatingCell.layer convertTime:CACurrentMediaTime() fromLayer:nil];
animatingCell.layer.speed = 0.0;
animatingCell.layer.timeOffset = pausedTime;
[animatingCell.layer removeAllAnimations];
[animatingCell removeFromSuperview];
}
[self.animatingCells removeAllObjects];
dispatch_semaphore_signal(_animatingCellsLock);
dispatch_semaphore_wait(_idleCellsLock, DISPATCH_TIME_FOREVER);
[self.idleCells removeAllObjects];
dispatch_semaphore_signal(_idleCellsLock);
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
[_trackNextAvailableTime removeAllObjects];
dispatch_semaphore_signal(_trackInfoLock);
}
- (void)fireBarrageCell:(OCBarrageCell *)barrageCell {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
}
break;
case OCBarrageRenderPaused: {
return;
}
break;
default:
return;
break;
}
if (!barrageCell) {
return;
}
if (![barrageCell isKindOfClass:[OCBarrageCell class]]) {
return;
}
[barrageCell clearContents];
[barrageCell updateSubviewsData];
[barrageCell layoutContentSubviews];
[barrageCell convertContentToImage];
[barrageCell sizeToFit];
[barrageCell removeSubViewsAndSublayers];
[barrageCell addBorderAttributes];
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
_lastestCell = [self.animatingCells lastObject];
[self.animatingCells addObject:barrageCell];
barrageCell.idle = NO;
dispatch_semaphore_signal(_animatingCellsLock);
[self addBarrageCell:barrageCell WithPositionPriority:barrageCell.barrageDescriptor.positionPriority];
CGRect cellFrame = [self calculationBarrageCellFrame:barrageCell];
barrageCell.frame = cellFrame;
[barrageCell addBarrageAnimationWithDelegate:self];
[self recordTrackInfoWithBarrageCell:barrageCell];
_lastestCell = barrageCell;
}
- (void)addBarrageCell:(OCBarrageCell *)barrageCell WithPositionPriority:(OCBarragePositionPriority)positionPriority {
switch (positionPriority) {
case OCBarragePositionMiddle: {
[self insertSubview:barrageCell aboveSubview:_middlePositionView];
}
break;
case OCBarragePositionHigh: {
[self insertSubview:barrageCell belowSubview:_highPositionView];
}
break;
case OCBarragePositionVeryHigh: {
[self insertSubview:barrageCell belowSubview:_veryHighPositionView];
}
break;
default: {
[self insertSubview:barrageCell belowSubview:_lowPositionView];
}
break;
}
}
- (CGRect)calculationBarrageCellFrame:(OCBarrageCell *)barrageCell {
CGRect cellFrame = barrageCell.bounds;
cellFrame.origin.x = CGRectGetMaxX(self.frame);
if (![[NSValue valueWithRange:barrageCell.barrageDescriptor.renderRange] isEqualToValue:[NSValue valueWithRange:NSMakeRange(0, 0)]]) {
CGFloat cellHeight = CGRectGetHeight(barrageCell.bounds);
CGFloat minOriginY = barrageCell.barrageDescriptor.renderRange.location;
CGFloat maxOriginY = barrageCell.barrageDescriptor.renderRange.length;
if (maxOriginY > CGRectGetHeight(self.bounds)) {
maxOriginY = CGRectGetHeight(self.bounds);
}
if (minOriginY < 0) {
minOriginY = 0;
}
CGFloat renderHeight = maxOriginY - minOriginY;
if (renderHeight < 0) {
renderHeight = cellHeight;
}
int trackCount = floorf(renderHeight/cellHeight);
int trackIndex = arc4random_uniform(trackCount);//用户改变行高(比如弹幕文字大小不会引起显示bug, 因为虽然是同一个类, 但是trackCount变小了, 所以不会出现trackIndex*cellHeight超出屏幕边界的情况)
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
OCBarrageTrackInfo *trackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), trackIndex)];
if (trackInfo && trackInfo.nextAvailableTime > CACurrentMediaTime()) {//当前行暂不可用
NSMutableArray *availableTrackInfos = [NSMutableArray array];
for (OCBarrageTrackInfo *info in _trackNextAvailableTime.allValues) {
if (CACurrentMediaTime() > info.nextAvailableTime && [info.trackIdentifier containsString:NSStringFromClass([barrageCell class])]) {//只在同类弹幕中判断是否有可用的轨道
[availableTrackInfos addObject:info];
}
}
if (availableTrackInfos.count > 0) {
OCBarrageTrackInfo *randomInfo = [availableTrackInfos objectAtIndex:arc4random_uniform((int)availableTrackInfos.count)];
trackIndex = randomInfo.trackIndex;
} else {
if (_trackNextAvailableTime.count < trackCount) {//刚开始不是每一条轨道都跑过弹幕, 还有空轨道
NSMutableArray *numberArray = [NSMutableArray array];
for (int index = 0; index < trackCount; index++) {
OCBarrageTrackInfo *emptyTrackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), index)];
if (!emptyTrackInfo) {
[numberArray addObject:[NSNumber numberWithInt:index]];
}
}
if (numberArray.count > 0) {
trackIndex = [[numberArray objectAtIndex:arc4random_uniform((int)numberArray.count)] intValue];
}
}
//真的是没有可用的轨道了
}
}
dispatch_semaphore_signal(_trackInfoLock);
barrageCell.trackIndex = trackIndex;
cellFrame.origin.y = trackIndex*cellHeight+minOriginY;
} else {
switch (self.renderPositionStyle) {
case OCBarrageRenderPositionRandom: {
CGFloat maxY = CGRectGetHeight(self.bounds) - CGRectGetHeight(cellFrame);
int originY = floorl(maxY);
cellFrame.origin.y = arc4random_uniform(originY);
}
break;
case OCBarrageRenderPositionIncrease: {
if (_lastestCell) {
CGRect lastestFrame = _lastestCell.frame;
cellFrame.origin.y = CGRectGetMaxY(lastestFrame);
} else {
cellFrame.origin.y = 0.0;
}
}
break;
default: {
CGFloat renderViewHeight = CGRectGetHeight(self.bounds);
CGFloat cellHeight = CGRectGetHeight(barrageCell.bounds);
int trackCount = floorf(renderViewHeight/cellHeight);
int trackIndex = arc4random_uniform(trackCount);//用户改变行高(比如弹幕文字大小不会引起显示bug, 因为虽然是同一个类, 但是trackCount变小了, 所以不会出现trackIndex*cellHeight超出屏幕边界的情况)
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
OCBarrageTrackInfo *trackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), trackIndex)];
if (trackInfo && trackInfo.nextAvailableTime > CACurrentMediaTime()) {//当前行暂不可用
NSMutableArray *availableTrackInfos = [NSMutableArray array];
for (OCBarrageTrackInfo *info in _trackNextAvailableTime.allValues) {
if (CACurrentMediaTime() > info.nextAvailableTime && [info.trackIdentifier containsString:NSStringFromClass([barrageCell class])]) {//只在同类弹幕中判断是否有可用的轨道
[availableTrackInfos addObject:info];
}
}
if (availableTrackInfos.count > 0) {
OCBarrageTrackInfo *randomInfo = [availableTrackInfos objectAtIndex:arc4random_uniform((int)availableTrackInfos.count)];
trackIndex = randomInfo.trackIndex;
} else {
if (_trackNextAvailableTime.count < trackCount) {//刚开始不是每一条轨道都跑过弹幕, 还有空轨道
NSMutableArray *numberArray = [NSMutableArray array];
for (int index = 0; index < trackCount; index++) {
OCBarrageTrackInfo *emptyTrackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), index)];
if (!emptyTrackInfo) {
[numberArray addObject:[NSNumber numberWithInt:index]];
}
}
if (numberArray.count > 0) {
trackIndex = [[numberArray objectAtIndex:arc4random_uniform((int)numberArray.count)] intValue];
}
}
//真的是没有可用的轨道了
}
}
dispatch_semaphore_signal(_trackInfoLock);
barrageCell.trackIndex = trackIndex;
cellFrame.origin.y = trackIndex*cellHeight;
}
break;
}
}
if (CGRectGetMaxY(cellFrame) > CGRectGetHeight(self.bounds)) {
cellFrame.origin.y = 0.0; //超过底部, 回到顶部
} else if (cellFrame.origin.y < 0) {
cellFrame.origin.y = 0.0;
}
return cellFrame;
}
- (void)clearIdleCells {
dispatch_semaphore_wait(_idleCellsLock, DISPATCH_TIME_FOREVER);
NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970];
NSEnumerator *enumerator = [self.idleCells reverseObjectEnumerator];
OCBarrageCell *cell;
while (cell = [enumerator nextObject]){
CGFloat time = timeInterval - cell.idleTime;
if (time > 5.0 && cell.idleTime > 0) {
[self.idleCells removeObject:cell];
}
}
if (self.idleCells.count == 0) {
_autoClear = NO;
} else {
[self performSelector:@selector(clearIdleCells) withObject:nil afterDelay:5.0];
}
dispatch_semaphore_signal(_idleCellsLock);
}
- (void)recordTrackInfoWithBarrageCell:(OCBarrageCell *)barrageCell {
NSString *nextAvalibleTimeKey = kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), barrageCell.trackIndex);
CFTimeInterval duration = barrageCell.barrageAnimation.duration;
NSValue *fromValue = nil;
NSValue *toValue = nil;
if ([barrageCell.barrageAnimation isKindOfClass:[CABasicAnimation class]]) {
fromValue = [(CABasicAnimation *)barrageCell.barrageAnimation fromValue];
toValue = [(CABasicAnimation *)barrageCell.barrageAnimation toValue];
} else if ([barrageCell.barrageAnimation isKindOfClass:[CAKeyframeAnimation class]]) {
fromValue = [[(CAKeyframeAnimation *)barrageCell.barrageAnimation values] firstObject];
toValue = [[(CAKeyframeAnimation *)barrageCell.barrageAnimation values] lastObject];
} else {
}
const char *fromeValueType = [fromValue objCType];
const char *toValueType = [toValue objCType];
if (!fromeValueType || !toValueType) {
return;
}
NSString *fromeValueTypeString = [NSString stringWithCString:fromeValueType encoding:NSUTF8StringEncoding];
NSString *toValueTypeString = [NSString stringWithCString:toValueType encoding:NSUTF8StringEncoding];
if (![fromeValueTypeString isEqualToString:toValueTypeString]) {
return;
}
if ([fromeValueTypeString containsString:@"CGPoint"]) {
CGPoint fromPoint = [fromValue CGPointValue];
CGPoint toPoint = [toValue CGPointValue];
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
OCBarrageTrackInfo *trackInfo = [_trackNextAvailableTime objectForKey:nextAvalibleTimeKey];
if (!trackInfo) {
trackInfo = [[OCBarrageTrackInfo alloc] init];
trackInfo.trackIdentifier = nextAvalibleTimeKey;
trackInfo.trackIndex = barrageCell.trackIndex;
}
trackInfo.barrageCount++;
trackInfo.nextAvailableTime = CGRectGetWidth(barrageCell.bounds);
CGFloat distanceX = fabs(toPoint.x - fromPoint.x);
CGFloat distanceY = fabs(toPoint.y - fromPoint.y);
CGFloat distance = MAX(distanceX, distanceY);
CGFloat speed = distance/duration;
if (distanceX == distance) {
CFTimeInterval time = CGRectGetWidth(barrageCell.bounds)/speed;
trackInfo.nextAvailableTime = CACurrentMediaTime() + time + 0.1;//多加一点时间
[_trackNextAvailableTime setValue:trackInfo forKey:nextAvalibleTimeKey];
} else if (distanceY == distance) {
// CFTimeInterval time = CGRectGetHeight(barrageCell.bounds)/speed;
} else {
}
dispatch_semaphore_signal(_trackInfoLock);
return;
} else if ([fromeValueTypeString containsString:@"CGVector"]) {
return;
} else if ([fromeValueTypeString containsString:@"CGSize"]) {
return;
} else if ([fromeValueTypeString containsString:@"CGRect"]) {
return;
} else if ([fromeValueTypeString containsString:@"CGAffineTransform"]) {
return;
} else if ([fromeValueTypeString containsString:@"UIEdgeInsets"]) {
return;
} else if ([fromeValueTypeString containsString:@"UIOffset"]) {
return;
}
}
#pragma mark ----- CAAnimationDelegate
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
!self.animationStopBlock ?: self.animationStopBlock();
if (!flag) {
return;
}
if (self.renderStatus == OCBarrageRenderStoped) {
return;
}
OCBarrageCell *animationedCell = nil;
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
for (OCBarrageCell *cell in self.animatingCells) {
CAAnimation *barrageAnimation = [cell barrageAnimation];
if (barrageAnimation == anim) {
animationedCell = cell;
[self.animatingCells removeObject:cell];
break;
}
}
dispatch_semaphore_signal(_animatingCellsLock);
if (!animationedCell) {
return;
}
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
OCBarrageTrackInfo *trackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([animationedCell class]), animationedCell.trackIndex)];
if (trackInfo) {
trackInfo.barrageCount--;
}
dispatch_semaphore_signal(_trackInfoLock);
[animationedCell removeFromSuperview];
[animationedCell prepareForReuse];
dispatch_semaphore_wait(_idleCellsLock, DISPATCH_TIME_FOREVER);
animationedCell.idleTime = [[NSDate date] timeIntervalSince1970];
[self.idleCells addObject:animationedCell];
dispatch_semaphore_signal(_idleCellsLock);
if (!_autoClear) {
[self performSelector:@selector(clearIdleCells) withObject:nil afterDelay:5.0];
_autoClear = YES;
}
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (event.type == UIEventTypeTouches) {
UITouch *touch = [touches.allObjects firstObject];
CGPoint touchPoint = [touch locationInView:self];
[self trigerActionWithPoint:touchPoint];
}
}
- (BOOL)trigerActionWithPoint:(CGPoint)touchPoint
{
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
BOOL anyTriger = NO;
NSEnumerator *enumerator = [self.animatingCells reverseObjectEnumerator];
OCBarrageCell *cell = nil;
while (cell = [enumerator nextObject]){
if ([cell.layer.presentationLayer hitTest:touchPoint]) {
if (cell.barrageDescriptor.touchAction) {
cell.barrageDescriptor.touchAction(cell.barrageDescriptor);
anyTriger = YES;
}
if (cell.barrageDescriptor.cellTouchedAction) {
cell.barrageDescriptor.cellTouchedAction(cell.barrageDescriptor, cell);
anyTriger = YES;
}
break;
}
}
dispatch_semaphore_signal(_animatingCellsLock);
return anyTriger;
}
#pragma mark ----- getter
- (NSMutableArray<OCBarrageCell *> *)animatingCells {
if (!_animatingCells) {
_animatingCells = [[NSMutableArray alloc] init];
}
return _animatingCells;
}
- (NSMutableArray<OCBarrageCell *> *)idleCells {
if (!_idleCells) {
_idleCells = [[NSMutableArray alloc] init];
}
return _idleCells;
}
- (OCBarrageRenderStatus)renderStatus {
return _renderStatus;
}
@end
@@ -0,0 +1,21 @@
//
// OCBarrageTextCell.h
// TestApp
//
// Created by QMTV on 2017/8/23.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageCell.h"
#import "OCBarrageTextDescriptor.h"
NS_ASSUME_NONNULL_BEGIN
@interface OCBarrageTextCell : OCBarrageCell
@property (nonatomic, strong) UILabel *textLabel;
@property (nonatomic, strong, nullable) OCBarrageTextDescriptor *textDescriptor;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,100 @@
//
// OCBarrageTextCell.m
// TestApp
//
// Created by QMTV on 2017/8/23.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageTextCell.h"
@implementation OCBarrageTextCell
- (instancetype)init {
self = [super init];
if (self) {
}
return self;
}
- (void)prepareForReuse {
[super prepareForReuse];
}
- (void)updateSubviewsData {
if (!_textLabel) {
[self addSubview:self.textLabel];
}
if (self.textDescriptor.textShadowOpened) {
self.textLabel.layer.shadowColor = self.textDescriptor.shadowColor.CGColor;
self.textLabel.layer.shadowOffset = self.textDescriptor.shadowOffset;
self.textLabel.layer.shadowRadius = self.textDescriptor.shadowRadius;
self.textLabel.layer.shadowOpacity = self.textDescriptor.shadowOpacity;
}
[self.textLabel setAttributedText:self.textDescriptor.attributedText];
}
- (void)layoutContentSubviews {
// CGRect textFrame = [self.textDescriptor.attributedText.string boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:[self.textDescriptor.attributedText attributesAtIndex:0 effectiveRange:NULL] context:nil];
// self.textLabel.frame = textFrame;
self.textLabel.frame = CGRectMake(0, 0, self.textLabel.intrinsicContentSize.width, self.textLabel.intrinsicContentSize.height + 20);
}
- (void)convertContentToImage {
UIImage *contentImage = [self.layer convertContentToImageWithSize:_textLabel.frame.size];
[self.layer setContents:(__bridge id)contentImage.CGImage];
}
- (void)removeSubViewsAndSublayers {
[super removeSubViewsAndSublayers];
_textLabel = nil;
}
- (void)addBarrageAnimationWithDelegate:(id<CAAnimationDelegate>)animationDelegate {
if (!self.superview) {
return;
}
CGPoint startCenter = CGPointMake(CGRectGetMaxX(self.superview.bounds) + CGRectGetWidth(self.bounds)/2, self.center.y);
CGPoint endCenter = CGPointMake(-(CGRectGetWidth(self.bounds)/2), self.center.y);
CGFloat animationDuration = self.barrageDescriptor.animationDuration;
if (self.barrageDescriptor.fixedSpeed > 0.0) {//如果是固定速度那就用固定速度
if (self.barrageDescriptor.fixedSpeed > 100.0) {
self.barrageDescriptor.fixedSpeed = 100.0;
}
animationDuration = (startCenter.x - endCenter.x)/([UIScreen mainScreen].scale*2)/self.barrageDescriptor.fixedSpeed;
}
CAKeyframeAnimation *walkAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
walkAnimation.values = @[[NSValue valueWithCGPoint:startCenter], [NSValue valueWithCGPoint:endCenter]];
walkAnimation.keyTimes = @[@(0.0), @(1.0)];
walkAnimation.duration = animationDuration;
walkAnimation.repeatCount = 1;
walkAnimation.delegate = animationDelegate;
walkAnimation.removedOnCompletion = NO;
walkAnimation.fillMode = kCAFillModeForwards;
[self.layer addAnimation:walkAnimation forKey:kBarrageAnimation];
}
- (UILabel *)textLabel {
if (!_textLabel) {
_textLabel = [[UILabel alloc] init];
_textLabel.textAlignment = NSTextAlignmentCenter;
}
return _textLabel;
}
- (void)setBarrageDescriptor:(OCBarrageDescriptor *)barrageDescriptor {
[super setBarrageDescriptor:barrageDescriptor];
self.textDescriptor = (OCBarrageTextDescriptor *)barrageDescriptor;
}
@end
@@ -0,0 +1,37 @@
//
// OCBarrageTextDescriptor.h
// TestApp
//
// Created by QMTV on 2017/8/23.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageDescriptor.h"
NS_ASSUME_NONNULL_BEGIN
@interface OCBarrageTextDescriptor : OCBarrageDescriptor {
NSMutableDictionary *_textAttribute;
}
@property (nonatomic, strong, nullable) UIFont *textFont;
@property (nonatomic, strong, nullable) UIColor *textColor;
/*
* 关闭文字阴影可大幅提升性能, 推荐使用strokeColor, 与shadowColor相比strokeColor性能更强悍
*/
@property (nonatomic, assign) BOOL textShadowOpened;//默认NO
@property (nonatomic, strong, nullable) UIColor *shadowColor;//默认黑色
@property (nonatomic, assign) CGSize shadowOffset;//默认CGSizeZero
@property (nonatomic, assign) CGFloat shadowRadius;//默认2.0
@property (nonatomic, assign) CGFloat shadowOpacity;//默认0.5
@property (nonatomic, strong, nullable) UIColor *strokeColor;
@property (nonatomic, assign) int strokeWidth;//笔画宽度(粗细),取值为 NSNumber 对象(整数),负值填充效果,正值中空效果
@property (nonatomic, copy, nullable) NSString *text;
@property (nonatomic, copy, nullable) NSAttributedString *attributedText;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,191 @@
//
// OCBarrageTextDescriptor.m
// TestApp
//
// Created by QMTV on 2017/8/23.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageTextDescriptor.h"
@implementation OCBarrageTextDescriptor
@synthesize textFont = _textFont, textColor = _textColor, shadowColor = _shadowColor, attributedText = _attributedText;
- (instancetype)init {
self = [super init];
if (self) {
_textAttribute = [NSMutableDictionary dictionary];
_shadowColor = [UIColor blackColor];
_shadowOffset = CGSizeZero;
_shadowRadius = 2.0;
_shadowOpacity = 0.5;
}
return self;
}
#pragma mark ----- setter
- (void)setTextShadowOpened:(BOOL)textShadowOpened {
_textShadowOpened = textShadowOpened;
if (textShadowOpened) {
[_textAttribute removeObjectForKey:NSStrokeColorAttributeName];
[_textAttribute removeObjectForKey:NSStrokeWidthAttributeName];
}
}
- (void)setTextFont:(UIFont *)textFont {
_textFont = textFont;
[_textAttribute setValue:textFont forKey:NSFontAttributeName];
}
- (void)setTextColor:(UIColor *)textColor {
_textColor = textColor;
[_textAttribute setValue:textColor forKey:NSForegroundColorAttributeName];
}
- (void)setStrokeColor:(UIColor *)strokeColor {
_strokeColor = strokeColor;
if (_textShadowOpened) {
return;
}
[_textAttribute setValue:strokeColor forKey:NSStrokeColorAttributeName];
}
- (void)setStrokeWidth:(int)strokeWidth {
_strokeWidth = strokeWidth;
if (_textShadowOpened) {
return;
}
[_textAttribute setValue:[NSNumber numberWithInt:strokeWidth] forKey:NSStrokeWidthAttributeName];
}
#pragma mark ----- getter
- (NSString *)text {
if (!_text) {
_text = _attributedText.string;
}
return _text;
}
- (UIFont *)textFont {
if (!_textFont) {
_textFont = [UIFont systemFontOfSize:17.0];
}
return _textFont;
}
- (UIColor *)textColor {
if (!_textColor) {
_textColor = [UIColor whiteColor];
}
return _textColor;
}
- (UIColor *)shadowColor {
if (!_shadowColor) {
_shadowColor = [UIColor blackColor];
}
return _shadowColor;
}
- (NSAttributedString *)attributedText {
if (!_attributedText) {
if (!_text) {
return nil;
}
_attributedText = [[NSAttributedString alloc] initWithString:_text attributes:_textAttribute];
}
//修复阿拉伯文字显示的bug.
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setBaseWritingDirection:NSWritingDirectionLeftToRight];
NSMutableAttributedString *tempText = [[NSMutableAttributedString alloc] initWithAttributedString:_attributedText];
[tempText addAttributes:@{NSParagraphStyleAttributeName:paragraphStyle} range:NSMakeRange(0, tempText.string.length)];
return [tempText copy];
}
- (id)mutableCopy {
return [self copy];
}
@end
/**
* API: Character Attributes , NSAttributedString 共有21个属性
*
* 1. NSFontAttributeName ->设置字体属性,默认值:字体:Helvetica(Neue) 字号:12
*
*
* 2. NSParagraphStyleAttributeName ->设置文本段落排版格式,取值为 NSParagraphStyle 对象(详情见下面的API说明)
*
*
* 3. NSForegroundColorAttributeName ->设置字体颜色,取值为 UIColor对象,默认值为黑色
*
*
* 4. NSBackgroundColorAttributeName ->设置字体所在区域背景颜色,取值为 UIColor对象,默认值为nil, 透明色
*
*
* 5. NSLigatureAttributeName ->设置连体属性,取值为NSNumber 对象(整数),0 表示没有连体字符,1 表示使用默认的连体字符
*
*
* 6. NSKernAttributeName ->设置字符间距,取值为 NSNumber 对象(整数),正值间距加宽,负值间距变窄
*
*
* 7. NSStrikethroughStyleAttributeName ->设置删除线,取值为 NSNumber 对象(整数)
*
*
* 8. NSStrikethroughColorAttributeName ->设置删除线颜色,取值为 UIColor 对象,默认值为黑色
*
*
* 9. NSUnderlineStyleAttributeName ->设置下划线,取值为 NSNumber 对象(整数),枚举常量 NSUnderlineStyle中的值,与删除线类似
*
*
* 10. NSUnderlineColorAttributeName ->设置下划线颜色,取值为 UIColor 对象,默认值为黑色
*
*
* 11. NSStrokeWidthAttributeName ->设置笔画宽度(粗细),取值为 NSNumber 对象(整数),负值填充效果,正值中空效果
*
*
* 12. NSStrokeColorAttributeName ->填充部分颜色,不是字体颜色,取值为 UIColor 对象
*
*
* 13. NSShadowAttributeName ->设置阴影属性,取值为 NSShadow 对象
*
*
* 14. NSTextEffectAttributeName ->设置文本特殊效果,取值为 NSString 对象,目前只有图版印刷效果可用
*
*
* 15. NSBaselineOffsetAttributeName ->设置基线偏移值,取值为 NSNumber (float),正值上偏,负值下偏
*
*
* 16. NSObliquenessAttributeName ->设置字形倾斜度,取值为 NSNumber (float),正值右倾,负值左倾
*
*
* 17. NSExpansionAttributeName ->设置文本横向拉伸属性,取值为 NSNumber (float),正值横向拉伸文本,负值横向压缩文本
*
*
* 18. NSWritingDirectionAttributeName ->设置文字书写方向,从左向右书写或者从右向左书写
*
*
* 19. NSVerticalGlyphFormAttributeName ->设置文字排版方向,取值为 NSNumber 对象(整数),0 表示横排文本,1 表示竖排文本
*
*
* 20. NSLinkAttributeName ->设置链接属性,点击后调用浏览器打开指定URL地址
*
*
* 21. NSAttachmentAttributeName ->设置文本附件,取值为NSTextAttachment对象,常用于文字图片混排
*
*/
@@ -0,0 +1,24 @@
//
// OCBarrageTrackInfo.h
// OCBarrage
//
// Created by QMTV on 2017/8/25.
// Copyright © 2017年 LFC. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface OCBarrageTrackInfo : NSObject
@property (nonatomic, assign) int trackIndex;
@property (nonatomic, copy, nullable) NSString *trackIdentifier;
@property (nonatomic, assign) CFTimeInterval nextAvailableTime;//下次可用的时间
@property (nonatomic, assign) NSInteger barrageCount;//当前行的弹幕数量
@property (nonatomic, assign) CGFloat trackHeight;//轨道高度
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,13 @@
//
// OCBarrageTrackInfo.m
// OCBarrage
//
// Created by QMTV on 2017/8/25.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageTrackInfo.h"
@implementation OCBarrageTrackInfo
@end