前言
简单的来电提醒动画效果,直播送礼显示 Demo地址
API & Property
/// 最大显示个数,默认3个 @property(nonatomic,assign)NSInteger maxCount; /// 自动消失时间,默认5s @property(nonatomic,assign)CGFloat vanishTime; /// 是否允许重复显示,默认NO @property(nonatomic,assign)BOOL repetition; /// 点击控件是否消失,默认NO @property(nonatomic,assign)BOOL tapVanish; /// 创建单例 + (instancetype)kj_shareInstance; /// 添加来电消息,重复条件默认根据 userid 判断 - (void)kj_addCallNotify:(void(^)(KJCallNotifyInfo *info))block RepetitionCondition:(bool(^_Nullable)(KJCallNotifyInfo *info))condition; /// 点击事件 - (void)kj_tapBlock:(void(^)(KJCallNotifyInfo *info))block;
简单介绍思路
KJCallNotifyInfo数据模型
声明一个接受外界的数据模型,暂时我只需要图片地址,名字,唯一id(需要再添加就完事)
@interface KJCallNotifyInfo : NSObject @property(nonatomic,strong)NSString *imageUrl; @property(nonatomic,strong)NSString *name; @property(nonatomic,strong)NSString *userid; @end
单例模式
由于这玩意需要一直存在并且放在最顶层,所以我采用单例来设计
static KJCallNotifyView *_instance = nil; + (instancetype)kj_shareInstance{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (_instance == nil) { _instance = [[KJCallNotifyView alloc] initWithFrame:CGRectMake(0, 0, kScreenW, kScreenH)]; [kKeyWindow addSubview:_instance]; } }); return _instance; }
当然你也可以不使用该单例,则按下面方式
__block KJCallNotifyView *view = [[KJCallNotifyView alloc]initWithFrame:CGRectMake(0, 64, kScreenW, kScreenH-64)]; [self.view addSubview:view]; view.maxCount = 5; view.vanishTime = 5; [view kj_tapBlock:^(KJCallNotifyInfo * _Nonnull info) { }]; __block NSInteger index = 1000; NSArray *names = @[@"Sone",@"痛苦的信仰",@"X"]; [button kj_addAction:^(UIButton * _Nonnull kButton) { [view kj_addCallNotify:^(KJCallNotifyInfo * _Nonnull info) { info.imageUrl = @"xxsf"; info.userid = [NSString stringWithFormat:@"%ld",index]; info.name = names[1]; } RepetitionCondition:nil]; }];
手势穿透处理
不影响后面的交互效果
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event{ NSInteger count = self.subviews.count; for (int i = 0; i < count; i++){ UIView *childView = self.subviews[count - 1 - I]; CGPoint childPoint = [self convertPoint:point toView:childView]; UIView *view = [childView hitTest:childPoint withEvent:event]; if (view) return view; } return nil; }
KJCallView
来电样式UI处理
@implementation KJCallView - (instancetype)kj_initWithFrame:(CGRect)frame Name:(NSString*)name{ if (self==[super initWithFrame:frame]) { self.centerX = kScreenW/2; self.backgroundColor = UIColor.whiteColor; self.cornerRadius = kAutoH(48)/2; self.shadowColor = [UIColor colorWithRed:0/255.0 green:0/255.0 blue:0/255.0 alpha:0.16]; self.shadowOffset = CGSizeMake(0,3); self.shadowRadius = 6; self.shadowOpacity = 1; CGFloat height = self.height; self.imageView = [[UIImageView alloc]initWithFrame:CGRectMake(5, 5, height-10, height-10)]; self.imageView.cornerRadius = (height-10)/2; [self addSubview:self.imageView]; self.button = [UIButton kj_createButtonWithImageName:@"button_like_norm"]; self.button.frame = CGRectMake(self.width-18-8, 0, 18, 18); self.button.centerY = self.height/2; [self addSubview:self.button]; self.label = [UILabel kj_createLabelWithText:name]; [self addSubview:self.label]; self.label.textColor = [UIColor colorWithRed:51/255.0 green:51/255.0 blue:51/255.0 alpha:1.0]; self.label.font = [UIFont fontWithName:@"PingFang SC" size:14]; CGFloat width = [self.label kj_calculateWidth]; CGFloat maxw = self.button.x - self.imageView.maxX - 10 - 23 - 10; if (width>=maxw) width = maxw; self.label.frame = CGRectMake(self.imageView.maxX+10, 0, width, self.height); self.tvImageView = [[UIImageView alloc]initWithFrame:CGRectMake(self.label.maxX+2.5, 0, 23, 22)]; self.tvImageView.image = kGetImage(@"wode_nor"); self.tvImageView.centerY = self.height/2; [self addSubview:self.tvImageView]; } return self; } - (void)kj_invalidateTimer{ [_timer invalidate]; _timer = nil; } @end
点击事件
- (void)kj_tapBlock:(void(^)(KJCallNotifyInfo *info))block{ self.tapblock = block; }
点击回调,传递出来处理点击的相关逻辑
添加来电消息
介绍主要方法和核心点,
displayCount
:当前显示的UI个数属性
temps
:存储模型数据
viewTemps
:存储来电显示UI数据
kj_addCallNotify:RepetitionCondition:
:添加来电消息
@synchronized (@(self.displayCount))
:互斥锁,保证正常运行
kj_viewIndex:Info:
:创建来电UI
kj_displayEnd:
:显示结束
kj_autoVanish:
:自动消失动画
kj_vanish:
:点叉消失动画
kj_changeIndex:Y:
:递归修改来电坐标
一、kj_addCallNotify:RepetitionCondition:
添加来电消息,self.repetition
控制是否重复显示,内部条件根据userid来判断是否重复
- (void)kj_addCallNotify:(void(^)(KJCallNotifyInfo *))block RepetitionCondition:(bool(^_Nullable)(KJCallNotifyInfo *))condition{ KJCallNotifyInfo *info = [KJCallNotifyInfo new]; if (block) block(info); if (self.repetition == NO) { __block bool skip = false; if (condition) { for (KJCallNotifyInfo *_info in self.temps) { skip = condition(_info); if (skip) break; } }else{ [self.temps enumerateObjectsUsingBlock:^(KJCallNotifyInfo * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([info.userid isEqualToString:obj.userid]) { skip = true;*stop = YES; } }]; } if (skip) return; [self.temps addObject:info]; } @synchronized (@(self.displayCount)) { self.displayCount++; KJCallView *view = [self kj_viewIndex:self.displayCount Info:info]; [self.viewTemps addObject:view]; [self addSubview:view]; if (self.displayCount > self.maxCount) { KJCallView *fristView = self.viewTemps.firstObject; [self kj_autoVanish:fristView]; [self kj_displayEnd:fristView]; } } }
二、kj_viewIndex:Info:
创建UI控件,添加计时器view.timer
来管理自动消失
- (KJCallView*)kj_viewIndex:(NSInteger)index Info:(KJCallNotifyInfo*)info{ CGFloat y = kAutoH(17) + (index-1) * kAutoH(58) + kSTATUSBAR_HEIGHT - 20; __block KJCallView *view = [[KJCallView alloc]kj_initWithFrame:CGRectMake(0, y, kAutoW(170), kAutoH(48)) Name:info.name]; view.tag = 520 + index - 1; view.info = info; view.imageView.image = [UIImage imageNamed:info.imageUrl]; _weakself; void (^kRemove)(bool tapX) = ^(bool tapX){ if (tapX) { [weakself kj_vanish:view]; }else{ [weakself kj_autoVanish:view]; } [weakself kj_displayEnd:view]; }; [view kj_AddTapGestureRecognizerBlock:^(UIView * _Nonnull __view, UIGestureRecognizer * _Nonnull gesture) { if (weakself.tapblock) { weakself.tapblock(view.info); if (weakself.tapVanish) { [weakself kj_vanish:view]; [weakself kj_displayEnd:view]; } } }]; [view.button kj_addAction:^(UIButton * _Nonnull kButton) { kRemove(true); }]; view.timer = [NSTimer kj_scheduledNoImmediateTimerWithTimeInterval:self.vanishTime Block:^(NSTimer * _Nonnull timer) { kRemove(false); }]; [[NSRunLoop mainRunLoop] addTimer:view.timer forMode:NSRunLoopCommonModes]; [view.timer fire]; return view; }
三、kj_displayEnd:
显示结束,清理数据,关闭计时器,显示个数处理
- (void)kj_displayEnd:(KJCallView*)view{ [view kj_invalidateTimer]; [self.viewTemps removeObject:view]; if (self.repetition == NO) { [self.temps removeObject:view.info]; } @synchronized (@(self.displayCount)) { self.displayCount--; } }
四、kj_autoVanish:
自动消失处理
- (void)kj_autoVanish:(KJCallView*)view{ [UIView animateWithDuration:.5 animations:^{ [self kj_changeIndex:0 Y:-kAutoH(48)]; } completion:^(BOOL finished) { [view removeFromSuperview]; }]; }
五、kj_vanish:
点叉消失处理,当前点击控件消失,其余依次补位
- (void)kj_vanish:(KJCallView*)view{ [UIView animateWithDuration:.5 animations:^{ view.hidden = 0; if (view.tag == 520) { [self kj_changeIndex:0 Y:-kAutoH(48)]; }else{ [self kj_changeIndex:view.tag-520 Y:view.y]; } } completion:^(BOOL finished) { [view removeFromSuperview]; }]; }
六、kj_changeIndex:Y:
递归来处理补位
- (void)kj_changeIndex:(NSInteger)index Y:(CGFloat)y{ KJCallView *view = self.viewTemps[index]; view.tag = 520 + index - 1; CGFloat xy = view.y;view.y = y; if (index+1<self.viewTemps.count) [self kj_changeIndex:index+1 Y:xy]; }
使用示例
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. UIButton *button = [UIButton kj_createButtonWithFontSize:15 Title:@"测试来电" TextColor:UIColor.orangeColor]; button.frame = CGRectMake(0, 0, 100, 50); button.centerX = kScreenW/2; button.centerY = kScreenH - 100; button.borderWidth = 1; button.borderColor = UIColor.orangeColor; [self.view addSubview:button]; __block NSInteger index = 520; NSArray *names = @[@"Sone",@"痛苦的信仰",@"X",@"yang"]; [button kj_addAction:^(UIButton * _Nonnull kButton) { [[KJCallNotifyView kj_shareInstance] kj_addCallNotify:^(KJCallNotifyInfo * _Nonnull info) { info.imageUrl = @"xxsf"; info.userid = [NSString stringWithFormat:@"%ld",index++]; info.name = names[index%4]; } RepetitionCondition:^bool(KJCallNotifyInfo * _Nonnull info) { if ([info.name isEqualToString:names[index%4]]) { return true; } return false; }]; }]; [KJCallNotifyView kj_shareInstance].maxCount = 5; [KJCallNotifyView kj_shareInstance].vanishTime = 7; [KJCallNotifyView kj_shareInstance].repetition = YES; [[KJCallNotifyView kj_shareInstance] kj_tapBlock:^(KJCallNotifyInfo * _Nonnull info) { NSLog(@"-----%@",info); [self.navigationController popViewControllerAnimated:YES]; }]; }