类QQ粘性按钮(封装)

那个,先来说说原理吧:

这里原理就是,在界面设置两个控件一个按钮在上面,一个View在下面(同样大小),当我们拖动按钮的时候显示下面的View,view不移动,但是会根据按钮中心点和它的中心点的距离去等比例变化自己的半径,越远半径酒越小,最后就会消失,而我们这里吗最难的就是在变化的过程中去计算并且设置他们两个之间的区域并且填充。这里需要计算六个点的位置(根据勾股定理),然后根据两个控件同一边的位置的两个点去绘制一条曲线。拖动距离到达一定的时候就会使用动画(序列帧)去清楚界面的按钮,随后做了一定的优化,,,好了就这么多,下面来看看具体怎么实现它!

1:创建一个自定义的按钮:这里名为iCocosBadgeView

2:在头文件中创建一个图片数组,这里时为了后面实现拖动后删除动画:

 #import <UIKit/UIKit.h>

 @interface iCocosBadgeView : UIButton<NSCopying>

 @property (nonatomic, strong) NSArray *images;

 @end

3:实现文件中实现相应的功能需求

这里时自定义View的基本常识久不多说:

- (void)awakeFromNib

{
    [self setUp];
}

- (instancetype)initWithFrame:(CGRect)frame

{
    if (self = [super initWithFrame:frame]) {

        [self setUp];
    }
    return self;
}

4:实现按钮的初始化和属性的设置,并且为他添加拖动手势


 // 初始化

 - (void)setUp

 {

     // self.width

     CGFloat w = self.frame.size.width;

     // 设置圆角

     self.layer.cornerRadius = w * 0.5;

     // 设置字体

     self.titleLabel.font = [UIFont systemFontOfSize:];

     // 设置字体颜色

     [self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];

     // 添加手势

     UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];

     [self addGestureRecognizer:pan];

     // 添加小圆,颜色一样,圆角半径,尺寸

     // 如果一个类想使用copy,必须要遵守NSCopying

     UIView *smallCircleView = [self copy];

     // 把小圆添加badgeView的父控件

     [self.superview insertSubview:smallCircleView belowSubview:self];

 }

由于上面直接使用copy复制一份来实现下面的那个View,具体下面那个View我前面已经介绍,所以我们需要让他遵守NSCoping协议,并且实现copyWithZone方法:

  • <NSCopying>
 - (id)copyWithZone:(NSZone *)zone

 {

     UIView *smallCircleView = [[UIView alloc] initWithFrame:self.frame];

     smallCircleView.backgroundColor = self.backgroundColor;

     smallCircleView.layer.cornerRadius = self.layer.cornerRadius;

     _smallCircleView = smallCircleView;

     return _smallCircleView;

 }

5:实现按钮拖动手势方法

在这之前需要定义一个地步View的属性,用来记录用户的一些操作

@property (nonatomic, weak) UIView *smallCircleView;

// 手指拖动的时候调用

 - (void)pan:(UIPanGestureRecognizer *)pan

 {

     // 获取手指的偏移量

     CGPoint transP = [pan translationInView:self];

     // 设置形变

     // 修改形变不会修改center

     CGPoint center = self.center;

     center.x += transP.x;

     center.y += transP.y;

     self.center = center;

 //    self.transform = CGAffineTransformTranslate(self.transform, transP.x, transP.y);

     // 复位

     [pan setTranslation:CGPointZero inView:self];

     // 计算两个圆的圆心距离

     CGFloat d = [self distanceWithSmallCircleView:_smallCircleView bigCircleView:self];

     // 计算小圆的半径

     CGFloat smallRadius = self.bounds.size.width * 0.5 - d / 10.0;

     // 给小圆赋值

     _smallCircleView.bounds = CGRectMake(, , smallRadius * , smallRadius * );

     // 注意小圆半径一定要改

     _smallCircleView.layer.cornerRadius = smallRadius;

     // 设置不规则的矩形路径

     if (_smallCircleView.hidden == NO) {// 小圆显示的时候才需要描述不规则矩形

         self.shapeL.path = [self pathWithSmallCircleView:_smallCircleView bigCircleView:self].CGPath;

     }

     // 拖动的时候判断下圆心距离是否大于50

     ) { // 粘性效果拖没

         // 隐藏小圆

         _smallCircleView.hidden = YES;

         // 隐藏不规则的layer

 //        _shapeL.hidden = YES;

         // 从父层中移除,就有吸附效果

         [self.shapeL removeFromSuperlayer];

     }
     // 手指抬起的业务逻辑

     if (pan.state == UIGestureRecognizerStateEnded) {

         ) {

             // 播放gif动画

             // 创建UIImageView

             UIImageView *imageV = [[UIImageView alloc] initWithFrame:self.bounds];

              NSMutableArray *images = [NSMutableArray array];

             if (_images == nil) {

                 ; i <= ; i++) {

                     NSString *imageName = [NSString stringWithFormat:@"%d",i];

                     UIImage *image = [UIImage imageNamed:imageName];

                     [images addObject:image];

                 }

             }else{

                 images = _images;

             }

             imageV.animationImages = images;

             imageV.animationDuration = ;

             [imageV startAnimating];

             [self addSubview:imageV];

             dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.9 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

                 [self removeFromSuperview];

             });
         }else{ // 两个圆心距离没有超过范围
             // 弹簧效果

             [UIView animateWithDuration: usingSpringWithDamping: options:UIViewAnimationOptionCurveLinear animations:^{

                  // badgeView还原到之前的位置,设置中心点为原来位置

                 self.center = _smallCircleView.center;

             } completion:^(BOOL finished) {
             }];

             // 小圆重新显示

             _smallCircleView.hidden = NO;

             // 不规则的矩形形状也需要干掉

             [self.shapeL removeFromSuperlayer];
         }
     }
 }

6:下面就是本案例中最难的一部分,其实也不难,只不过涉及到了比较麻烦的计算

先来看图:

是不是感觉一片茫然,好吧哪里来根据下面的代码结合上面的图片,相信你会看懂。

提示一下,这里主要是计算ABCDOP四个点的位置(坐标)然后时候画图技术绘制并且填充这个区域,

// 根据两个控件描述不规则的路径

 - (UIBezierPath *)pathWithSmallCircleView:(UIView *)smallCircleView bigCircleView:(UIView *)bigCircleView

 {

     // 小圆,x1,y1,r1

     CGFloat x1 = smallCircleView.center.x;

     CGFloat y1 = smallCircleView.center.y;

     CGFloat r1 = smallCircleView.bounds.size.width * 0.5;

     // 大圆,x2,y2,r2

     CGFloat x2 = bigCircleView.center.x;

     CGFloat y2 = bigCircleView.center.y;

     CGFloat r2 = bigCircleView.bounds.size.width * 0.5;

     // 计算两个圆心距离

     CGFloat d = [self distanceWithSmallCircleView:smallCircleView bigCircleView:bigCircleView];

     )  return nil;

     // cosθ

     CGFloat cosθ = (y2 - y1) / d;

     // sinθ

     CGFloat sinθ = (x2 - x1) / d;

     CGPoint pointA = CGPointMake(x1 - r1 * cosθ, y1 + r1 * sinθ);

     CGPoint pointB = CGPointMake(x1 + r1 * cosθ, y1 - r1 * sinθ);

     CGPoint pointC = CGPointMake(x2 + r2 * cosθ, y2 - r2 * sinθ);

     CGPoint pointD = CGPointMake(x2 - r2 * cosθ, y2 + r2 * sinθ);

     CGPoint pointO = CGPointMake(pointA.x + d * 0.5 * sinθ, pointA.y + d * 0.5 * cosθ);

     CGPoint pointP = CGPointMake(pointB.x + d * 0.5 * sinθ, pointB.y + d * 0.5 * cosθ);

     // 描述路径

     UIBezierPath *path = [UIBezierPath bezierPath];

     // 设置起点

     [path moveToPoint:pointA];

     // AB

     [path addLineToPoint:pointB];

     // BC

     [path addQuadCurveToPoint:pointC controlPoint:pointP];

     // CD

     [path addLineToPoint:pointD];

     // DA

     [path addQuadCurveToPoint:pointA controlPoint:pointO];

     return path;

 }

由于前面设计到了计算两个控件的中心点之间的距离,所以我们将它抽出来,这样一看就懂,而且方便以后使用

// 获取两个控件之间圆心距离

 - (CGFloat)distanceWithSmallCircleView:(UIView *)smallCircleView bigCircleView:(UIView *)bigCircleView

 {

     // 获取x轴偏移量

     CGFloat offsetX = bigCircleView.center.x - smallCircleView.center.x;

     // 获取y轴偏移量

     CGFloat offsetY = bigCircleView.center.y - smallCircleView.center.y;

     // 获取两个圆心的距离

     CGFloat d = sqrtf((offsetX * offsetX + offsetY * offsetY));

     // sqrtf开根

     return d;

 }

由于前面设置到了拖动上面那个按钮需要实现地步View的半径的变化,并且实现两个控件之间填充控一些对应的,所以这里我们使用的是形变图层:

需要先定义一个图层属性

@property (nonatomic, weak) CAShapeLayer *shapeL;

然后懒加载他:

 - (CAShapeLayer *)shapeL

 {

     if (_shapeL == nil) {

         // 创建形状图层

         // 利用形状图层

         CAShapeLayer *shape = [CAShapeLayer layer];

         // 设置填充颜色

         shape.fillColor = [UIColor redColor].CGColor;

         [self.superview.layer insertSublayer:shape atIndex:];

         _shapeL = shape;

     }

     return _shapeL;

 }

注意:由于默认系统会讲控制器设置为自动约束,所以一半我们需要取消他:

self.view.translatesAutoresizingMaskIntoConstraints = NO;

下面说说怎么去使用它吧,

1:在界面拖一个按钮设置对应的frame,然后你只需要将对应按钮的class设置为我们自定义的按钮就可以,就这么多:

2:导入我们的按钮类,然后初始化他,并且设置对应的属性:

#import "iCocosBadgeView.h"

初始化控件:

     iCocosBadgeView *bage = [[iCocosBadgeView alloc] init];

     bage.frame = CGRectMake(, , , );

     bage.backgroundColor = [UIColor redColor];

     [self.view addSubview:bage];

实现效果:

所有源码:

iCocosBadgeView.h文件的声明

 #import <UIKit/UIKit.h>

 @interface iCocosBadgeView : UIButton<NSCopying>

 @property (nonatomic, strong) NSArray *images;

 @end

iCocosBadgeView.m文件的实现

 #import "iCocosBadgeView.h"

 @interface iCocosBadgeView ()

 @property (nonatomic, weak) UIView *smallCircleView;

 @property (nonatomic, weak) CAShapeLayer *shapeL;

 @end

 @implementation iCocosBadgeView

 - (CAShapeLayer *)shapeL

 {

     if (_shapeL == nil) {

         // 创建形状图层

         // 利用形状图层

         CAShapeLayer *shape = [CAShapeLayer layer];

         // 设置填充颜色

         shape.fillColor = [UIColor redColor].CGColor;

         [self.superview.layer insertSublayer:shape atIndex:];

         _shapeL = shape;

     }

     return _shapeL;

 }

 - (void)awakeFromNib

 {

     [self setUp];

 }

 - (instancetype)initWithFrame:(CGRect)frame

 {

     if (self = [super initWithFrame:frame]) {

         [self setUp];

     }

     return self;

 }

 // 初始化

 - (void)setUp

 {

     // self.width

     CGFloat w = self.frame.size.width;

     // 设置圆角

     self.layer.cornerRadius = w * 0.5;

     // 设置字体

     self.titleLabel.font = [UIFont systemFontOfSize:];

     // 设置字体颜色

     [self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];

     // 添加手势

     UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];

     [self addGestureRecognizer:pan];

     // 添加小圆,颜色一样,圆角半径,尺寸

     // 如果一个类想使用copy,必须要遵守NSCopying

     UIView *smallCircleView = [self copy];

     // 把小圆添加badgeView的父控件

     [self.superview insertSubview:smallCircleView belowSubview:self];

 }

 // 只要调用copy就会调用这个方法

 - (id)copyWithZone:(NSZone *)zone

 {

     UIView *smallCircleView = [[UIView alloc] initWithFrame:self.frame];

     smallCircleView.backgroundColor = self.backgroundColor;

     smallCircleView.layer.cornerRadius = self.layer.cornerRadius;

     _smallCircleView = smallCircleView;

     return _smallCircleView;

 }

 // 手指拖动的时候调用

 - (void)pan:(UIPanGestureRecognizer *)pan

 {

     // 获取手指的偏移量

     CGPoint transP = [pan translationInView:self];

     // 设置形变

     // 修改形变不会修改center

     CGPoint center = self.center;

     center.x += transP.x;

     center.y += transP.y;

     self.center = center;

 //    self.transform = CGAffineTransformTranslate(self.transform, transP.x, transP.y);

     // 复位

     [pan setTranslation:CGPointZero inView:self];

     // 计算两个圆的圆心距离

     CGFloat d = [self distanceWithSmallCircleView:_smallCircleView bigCircleView:self];

     // 计算小圆的半径

     CGFloat smallRadius = self.bounds.size.width * 0.5 - d / 10.0;

     // 给小圆赋值

     _smallCircleView.bounds = CGRectMake(, , smallRadius * , smallRadius * );

     // 注意小圆半径一定要改

     _smallCircleView.layer.cornerRadius = smallRadius;

     // 设置不规则的矩形路径

     if (_smallCircleView.hidden == NO) {// 小圆显示的时候才需要描述不规则矩形

         self.shapeL.path = [self pathWithSmallCircleView:_smallCircleView bigCircleView:self].CGPath;

     }

     // 拖动的时候判断下圆心距离是否大于50

     ) { // 粘性效果拖没

         // 隐藏小圆

         _smallCircleView.hidden = YES;

         // 隐藏不规则的layer

 //        _shapeL.hidden = YES;

         // 从父层中移除,就有吸附效果

         [self.shapeL removeFromSuperlayer];

     }

     // 手指抬起的业务逻辑

     if (pan.state == UIGestureRecognizerStateEnded) {

         ) {

             // 播放gif动画

             // 创建UIImageView

             UIImageView *imageV = [[UIImageView alloc] initWithFrame:self.bounds];

              NSMutableArray *images = [NSMutableArray array];

             if (_images == nil) {

                 ; i <= ; i++) {

                     NSString *imageName = [NSString stringWithFormat:@"%d",i];

                     UIImage *image = [UIImage imageNamed:imageName];

                     [images addObject:image];

                 }

             }else{

                 images = _images;

             }

             imageV.animationImages = images;

             imageV.animationDuration = ;

             [imageV startAnimating];

             [self addSubview:imageV];

             dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.9 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

                 [self removeFromSuperview];

             });

         }else{ // 两个圆心距离没有超过范围

             // 弹簧效果

             [UIView animateWithDuration: usingSpringWithDamping: options:UIViewAnimationOptionCurveLinear animations:^{

                  // badgeView还原到之前的位置,设置中心点为原来位置

                 self.center = _smallCircleView.center;

             } completion:^(BOOL finished) {

             }];

             // 小圆重新显示

             _smallCircleView.hidden = NO;

             // 不规则的矩形形状也需要干掉

             [self.shapeL removeFromSuperlayer];

         }

     }

 }

 // 根据两个控件描述不规则的路径

 - (UIBezierPath *)pathWithSmallCircleView:(UIView *)smallCircleView bigCircleView:(UIView *)bigCircleView

 {

     // 小圆,x1,y1,r1

     CGFloat x1 = smallCircleView.center.x;

     CGFloat y1 = smallCircleView.center.y;

     CGFloat r1 = smallCircleView.bounds.size.width * 0.5;

     // 大圆,x2,y2,r2

     CGFloat x2 = bigCircleView.center.x;

     CGFloat y2 = bigCircleView.center.y;

     CGFloat r2 = bigCircleView.bounds.size.width * 0.5;

     // 计算两个圆心距离

     CGFloat d = [self distanceWithSmallCircleView:smallCircleView bigCircleView:bigCircleView];

     )  return nil;

     // cosθ

     CGFloat cosθ = (y2 - y1) / d;

     // sinθ

     CGFloat sinθ = (x2 - x1) / d;

     CGPoint pointA = CGPointMake(x1 - r1 * cosθ, y1 + r1 * sinθ);

     CGPoint pointB = CGPointMake(x1 + r1 * cosθ, y1 - r1 * sinθ);

     CGPoint pointC = CGPointMake(x2 + r2 * cosθ, y2 - r2 * sinθ);

     CGPoint pointD = CGPointMake(x2 - r2 * cosθ, y2 + r2 * sinθ);

     CGPoint pointO = CGPointMake(pointA.x + d * 0.5 * sinθ, pointA.y + d * 0.5 * cosθ);

     CGPoint pointP = CGPointMake(pointB.x + d * 0.5 * sinθ, pointB.y + d * 0.5 * cosθ);

     // 描述路径

     UIBezierPath *path = [UIBezierPath bezierPath];

     // 设置起点

     [path moveToPoint:pointA];

     // AB

     [path addLineToPoint:pointB];

     // BC

     [path addQuadCurveToPoint:pointC controlPoint:pointP];

     // CD

     [path addLineToPoint:pointD];

     // DA

     [path addQuadCurveToPoint:pointA controlPoint:pointO];

     return path;

 }

 // 获取两个控件之间圆心距离

 - (CGFloat)distanceWithSmallCircleView:(UIView *)smallCircleView bigCircleView:(UIView *)bigCircleView

 {

     // 获取x轴偏移量

     CGFloat offsetX = bigCircleView.center.x - smallCircleView.center.x;

     // 获取y轴偏移量

     CGFloat offsetY = bigCircleView.center.y - smallCircleView.center.y;

     // 获取两个圆心的距离

     CGFloat d = sqrtf((offsetX * offsetX + offsetY * offsetY));

     // sqrtf开根

     return d;

 }

 // 目的:取消系统高亮状态做的事情

 - (void)setHighlighted:(BOOL)highlighted

 {

 }

 @end

iOS开发——项目实战OC篇&类QQ黏性按钮(封装)的更多相关文章

  1. iOS开发——UI高级OC篇&自定义控件之调整按钮中子控件(图片和文字)的位置

    自定义控件之调整按钮中子控件(图片和文字)的位置 其实还有一种是在storyBoard中实现的,只需要设置对应空间的左右间距: 这里实现前面两种自定义的方式 一:imageRectForContent ...

  2. iOS开发——UI精选OC篇&UIApplication,UIWindow,UIViewController,UIView(layer)简单介绍

    UIApplication,UIWindow,UIViewController,UIView(layer)简单介绍 一:UIApplication:单例(关于单例后面的文章中会详细介绍,你现在只要知道 ...

  3. ios开发——实用技术篇OC篇&iOS的主要框架

    iOS的主要框架         阅读目录 Foundation框架为所有的应用程序提供基本系统服务 UIKit框架提供创建基于触摸用户界面的类 Core Data框架管着理应用程序数据模型 Core ...

  4. iOS开发——网络实用技术OC篇&网络爬虫-使用青花瓷抓取网络数据

    网络爬虫-使用青花瓷抓取网络数据 由于最近在研究网络爬虫相关技术,刚好看到一篇的的搬了过来! 望谅解..... 写本文的契机主要是前段时间有次用青花瓷抓包有一步忘了,在网上查了半天也没找到写的完整的教 ...

  5. iOS开发——高级技术OC篇&运行时(Runtime)机制

    运行时(Runtime)机制 本文将会以笔者个人的小小研究为例总结一下关于iOS开发中运行时的使用和常用方法的介绍,关于跟多运行时相关技术请查看笔者之前写的运行时高级用法及相关语法或者查看响应官方文档 ...

  6. iOS开发——网络实用技术OC篇&网络爬虫-使用java语言抓取网络数据

    网络爬虫-使用java语言抓取网络数据 前提:熟悉java语法(能看懂就行) 准备阶段:从网页中获取html代码 实战阶段:将对应的html代码使用java语言解析出来,最后保存到plist文件 上一 ...

  7. iOS开发——运行时OC篇&使用运行时获取系统的属性:使用自己的手势修改系统自带的手势

    使用运行时获取系统的属性:使用自己的手势修改系统自带的手势 有的时候我需要实现一个功能,但是没有想到很好的方法或者想到了方法只是那个方法实现起来太麻烦,一或者确实为了装逼,我们就会想到iOS开发中最牛 ...

  8. iOS开发——高级UI—OC篇&退出键盘

    退出键盘 iOS开发中键盘的退出方法用很多中我们应该在合适的地方使用合适的方法才能更好的提高开发的效率和应用的性能 下面给大家介绍几种最常用的键盘退出方法,基本上iOS开发中的键盘退出方法都是这几种中 ...

  9. iOS开发项目实战——Swift实现图片轮播与浏览

    近期開始开发一个新的iOS应用,自己决定使用Swift.进行了几天之后,发现了一个非常严峻的问题.那就是无论是书籍,还是网络资源,关于Swift的实在是太少了,随便一搜全都是OC实现某某某功能.就算是 ...

随机推荐

  1. 整理string类常见方法的使用说明

    整理String类的Length().charAt().getChars().replace().toUpperCase().toLowerCase().trim().toCharArray()使用说 ...

  2. GC算法 垃圾收集器

    GC算法 垃圾收集器 参考:http://www.cnblogs.com/ityouknow/p/5614961.html 概述 垃圾收集 Garbage Collection 通常被称为“GC”,它 ...

  3. .hpp文件

    hpp在C++中的含义 以前在开源代码里面遇到过,今天看boost源码的时候又遇到了,故学习一下. hPP,计算机术语,用C/C++语言编写的头文件,通常用来定义数据类型,声明变量.函数.结构和类.而 ...

  4. Python学习第二天数组

    1:Python定义数组:a=[0,1,2,3,4] ;   打印数组list(a); 这时:a[0]=0, a[1]=1, a[[2]=2...... 1.1:如果想定义一个很长的数组可以用到pyt ...

  5. Windows Azure存储容器私有,公共容器,公共Blob的区别

    当我们在Windows Azure中创建或编辑存储的容器时,需要选择访问类型,本文将描述一下这三个选项的区别. 1. 私有: 默认选项,顾名思义,用户不能通过URL匿名进行访问容器或容器内的任何Blo ...

  6. HDU1227:Fast Food

    题目链接:Fast Food 题意:一条直线上有n个饭店,问建造k个原料厂(仍旧在商店位置)得到的最小距离 分析:见代码 //一条直线上有n个饭店,问建造k个原料厂(仍旧在商店位置)得到的最小距离 / ...

  7. Quora 用了哪些技术(转)

    原文:http://dbanotes.net/arch/quora_tech.html 很多团队都在学习.研究 Quora .前段时间看到这篇 Quora’s Technology Examined  ...

  8. jxse2.6在jdk8下,JxtaMulticastSocket存在的问题

    JxtaMulticastSocket覆写了java.net.MulticastSocket的bind方法: @Override public void bind(SocketAddress addr ...

  9. HDU 5869 Different GCD Subarray Query (GCD种类预处理+树状数组维护)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5869 问你l~r之间的连续序列的gcd种类. 首先固定右端点,预处理gcd不同尽量靠右的位置(此时gc ...

  10. POJ 3659 Cell Phone Network (树dp)

    题目链接:http://poj.org/problem?id=3659 给你一个树形图,一个点可以覆盖他周围连接的点,让你用最少的点覆盖所有的点. dp[i][0]表示用i点来覆盖,dp[i][1]表 ...