• 前段时间公司APP要对直播间的礼物系统进行改版,由于以前直播的收入不在于礼物分成,所以以前的礼物系统是很简单的一个展示而已.为适应主流直播间的礼物效果,特由此改版!

1. 所有直播间的礼物系统,第一步用户看到的无外乎都是礼物的列表界面

  • 纵观主流直播间的礼物列表应该都是使用UICollectionView实现的,所以我也不例外,下面就是各种撸代码.效果如下

  • 看着效果还不错吧.但是但是我突然发现一个问题.礼物展示的顺序跟我想要的顺序不一样,跟数据的排序也不一致.看图来说

  • 黄色的顺序是我们想要的顺序,但是现在顺序确是红色的.为什么呢?我们都知道collectionview的滚动方向是有layout控制的.代码如下
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];layout.itemSize = CGSizeMake(itemW, itemH);layout.minimumLineSpacing = 0;layout.minimumInteritemSpacing = 0;layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
  • 看代码之后才明白,因为我们设置的滚动方向是横向滚动,所以系统会默认先把垂直方向的Item填充,然后再横向填充,这就不难解释为啥会是这种排序.如果换成垂直滚动呢?

  • 这样也不满足我们的需求,既然系统的不行,那么只有拿出独门武器,自定义一个flowlayout吧.让它按照我们的要求去滚动,去排序.
- (void)prepareLayout {//自定义layout都必须重写这个方法[super prepareLayout];//设置基本属性CGFloat itemW = SCREEN_WIDTH/4.0;CGFloat itemH = itemW*105/93.8;self.itemSize = CGSizeMake(itemW, itemH);self.minimumLineSpacing = 0;self.minimumInteritemSpacing = 0;self.scrollDirection = UICollectionViewScrollDirectionHorizontal;//刷新后清除所有已布局的属性 重新获取[self.cellAttributesArray removeAllObjects];NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];for (NSInteger i = 0; i < cellCount; i++) {//取出每一个的Item的布局.重新赋值NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];UICollectionViewLayoutAttributes *attibute = [self layoutAttributesForItemAtIndexPath:indexPath];NSInteger page = i / 8;//第几页NSInteger row = i % 4 + page*4;//第几列NSInteger col = i / 4 - page*2;//第几行attibute.frame = CGRectMake(row*itemW, col*itemH, itemW, itemH);//保存所有已经重新赋值的布局[self.cellAttributesArray addObject:attibute];}
}- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{//返回当前可见区域内的已经计算好的布局return self.cellAttributesArray;
}
  • 写出来之后心里沾沾自喜,这样应该可以实现了吧.看看效果吧


* 应该可以看出来问题了吧,我选中的那个礼物第一页和第二页竟然都出现了,我明明设置了分页滚动的呀.查看层级结构如下


* 原来是可爱的么么哒礼物被挤到外面了.由于没有设置弹簧的效果,所以没太注意少了一个礼物,那么原因呢? 想了好久才想起来是不是滚动的范围不够,导致么么哒不显示在界面中呢?又去扒了扒怎么设置自定义的layout的contentoffset.最终找到一个方法.

- (CGSize)collectionViewContentSize{NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];NSInteger page = cellCount / 8 + 1;return CGSizeMake(SCREEN_WIDTH*page, 0);
}
  • 但是这样做真的可以么?看看效果吧

  • 到此为止基本实现了一个主流的礼物列表界面.关于礼物的点击逻辑看看代码就可以了.在此就不多啰嗦了.(详见代码 – JPGiftView)

2. 点击发送之后的礼物动画效果展示

  • 最简单的实现就是创建一个View在点击发送后把当前选中的礼物信息传入这个展示礼物效果的view中,写一个位移的动画进行展示.如果连送,那么就在view展示之前计算好一共连击多少次礼物,然后直接展示x几.如图


* 但是这样的弊端肯定是很多,比如我会将一个用户送其中一个礼物这样算成一个完整的实际的礼物.同一个用户送不同的礼物算是第二个完整的礼物.那么每一个完整的礼物都是唯一的存在.如果使用上面的逻辑来处理,那么你会发现出现各种让你忍俊不禁的bug,比如,不同礼物的累加,不同礼物会进行顶替正在展示的当前礼物…..
* 既然知道了bug的存在,那么怎么解决呢?首先我脑海中第一个想到的就是强大的队列,一个苹果帮我们封装好的面向对象的类 – NSOperationQueue .这样我们就可以将每一个完整的礼物当成一个操作 – NSOperation .加入队列中,这样就会自动按照顺序去执行礼物的展示.道理和逻辑都想通了,怎么实现是需要好好斟酌下咯!
* 俗话说代码是不会骗人的,当我将一个个操作加入到队列中的时候,又出bug.并没有按照我们设想的一个个按照排队的顺序去执行.(系统有个依赖方法,但是想了想不太能实现需求,也就没试)随后去Google了一下,才知道原来系统提供的API只能加入操作,并不能在上一个操作结束的时候再去执行下一个操作.如果需要按照顺序执行,就要自定义一个操作,然后在一个完整礼物礼物动画展示完成后结束当前操作,那么才会按顺序去执行下一个操作!
* 具体的代码可见 JPGiftOperation类
* 自定义操作的主要是改变操作的两个属性 下图所示,默认改为NO.使用@synthesize禁止系统的GET/SET,有开发者自己控制
* 我们需要重写star方法来创建操作(礼物动画的展示)

- (void)start {if ([self isCancelled]) {_finished = YES;return;}_executing = YES;NSLog(@"当前队列-- %@",self.model.giftName);[[NSOperationQueue mainQueue] addOperationWithBlock:^{[self.giftShowView showGiftShowViewWithModel:self.model completeBlock:^(BOOL finished,NSString *giftKey) {self.finished = finished;if (self.opFinishedBlock) {self.opFinishedBlock(finished,giftKey);}}];}];}

//当动画结束时 self.finished = YES; 然后手动触发KVO改变当前操作的状态
#pragma mark -  手动触发 KVO
- (void)setExecuting:(BOOL)executing
{[self willChangeValueForKey:@"isExecuting"];_executing = executing;[self didChangeValueForKey:@"isExecuting"];
}- (void)setFinished:(BOOL)finished
{[self willChangeValueForKey:@"isFinished"];_finished = finished;[self didChangeValueForKey:@"isFinished"];
}
  • 这样在动画结束的时候,我们就能控制当前的操作也结束了.那么系统会自动去队列中执行下一个存在的操作.基本实现了队列的效果.


* 实现了队列的效果后,那么下一步,如果用户对一个礼物进行连击操作.该怎么实现呢?看看现在的连击是什么效果吧


* 这是什么鬼,这是连击么.
* 看来我们需要一个管理类来管理礼物的展示逻辑,按照一定的规则创建操作,加入队列. 这样 JPGiftShowManager类应运而生.
* 我们需要在拿到当前点击的礼物信息时,就可以判断这个礼物的具体该怎么展示,是排队等着展示还是在当前展示的礼物的连击,或者是排队等待展示的礼物的累加等情况,这样所有的逻辑都在这个管理类中实现,外部最少可以只需一句代码传入礼物的数据就可以完美的展示一个礼物的动效了.想想就是很好的.
* 让我们写一个展示礼物的方法入口吧,单例就不说了.
*

/**送礼物 @param backView 礼物动效展示父view
 @param giftModel 礼物的数据
 @param completeBlock 展示完毕回调*/- (void)showGiftViewWithBackView:(UIView *)backViewinfo:(JPGiftModel *)giftModelcompleteBlock:(completeBlock)completeBlock;
  • 前面说过每一个完整的礼物就是一个唯一的存在,只有相同的完整礼物才会执行连击或者累加的操作.那么怎么区别唯一的礼物呢.我在礼物的Model中放了一个属性 giftKey 使用礼物名和礼物的ID进行拼接而成(我在实际项目中是使用用户的ID+礼物ID拼接,这样肯定可以保证唯一性)
/** 礼物操作的唯一Key */
@property(nonatomic,copy)NSString *giftKey;//在.m中 自己写get方法
- (NSString *)giftKey {return [NSString stringWithFormat:@"%@%@",self.giftName,self.giftId];
}
  • 那么这样的话我们在管理类中还至少需要两个容器,来存储已经传进来的key和已经创建的操作.
/** 操作缓存 */
@property (nonatomic,strong) NSCache *operationCache;
/** 当前礼物的key */
@property(nonatomic,strong) NSString *curentGiftKey;
  • 最终的思路慢慢就确定了,当我们拿到一个新的礼物数据的时候,那么我们就要判断礼物的key是否与curentGiftKey相同,礼物的key对应的操作是否在operationCache中.
    if (self.curentGiftKey && [self.curentGiftKey isEqualToString:giftModel.giftKey]) {//有当前的礼物信息if ([self.operationCache objectForKey:giftModel.giftKey]) {//当前存在操作 那么就可以在当前操作上累加礼物 出现连击效果}else {//当前操作已结束 重新创建JPGiftOperation *operation = [JPGiftOperation addOperationWithView:showView OnView:backView Info:giftModel completeBlock:^(BOOL finished,NSString *giftKey) {if (self.finishedBlock) {self.finishedBlock(finished);}//移除操作[self.operationCache removeObjectForKey:giftKey];//清空唯一keyself.curentGiftKey = @"";}];//存储操作信息[self.operationCache setObject:operation forKey:giftModel.giftKey];//操作加入队列[queue addOperation:operation];}}else {//没有礼物的信息if ([self.operationCache objectForKey:giftModel.giftKey]) {//当前存在操作 说明是有礼物在排队等待展示}else {//当前第一次展示这个礼物JPGiftOperation *operation = [JPGiftOperation addOperationWithView:showView OnView:backView Info:giftModel completeBlock:^(BOOL finished,NSString *giftKey) {if (self.finishedBlock) {self.finishedBlock(finished);}//移除操作[self.operationCache removeObjectForKey:giftKey];//清空唯一key[self.curentGiftKeys removeObject:giftKey];}];operation.model.defaultCount += giftModel.sendCount;//存储操作信息[self.operationCache setObject:operation forKey:giftModel.giftKey];//操作加入队列[queue addOperation:operation];}}
  • 可能有的同学疑问了,这个当前礼物的key–self.curentGiftKey怎么得来的呢? 请看这段代码
        [_giftShowView setShowViewKeyBlock:^(JPGiftModel *giftModel) {_curentGiftKey = giftModel.giftKey;}];
  • 我在操作的star方法调用礼物展示的动画的时候进行回调,判断条件当前第一次展示这个礼物,把key回调给管理类.
    if (self.showViewKeyBlock && self.currentGiftCount == 0) {self.showViewKeyBlock(giftModel);}
  • 这样我们就可以拿到当前展示的key了.通过判断是创建新的操作还是进行连击的逻辑.
  • 虽然逻辑已经有了,但是具体的怎么实现连击的效果呢?因为我们的动画我是在show完之后,使用dispatch_after进行隐藏并移除的.想要实现连击,首先就要先解决怎么在连击的过程中,不会让礼物展示的动画结束消失.所以我就想到应该在礼物累加的过程中取消这个延迟执行的方法,取消完之后在创建延迟执行的方法.这样每一次连击的时候等于是重新创建了这个隐藏动画的方法.
  • 最后查了资料使用dispatch_after还无法实现这个需求.找到了一个方法可以实现.只要当前展示的礼物的个数大于1了,就会去执行这个逻辑,取消-创建.如果就一个礼物那么就按照正常的逻辑取消动画.
if (self.currentGiftCount > 1) {[self p_SetAnimation:self.countLabel];[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hiddenGiftShowView) object:nil];//可以取消成功。[self performSelector:@selector(hiddenGiftShowView) withObject:nil afterDelay:animationTime];}else {[self performSelector:@selector(hiddenGiftShowView) withObject:nil afterDelay:animationTime];}
  • 具体的连击代码是通过什么实现的呢?在展示礼物动画的view中有两个属性.一个传进来的用户当前点击所送的礼物总数(此处默认都是1),一个是当前展示的礼物总数.
/** 礼物数 */
@property(nonatomic,assign) NSInteger giftCount;
/** 当前礼物总数 */
@property(nonatomic,assign) NSInteger currentGiftCount;
  • 什么时候会发生连击效果和排队累加效果呢?
  • 连击效果 - 当前展示的self.curentGiftKey和拿到的新的礼物的key是一致的并且操作缓冲池中还存在当前key对应的操作.这样会发生连击效果.那么此时我们只需要给giftCount赋值用户选中的礼物数(当前默认都是一次送一个).
            JPGiftOperation *op = [self.operationCache objectForKey:giftModel.giftKey];op.giftShowView.giftCount = giftModel.sendCount;//限制一次礼物的连击最大值if (op.giftShowView.currentGiftCount >= giftMaxNum) {//移除操作[self.operationCache removeObjectForKey:giftModel.giftKey];//清空唯一keyself.curentGiftKey = @"";}
  • 让我们看看赋值之后的具体操作,拿到传进来的当前的礼物点击数后累加到总礼物数上,然后赋值.是不是看到熟悉的代码.没看错,延迟隐藏的方法也是在这里控制的.这样就实现了连击的效果.
- (void)setGiftCount:(NSInteger)giftCount {_giftCount = giftCount;self.currentGiftCount += giftCount;self.countLabel.text = [NSString stringWithFormat:@"x %zd",self.currentGiftCount];NSLog(@"累计礼物数 %zd",self.currentGiftCount);if (self.currentGiftCount > 1) {[self p_SetAnimation:self.countLabel];[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hiddenGiftShowView) object:nil];//可以取消成功。[self performSelector:@selector(hiddenGiftShowView) withObject:nil afterDelay:animationTime];}else {[self performSelector:@selector(hiddenGiftShowView) withObject:nil afterDelay:animationTime];}
}
  • 排队累加 - 在拿到当前用户点击的key之后与当前展示礼物的key比较不一样,但是这个点击的key对应的操作是存在的.那么就说明这个礼物正在等待展示,那么我们就要对这个没有展示的礼物进行累加.我称之为排队累加.
            JPGiftOperation *op = [self.operationCache objectForKey:giftModel.giftKey];op.model.defaultCount += giftModel.sendCount;//限制一次礼物的连击最大值if (op.model.defaultCount >= giftMaxNum) {//移除操作[self.operationCache removeObjectForKey:giftModel.giftKey];//清空唯一keyself.curentGiftKey = @"";}
  • 不知道有没有注意到这两个逻辑处理的不一样.没看错,就是这两个属性,一个是赋值,一个累加赋值.defaultCount是我给每一个礼物默认的点击数0.只有点击之后才会进行累加.比如,送了一个累加之后defaultCount就是1,那么在我第一个展示的时候,礼物右边的数字就是defaultCount的数值.只有在连击的时候使用的self.currentGiftCount的数值.
op.giftShowView.giftCount = giftModel.sendCount;
op.model.defaultCount += giftModel.sendCount;
  • 回头看下那么判断逻辑那,在完全的第一次创建礼物展示时使用的也是defaultCount.
  • 最终在show的方法中还是调用了这个方法来展示动画
        self.currentGiftCount = 0;[self setGiftCount:giftModel.defaultCount];
  • 写到这里,让我们看看现在的效果吧.


* 总算实现了.准备交工测试的时候,我们产品又加了一个需求(此处省略点字).让礼物第一次展示的时候放一个gif图.而且同一个礼物在连击的时候只展示一次.呀呀呀呀.
* 这样就以为可以难倒我了么.嘿嘿,还记得前面的一个方法么,现在刚好可以用到了.刚好符合产品的需求,只在第一次展示当前礼物的时候回调.

    if (self.showViewKeyBlock && self.currentGiftCount == 0) {self.showViewKeyBlock(giftModel);}
  • 这样的话就要改变管理类的方法了,因为我们需要一个回调告诉控制器,我的礼物开始展示了,你赶紧给我展示gif.
/**送礼物 @param backView 礼物需要展示的父view
 @param giftModel 礼物的数据
 @param completeBlock 回调*/
- (void)showGiftViewWithBackView:(UIView *)backViewinfo:(JPGiftModel *)giftModelcompleteBlock:(completeBlock)completeBlockcompleteShowGifImageBlock:(completeShowGifImageBlock)completeShowGifImageBlock;
  • 那么在回调的方法中我们就直接在调起这个回调剩下的就让控制器去处理吧.(各位同学可以酌情使用这个功能)
[_giftShowView setShowViewKeyBlock:^(JPGiftModel *giftModel) {_curentGiftKey = giftModel.giftKey;if (weakSelf.completeShowGifImageBlock) {weakSelf.completeShowGifImageBlock(giftModel);}}];
  • 下面看一个效果


* 写到这里,其实这个功能已经实现了产品的所有需求.我们项目中使用的也是只是到这里的功能.
* 但是我自己确在想了,现在主流的不都是支持同时显示两个礼物的信息么,那么该怎么实现呢.
* 思考中…
* 既然一个队列显示一个礼物,那么要显示2个或者更多是不是需要更多的队列去展示呢?那么就试一试吧.
* 两个队列,两个可以展示动画的view,还有key不在是NSString ,变成一个数组,以便放下当前展示的两个礼物的key.

/** 队列 */
@property(nonatomic,strong) NSOperationQueue *giftQueue1;
@property(nonatomic,strong) NSOperationQueue *giftQueue2;
/** showgift */
@property(nonatomic,strong) JPGiftShowView *giftShowView1;
@property(nonatomic,strong) JPGiftShowView *giftShowView2;
/** 操作缓存 */
@property (nonatomic,strong) NSCache *operationCache;
/** 当前礼物的keys */
@property(nonatomic,strong) NSMutableArray *curentGiftKeys;
  • 只需要在创建操作加入队列的时候判断当前哪个队列中的操作数比较少,那么就将新创建的操作加入到这个队列中等待展示.全部流程代码如下.
- (void)showGiftViewWithBackView:(UIView *)backView info:(JPGiftModel *)giftModel completeBlock:(completeBlock)completeBlock completeShowGifImageBlock:(completeShowGifImageBlock)completeShowGifImageBlock {self.completeShowGifImageBlock = completeShowGifImageBlock;if (self.curentGiftKeys.count && [self.curentGiftKeys containsObject:giftModel.giftKey]) {//有当前的礼物信息if ([self.operationCache objectForKey:giftModel.giftKey]) {//当前存在操作JPGiftOperation *op = [self.operationCache objectForKey:giftModel.giftKey];op.giftShowView.giftCount = giftModel.sendCount;//限制一次礼物的连击最大值if (op.giftShowView.currentGiftCount >= giftMaxNum) {//移除操作[self.operationCache removeObjectForKey:giftModel.giftKey];//清空唯一key[self.curentGiftKeys removeObject:giftModel.giftKey];}}else {NSOperationQueue *queue;JPGiftShowView *showView;if (self.giftQueue1.operations.count <= self.giftQueue2.operations.count) {queue = self.giftQueue1;showView = self.giftShowView1;}else {queue = self.giftQueue2;showView = self.giftShowView2;}//当前操作已结束 重新创建JPGiftOperation *operation = [JPGiftOperation addOperationWithView:showView OnView:backView Info:giftModel completeBlock:^(BOOL finished,NSString *giftKey) {if (self.finishedBlock) {self.finishedBlock(finished);}//移除操作[self.operationCache removeObjectForKey:giftKey];//清空唯一key[self.curentGiftKeys removeObject:giftKey];}];operation.model.defaultCount += giftModel.sendCount;//存储操作信息[self.operationCache setObject:operation forKey:giftModel.giftKey];//操作加入队列[queue addOperation:operation];}}else {//没有礼物的信息if ([self.operationCache objectForKey:giftModel.giftKey]) {//当前存在操作JPGiftOperation *op = [self.operationCache objectForKey:giftModel.giftKey];op.model.defaultCount += giftModel.sendCount;//限制一次礼物的连击最大值if (op.model.defaultCount >= giftMaxNum) {//移除操作[self.operationCache removeObjectForKey:giftModel.giftKey];//清空唯一key[self.curentGiftKeys removeObject:giftModel.giftKey];}}else {NSOperationQueue *queue;JPGiftShowView *showView;if (self.giftQueue1.operations.count <= self.giftQueue2.operations.count) {queue = self.giftQueue1;showView = self.giftShowView1;}else {queue = self.giftQueue2;showView = self.giftShowView2;}JPGiftOperation *operation = [JPGiftOperation addOperationWithView:showView OnView:backView Info:giftModel completeBlock:^(BOOL finished,NSString *giftKey) {if (self.finishedBlock) {self.finishedBlock(finished);}//移除操作[self.operationCache removeObjectForKey:giftKey];//清空唯一key[self.curentGiftKeys removeObject:giftKey];}];operation.model.defaultCount += giftModel.sendCount;//存储操作信息[self.operationCache setObject:operation forKey:giftModel.giftKey];//操作加入队列[queue addOperation:operation];}}
  • 效果如下

  • 那么到这里,整个结束了.第一次写这么长的文章,还是技术方面.很多不足之处我自己都能感觉到.很多都描述不出来并且基础有点薄弱.很多地方不能特别肯定只能笨笨的去用代码实验.最终运气比较好,在工期内完成了这个改版.不足之处,请多多指教.
  • 送上GitHub地址
    GitHub

.

从0开始实现一个直播礼物系统相关推荐

  1. 从0开始制作H5直播源码的教程

    本文为转载文章,原作者由于公司需要开发一款直播软件,以前也并没有接触过直播这一方面,所以就来从0开始做一个直播,本着开放的原则,发此博文以供后者参阅,视频流服务使用阿里云,H5直播源码. 视频直播服务 ...

  2. 从0到1入门STM32最小系统板(0)——前言

    从0到1入门STM32最小系统板--前言 这个系列的文章将从0开始制作一个STM32最小系统板,大概分为如下几个部分: 元件简述: 原理图绘制: PCB布局: 打板焊接: 本篇文章主要讲解一下最小系统 ...

  3. 网络直播平台搭建一个直播间的礼物系统

    网络直播平台搭建一个直播间的礼物系统 1. 所有直播间的礼物系统,第一步用户看到的无外乎都是礼物的列表界面 纵观主流直播间的礼物列表应该都是使用UICollectionView实现的,所以我也不例外, ...

  4. 如何自己搭建一个ai画图系统? 从0开始云服务器部署novelai

    如何自己搭建一个ai画图系统? 从0开始云服务器部署novelai ​ 上面两张图都是通过ai生成的,是不是有以假乱真的感觉. 本教程提供的是自己搭建一个可以外网访问的ai系统的方法,需要采购gpu服 ...

  5. 百度直播消息系统的实践和演进

    导读:直播业务的核心功能有两个,一个是实时音视频推拉流,另一个是直播间消息流的收发.本文主要介绍百度直播服务内的消息服务系统的设计实践和演化. 一.背景 直播间内用户聊天互动,形式上是常见的IM消息流 ...

  6. 【腾讯Bugly干货分享】从0到1打造直播 App

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/5811d... 作者:李智文 概要 分享内容: 互联网内容载体变迁历程,文字-- ...

  7. ✈️从0到1打造直播 App(iOS /Android直播流程介绍整理 <mark>)

    概要 分享内容: 互联网内容载体变迁历程,文字--图片/声音--视频--VR/AR----..从直播1.0秀场时代(YY),2.0游戏直播(斗鱼.虎牙.熊猫)到如今全民直播3.0泛生活娱乐时代(映客. ...

  8. PHP+JS写一个博客系统

    文章目录 注册和登录界面的完成 注册界面 登录界面 数据库的创建 导入 连接 使用 数据库的连接 验证码的生成 DOACTION 即登录和注册的验证 发表博客 PHP学习完成,随之的实验是结合数据库, ...

  9. 从0到1打造直播 App

    概要 分享内容: 互联网内容载体变迁历程,文字--图片/声音--视频--VR/AR----..从直播1.0秀场时代(YY),2.0游戏直播(斗鱼.虎牙.熊猫)到如今全民直播3.0泛生活娱乐时代(映客. ...

  10. 【腾讯Bugly干货分享】从0到1打造直播 App 1

    作者:腾讯Bugly 链接:http://zhuanlan.zhihu.com/p/23320475 来源:知乎 著作权归作者所有,转载请联系作者获得授权. 概要 分享内容: 互联网内容载体变迁历程, ...

最新文章

  1. UVa10825 Anagram and Multiplication(dfs)
  2. C++PrimerCH1
  3. LeetCode 375. 猜数字大小 II
  4. Python之%s%d%f使用实例
  5. 频繁自燃 烧伤消费者!充电宝一哥召回部分产品
  6. 20200910:力扣204周周赛题解上(Java/Python/Cpp)
  7. 【免费毕设】成绩查询系统(系统+论文+答辩PPT)
  8. 重新配置Domino服务器
  9. 【渝粤教育】国家开放大学2019年春季 3818-22T燃气工程施工 参考试题
  10. 五个 macOS12 Monterey 常用实用技巧
  11. C语言 谭浩强第五版 课后习题解答
  12. 创新案例分享 | 升级改造干部档案管理系统,精确剖析干部执行力情况
  13. 计算机专业本科毕业论文周进展,周进展记录.docx
  14. WATTMAN瓦特曼完成新一轮战略融资,持续深耕钢铁冶金等领域,推出机器人集群平台化产品...
  15. 红帽linux卸载软件命令,好记性不如烂笔头- linux 下rpm软件的安装和卸载 rpm --force -ivh ......
  16. centos 7(桌面应用)-桌面的应用合集
  17. 乐迪智能陪伴机器人_乐迪智能陪伴机器人app下载|乐迪智能iphone版下载 v2.8.5 - 跑跑车苹果网...
  18. android4.4 fragment,在Activity和多个Fragment之间共享资源
  19. python数组中最大元素_Python获取numpy数组中最大的5个元素(保持原顺序)
  20. 专业程序员开发-老狼孩插件懒人精灵版

热门文章

  1. SPSS应用——时间序列分析
  2. 【2020】微软 MCSA,MCSD,MCSE认证于2021年1月31停用,此后您将无法再获得此认证 - GJYJSJGS - 高级云计算架构师
  3. 安卓手机抓包方法归纳总结
  4. QCA9531模块ART 认证测试指导
  5. openwrt 遇到问题三 高通9531编译过程
  6. Echarts滚动条
  7. CS224N 笔记一
  8. 国际C语言混乱代码大赛 获奖作品
  9. AHP(层次分析法)的全面讲解及python实现
  10. 5G时代|淘宝直播高画质低延时技术探索