QQ粘性气泡

QQ20170717-000731-HD.gif
  • 分析图:


    QQ20170717-000455@2x.png
  • UnReadBubbleBtn.h
#import <UIKit/UIKit.h>

@interface UnReadBubbleBtn : UIButton

@end
  • UnReadBubbleBtn.m
#import "UnReadBubbleBtn.h"
#define smallViewWH (self.bounds.size.width-1)

@interface UnReadBubbleBtn ()

@property(nonatomic, weak) UIView * smallV;
@property(nonatomic, weak) CAShapeLayer * shapeL;

@end


@implementation UnReadBubbleBtn

-(UIView *)smallV{
    if (_smallV == nil) {
        //添加小圆
        UIView *smallV = [[UIView alloc] initWithFrame:CGRectMake(0, 0, smallViewWH, smallViewWH)];
        smallV.center = self.center;
        smallV.backgroundColor = self.backgroundColor;
        smallV.layer.cornerRadius = smallV.bounds.size.width*0.5;
        [self.superview insertSubview:smallV belowSubview:self];
        _smallV = smallV;
        
    }
    return _smallV;
}

-(CAShapeLayer *)shapeL{
    if (_shapeL == nil) {
        CAShapeLayer * shapeL = [[CAShapeLayer alloc] init];
        shapeL.fillColor = self.backgroundColor.CGColor;
        [self.superview.layer insertSublayer:shapeL atIndex:0]; //superview.layer的子layer中的最底层
        self.shapeL = shapeL;
    }
    return _shapeL;
}


-(void)layoutSubviews{
    [super layoutSubviews];
    [self setUp]; //初始化
}

-(void)awakeFromNib{
    [self setUp]; //初始化
}

//初始化
-(void)setUp{
    //设置圆角
    self.layer.cornerRadius = self.bounds.size.width*0.5;
    self.layer.masksToBounds = YES;
    self.clipsToBounds = NO;
    
    //添加手势
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self addGestureRecognizer:pan];
}



CGFloat dd = 0;
-(void)pan:(UIPanGestureRecognizer *)pan{
    //由于计算中用到两圆圆心的值,所以将普通移位换成移动圆心(translate操作只会改变原来的frame,而不会改变圆心的值)
    CGPoint tran = [pan translationInView:self];
    CGPoint center = self.center;
    center.x += tran.x;
    center.y += tran.y;
    self.center = center;
    [pan setTranslation:CGPointZero inView:self];
    
    
    //计算大圆与小圆的距离
    CGFloat r = self.smallV.bounds.size.width*0.5;
    //    CGFloat R = self.bounds.size.width*0.5;
    CGFloat x1 = self.smallV.center.x;
    CGFloat y1 = self.smallV.center.y;
    CGFloat x2 = self.center.x;
    CGFloat y2 = self.center.y;
    CGFloat d = sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
    NSLog(@"%f", d);
    
    
    //缩小小圆半径
    CGFloat n = 16.0; //数字越大,小圆半径减小越慢
    if (d > dd) {
        r = r - sqrt(tran.x*tran.x+tran.y*tran.y)/n;
    }else{
        r = r + sqrt(tran.x*tran.x+tran.y*tran.y)/n;
    }
    self.smallV.bounds = CGRectMake(0, 0, r*2, r*2);
    self.smallV.layer.cornerRadius = r;
    dd = d;
    
    //描述路径
    CGFloat cosθ = (y2-y1)/d;
    CGFloat sinθ = (x2-x1)/d;
    
    CGPoint A = CGPointMake(x1-r*cosθ, y1+r*sinθ);
    CGPoint B = CGPointMake(x1+r*cosθ, y1-r*sinθ);
    //    CGPoint C = CGPointMake(x2+R*cosθ, y2-R*sinθ);
    //    CGPoint D = CGPointMake(x2-R*cosθ, y2+R*sinθ);
    CGPoint C1 = CGPointMake(x2+(smallViewWH)*0.5*cosθ, y2-(smallViewWH)*0.5*sinθ);
    CGPoint D1 = CGPointMake(x2-(smallViewWH)*0.5*cosθ, y2+(smallViewWH)*0.5*sinθ);
    
    //CGPoint O = CGPointMake((self.center.x+self.smallV.center.x)*0.5, (self.center.y+self.smallV.center.y)*0.5);
    CGPoint P1 = CGPointMake((B.x+C1.x)*0.5, (B.y+C1.y)*0.5);
    CGPoint Q1 = CGPointMake((A.x+D1.x)*0.5, (A.y+D1.y)*0.5);
    
    CGFloat k = (A.y-B.y)/(A.x-B.x);
    CGFloat b = P1.y-k*P1.x;
    
    
    CGFloat m = d/51.0; //分母越大,腰减小越慢
    if (m < 1) {
        m = 1;
    }
    CGFloat x = 2*r*cosθ;
    CGFloat Px = Q1.x+x/m;
    CGFloat Qx = P1.x-x/m;
    
    CGPoint P = CGPointMake(Px, k*Px+b);
    CGPoint Q = CGPointMake(Qx, k*Qx+b);
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:A];
    [path addLineToPoint:B];
    [path addQuadCurveToPoint:C1 controlPoint:P];
    [path addLineToPoint:D1];
    [path addQuadCurveToPoint:A controlPoint:Q];
    
    //描绘形状
    if (self.smallV.hidden == NO) {
        self.shapeL.path = path.CGPath;
    }
    
    
    //拉到一定长度,小圆与梯形都消失
    if (r<=3) {
        [self.shapeL removeFromSuperlayer];
        self.smallV.hidden = YES;
    }
    
    //放手
    if (pan.state == UIGestureRecognizerStateEnded){
        if (self.smallV.hidden) { //小圆已经消失
            if (d<=dd && d<=15) { //吸回原位
                [self.shapeL removeFromSuperlayer];
                self.center = self.smallV.center;
                self.smallV.hidden = NO;
                self.smallV.bounds = CGRectMake(0, 0, smallViewWH, smallViewWH);
                self.smallV.layer.cornerRadius = self.smallV.bounds.size.width*0.5;
            }else{ //爆炸
                self.backgroundColor = [UIColor clearColor];
                //大圆消失,播放爆炸动画
                CGFloat bombW = 30;
                CGFloat btnW = self.bounds.size.width;
                UIImageView *imageV = [[UIImageView alloc] initWithFrame:CGRectMake(-(bombW-btnW)*0.5, -(bombW-btnW)*0.5, bombW, bombW)];
                NSMutableArray *arr = [NSMutableArray array];
                for (int i=1; i<=5; i++) {
                    UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:@"unreadBomb_%i",i]];
                    [arr addObject:img];
                }
                imageV.animationImages = arr;
                imageV.animationDuration = 0.5;
                imageV.animationRepeatCount = 1;
                [imageV startAnimating];
                [self addSubview:imageV];
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    [self removeFromSuperview];
                });
            }
        }else{
            //删除路径,大圆弹回原位,小圆半径恢复原状
            [self.shapeL removeFromSuperlayer];
            [UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.2 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
                self.center = self.smallV.center;
            } completion:^(BOOL finished) {
                self.smallV.bounds = CGRectMake(0, 0, smallViewWH, smallViewWH);
                self.smallV.layer.cornerRadius = self.smallV.bounds.size.width*0.5;
            }];
        }
    }
}


-(void)setHighlighted:(BOOL)highlighted{
}

@end

  • ViewController.m(用代码加载气泡)
#import "ViewController.h"
#import "UnReadBubbleBtn.h"

@interface ViewController ()

@end

@implementation ViewController

-(void)viewDidLoad {
    [super viewDidLoad];
    
    UnReadBubbleBtn *btn = [[UnReadBubbleBtn alloc] initWithFrame:CGRectMake(100, 100, 16, 16)];
    [btn setTitle:@"1" forState:UIControlStateNormal];
    btn.backgroundColor = [UIColor redColor];
    [btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [btn setFont:[UIFont systemFontOfSize:12]];
    [self.view addSubview:btn];
}

@end

  • 如果用 storyBoard 加载气泡按钮,记得将 AutoLayout 的勾去掉:
    Snip20170713_2.png
  • 或者设置 translatesAutoresizingMaskIntoConstraints = YES;
#import "ViewController.h"
#import "NoReadBubbleBtn.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet NoReadBubbleBtn *unReadBtn;
@end

@implementation ViewController

-(void)viewDidLoad {
     [super viewDidLoad];

     self.unReadBtn.translatesAutoresizingMaskIntoConstraints = YES;
}

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

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,967评论 3 119
  • 人生最厉害的情操 人有多种情怀与道德情操。或诚实、或正直、或忠义。但人生最厉害的道德情操是什么呢?那就是-自律。 ...
    岳麓山桦阅读 236评论 0 2
  • 小时候你是墙 威严耸立保护里头稚嫩的我 度过一个安然舒适的童年 些许年后 你是我的指路人 指明我前进的方向 使我没...
    4c385424e0da阅读 233评论 0 2
  • 001 警惕温水煮青蛙的危险 对于可能会突发的危险,很多人保持很强的警惕性。但是对于温水煮青蛙的危险却不易察觉。呆...
    苏菲亲子阅读 141评论 1 4
  • 六月绵雨飞 夜有猫叫声 无风走飞檐 头上长蘑菇
    咕咕羊羊阅读 141评论 0 0