iOS开发——项目实战OC篇&类QQ黏性按钮(封装)

时间:2021-01-04 09:06:08

类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:下面就是本案例中最难的一部分,其实也不难,只不过涉及到了比较麻烦的计算

先来看图:

iOS开发——项目实战OC篇&类QQ黏性按钮(封装)

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

提示一下,这里主要是计算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设置为我们自定义的按钮就可以,就这么多:

iOS开发——项目实战OC篇&类QQ黏性按钮(封装)

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

#import "iCocosBadgeView.h"

初始化控件:

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

     bage.frame = CGRectMake(, , , );

     bage.backgroundColor = [UIColor redColor];

     [self.view addSubview:bage];

实现效果:

iOS开发——项目实战OC篇&类QQ黏性按钮(封装)

所有源码:

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篇&amp&semi;自定义控件之调整按钮中子控件(图片和文字)的位置

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

  2. iOS开发——UI精选OC篇&amp&semi;UIApplication&comma;UIWindow&comma;UIViewController&comma;UIView&lpar;layer&rpar;简单介绍

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. Python-day-21

    1.请求周期 url> 路由 > 函数或类 > 返回字符串或者模板语言? Form表单提交: 提交 -> url > 函数或类中的方法 - .... HttpRespon ...

  2. 转&colon;Java NIO系列教程&lpar;九&rpar; Pipe

    Java NIO 管道是2个线程之间的单向数据连接.Pipe有一个source通道和一个sink通道.数据会被写到sink通道,从source通道读取. 这里是Pipe原理的图示: 创建管道 通过Pi ...

  3. php 多进程workman服务器框架

    今天搜php socket,发现了一个给力的php写socket的框架workman,有机会要用用. 好给力,原来那个小蝌蚪聊天室就是用这个开发的. 仿佛发现了新大陆.

  4. ThinkPHP CURD返回结果参考

    ThinkPHP CURD返回结果参考: 1)查询$table->find() ##返回一条记录,是一个关联数组,是一维数组.$table->select() ##返回第一维是索引数组,第 ...

  5. Visual Studio Code 中编写 C&plus;&plus; 的工作流

    1. 官网下载 Visual Studio Code ,安装.按提示安装 cpp 插件和 cmake 插件. 官网下载 CMake ,安装. 官网下载 Mingw ,安装. 安装 Mingw 时,注意 ...

  6. 洛谷P3205 &lbrack;HNOI2011&rsqb;合唱队 DP

    原题链接点这里 今天在课上听到了这个题,听完后觉得对于一道\(DP\)题目来说,好的状态定义就意味着一切啊! 来看题: 题目描述 为了在即将到来的晚会上有更好的演出效果,作为AAA合唱队负责人的小A需 ...

  7. (1)DBA查询:数据库

    1.数据库状态:[1]sys.databases   [2]exec sp_spaceused 2.数据文件状态:[1]sys.master_files [2]查看ldf与mdf:sp_helpfil ...

  8. Photoshop学习笔记(1)--界面设置

    根据慕课网教学视频整理(抄了一个妹子的评论): http://www.imooc.com/video/9813 ps版本:CS6 步骤: 1.视图->显示->智能参考线,以及视图-> ...

  9. Redis(九):Redis的Java客户端Jedis

    Redis的Java客户端Jedis导航目录: 安装JDK 安装Eclipse Jedis所需要的Jar包 Jedis常用操作 JedisPool 安装JDK tar -zxvf jdk-7u67-l ...

  10. jquery 复选框全选&sol;全不选切换 普通DOM元素点击选中&sol;取消选中切换

    1.要选中的复选框设置统一的name 用prop() prop() 方法设置或返回被选元素的属性和值. $("#selectAll").click(function(){ $(&q ...