iOS UIView动画效果来电提醒直播送礼

前言

  • 简单的来电提醒动画效果,直播送礼显示

. Demo地址

xxxx.gif

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_createButton:^(id<KJQuickCreateHandle>  _Nonnull handle) {
            handle.kj_frame(self.width-18-8, 0, 18, 18).kj_add(self);
            handle.kj_imageName(@"xxx").kj_fontSize(14).kj_textColor(UIColor.blackColor);
        }];
        self.button.centerY = self.height/2;
        self.label = [UILabel kj_createLabel:^(id<KJQuickCreateHandle>  _Nonnull handle) {
            handle.kj_add(self);
            handle.kj_text(name).kj_font([UIFont fontWithName:@"PingFang SC" size:14]);
            handle.kj_textColor([UIColor colorWithRed:51/255.0 green:51/255.0 blue:51/255.0 alpha:1.0]);
        }];
        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;
}
@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];
    }];
}
备注:本文用到的部分函数方法和Demo,均来自三方库KJEmitterView,如有需要的朋友可自行pod 'KJEmitterView'引入即可

来电提醒小控件介绍就到此完毕,后面有相关再补充,写文章不容易,还请点个小星星传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,547评论 4 374
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,787评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,175评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,752评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,169评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,056评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,195评论 2 321
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,980评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,724评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,872评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,344评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,667评论 3 264
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,379评论 3 245
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,202评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,992评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,189评论 2 286
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,987评论 2 279

推荐阅读更多精彩内容