iOS开发——项目实战OC篇&类QQ黏性按钮(封装)
类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黏性按钮(封装)的更多相关文章
- iOS开发——UI高级OC篇&自定义控件之调整按钮中子控件(图片和文字)的位置
自定义控件之调整按钮中子控件(图片和文字)的位置 其实还有一种是在storyBoard中实现的,只需要设置对应空间的左右间距: 这里实现前面两种自定义的方式 一:imageRectForContent ...
- iOS开发——UI精选OC篇&UIApplication,UIWindow,UIViewController,UIView(layer)简单介绍
UIApplication,UIWindow,UIViewController,UIView(layer)简单介绍 一:UIApplication:单例(关于单例后面的文章中会详细介绍,你现在只要知道 ...
- ios开发——实用技术篇OC篇&iOS的主要框架
iOS的主要框架 阅读目录 Foundation框架为所有的应用程序提供基本系统服务 UIKit框架提供创建基于触摸用户界面的类 Core Data框架管着理应用程序数据模型 Core ...
- iOS开发——网络实用技术OC篇&网络爬虫-使用青花瓷抓取网络数据
网络爬虫-使用青花瓷抓取网络数据 由于最近在研究网络爬虫相关技术,刚好看到一篇的的搬了过来! 望谅解..... 写本文的契机主要是前段时间有次用青花瓷抓包有一步忘了,在网上查了半天也没找到写的完整的教 ...
- iOS开发——高级技术OC篇&运行时(Runtime)机制
运行时(Runtime)机制 本文将会以笔者个人的小小研究为例总结一下关于iOS开发中运行时的使用和常用方法的介绍,关于跟多运行时相关技术请查看笔者之前写的运行时高级用法及相关语法或者查看响应官方文档 ...
- iOS开发——网络实用技术OC篇&网络爬虫-使用java语言抓取网络数据
网络爬虫-使用java语言抓取网络数据 前提:熟悉java语法(能看懂就行) 准备阶段:从网页中获取html代码 实战阶段:将对应的html代码使用java语言解析出来,最后保存到plist文件 上一 ...
- iOS开发——运行时OC篇&使用运行时获取系统的属性:使用自己的手势修改系统自带的手势
使用运行时获取系统的属性:使用自己的手势修改系统自带的手势 有的时候我需要实现一个功能,但是没有想到很好的方法或者想到了方法只是那个方法实现起来太麻烦,一或者确实为了装逼,我们就会想到iOS开发中最牛 ...
- iOS开发——高级UI—OC篇&退出键盘
退出键盘 iOS开发中键盘的退出方法用很多中我们应该在合适的地方使用合适的方法才能更好的提高开发的效率和应用的性能 下面给大家介绍几种最常用的键盘退出方法,基本上iOS开发中的键盘退出方法都是这几种中 ...
- iOS开发项目实战——Swift实现图片轮播与浏览
近期開始开发一个新的iOS应用,自己决定使用Swift.进行了几天之后,发现了一个非常严峻的问题.那就是无论是书籍,还是网络资源,关于Swift的实在是太少了,随便一搜全都是OC实现某某某功能.就算是 ...
随机推荐
- 在VS2103环境中集成Doxygen工具
自己已将学习了两三次了吧,差不多这次该总结一下: Doxygen是一种开源跨平台的,以类似JavaDoc风格描述的文档系统,完全支持C.C++.Java.Objective-C和IDL语言,部分支持P ...
- ubuntu14.04修复启动项
自从安装了ubuntu14.04系统后(win7+ubuntu双系统),一直使用grub来引导win7和ubuntu,很长一段时间都使用的很好.突然前两天win7进不去了,无奈之下就用pe修复了win ...
- Java基础 —— DOM
DOM:文档对象模型(Document Object Model) 定义: 文档:标记型文档:html,xml 对象:将文档或文档中的标签等内容都封装到对象中 模型:只要是标记型文档都通用 将html ...
- Transact-SQL
Transact-SQL(又称T-SQL),是在Microsoft SQL Server和Sybase SQL Server上的ANSI SQL实现,与Oracle的PL/SQL性质相近(不只是实现A ...
- Jquery 等待ajax返回数据loading控件ShowLoading组件
1.意义 开发项目中,前台的页面要发请求到服务器,服务器响应请求返回数据到前台,这段时间,有可能因为返回的数据量较大导致前台页面出现短暂性的等待,此时如果用户因不知情而乱点击有可能造成逻辑混乱,所以此 ...
- error日志
2016/06/15 微信调核心时通用意外险 2016-06-15 11:44:23,771>>INFO >> com.isoftstone.core.service.comm ...
- python知识点 07-11
python引用变量的顺序: 当前作用域局部变量->外层作用域变量->当前模块中的全局变量->python内置变量 python的 nonlocal关键字用来在函数或其他作用域中使用 ...
- CodeForces 709C Letters Cyclic Shift (水题)
题意:给定一个字符串,让你把它的一个子串字符都减1,使得总字符串字典序最小. 析:由于这个题是必须要有一个字串,所以你就要注意这个只有一个字符a的情况,其他的就从开始减 1,如果碰到a了就不减了,如果 ...
- servlet-3_0-final-spec
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns="http://w ...
- chrome emulator use-agent 设置 chrom模拟手机客户端
谷歌升级以后,发现找不到use-agent设置了 在Element 下点击ESC 出现console,再点击Emulation就出现了