一个类似于QQ语音聊天时的拖拽移动悬浮小球

闲来无事,分享一个最近在某个地方借鉴的一个demo(原谅我真的忘了在哪里看到的了,不然也就贴地址了)这个demo的逻辑思路并不是很难,推敲一下,很快就能理解,只是觉得这样的一个组合控件用起来蛮能增色自己的APP的,所以也就记下了。
先给你们看一下效果图。

9EE94CC0-EA84-44BD-8840-E6B3252FA4EE.png

这里的悬浮小球其实是一个组合控件,可以在上面加上其他效果。之后我会上传demo。如果要做成QQ语音的那种,可以把图片移除,换上一个label,在label上加上时间久可以了,用的时候,可以直接把这个类拖进工程,直接加到想要添加的仕途上就可以啦。

这个demo我也是学习过程中做了很多注释,基本上都应该能看懂,还是一样不多解释,看注释看代码。_
这是还是说一下具体的实现功能吧:
第一个:是营造一个呼吸的效果,这个呼吸效果实际上只是一个假象,UI控件哪有什么呼吸的嘛,都是自己YY的。这个呼吸效果实际上是通过动画效果改变Alpha,不断循环,达到一致循环改变Alpha,感觉就像是在呼吸一样。

第二个:是图片效果,这里是采用了UIImageRenderingModeAlwaysTemplate,不懂得同学可以查一下,不过我demo里面也有部分解释。这里主要是忽略的图片的颜色,设置成自己想要的颜色(这里的图片原本是黑色的,我通过这个枚举,让他的颜色变成了白色)

第三个:是移动,这里是通过-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event , -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event以及 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 这三个方法来实现移动的,大致就是触摸时取出触摸点坐标,然后作为悬浮小球的center,实现了小球的移动。

//将触摸点赋值给touchView的中心点 也就是根据触摸的位置实时修改view的位置
    
    self.center = [startTouch locationInView:self.superview];

还有一个注意的是在开始移动之前一定要把之前的效果给移除掉,不然会很奇葩的,不信你也可以试一试。

 // 移除之前的所有行为
    // 如果不移除,之前移动所触发的物理吸附事件就会一直执行
    [self.animator removeAllBehaviors];

第四:就是计算计算距离最近的边缘 吸附到边缘停靠,具体的计算就不说了,因为太繁琐了,没有想着优化,看看啥时候闲下来再来看看能不能优化吧。主要就是考虑的情况稍微多了点,但是逻辑上还是很清晰的。这里有一个吸附的物理行为,其实也就是一个动画效果,直接加上就好了,也不是很复杂。

好的,具体的都解释完了。上代码!!!,突然发现并不能传文件,好吧,赋值过来看看吧

这是.h里面的代码

这里只是给外界留了个借口,这些属性也可以.m里面,我只是在外界用到了这个而已。


#import <UIKit/UIKit.h>

typedef void (^DownLoadBlock) ();
@interface xuanfuwu : UIView



@property (nonatomic ,assign) CGPoint startPoint;//触摸起始点

@property (nonatomic ,assign) CGPoint endPoint;//触摸结束点

@property (nonatomic ,copy) DownLoadBlock downLoadBlock;
@end

这是.m里面的代码


//主题颜色

#import "AppDelegate.h"
#import "xuanfuwu.h"




#define MAINCOLOER [UIColor colorWithRed:105/255.0 green:149/255.0 blue:246/255.0 alpha:1]


#define kDownLoadWidth 60

#define kOffSet kDownLoadWidth / 2


@interface xuanfuwu ()<UIDynamicAnimatorDelegate>

@property (nonatomic , retain ) UIView *backgroundView;//背景视图

@property (nonatomic , retain ) UIImageView *imageView;//图片视图

@property (nonatomic , retain ) UIDynamicAnimator *animator;//物理仿真动画
@end

@implementation xuanfuwu

//初始化

- (instancetype)initWithFrame:(CGRect)frame{
    
    // 设置 xuanfuwu 这个视图的大小 宽高都是60
    frame.size.width = kDownLoadWidth;
    
    frame.size.height = kDownLoadWidth;
    
    
    if (self = [super initWithFrame:frame]) {
        
        
        //初始化背景视图
        
        _backgroundView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame))];
        
        // 让初始化背景变成圆
        _backgroundView.layer.cornerRadius = _backgroundView.frame.size.width / 2;
        
//        clipsToBounds
//        是指视图上的子视图,如果超出父视图的部分就截取掉,
//        masksToBounds
//        却是指视图的图层上的子图层,如果超出父图层的部分就截取掉
        _backgroundView.clipsToBounds = YES;
        
        // 设置颜色和透明度
        _backgroundView.backgroundColor = [MAINCOLOER colorWithAlphaComponent:0.7];
        
        _backgroundView.userInteractionEnabled = NO;
        
        [self addSubview:_backgroundView];
        
        //初始化图片背景视图
        // 比背景视图稍微小一点,显示出呼吸的效果
        
        UIView * imageBackgroundView = [[UIView alloc]initWithFrame:CGRectMake(5, 5, CGRectGetWidth(self.frame) - 10, CGRectGetHeight(self.frame) - 10)];
        // 变圆
        imageBackgroundView.layer.cornerRadius = imageBackgroundView.frame.size.width / 2;
        
        imageBackgroundView.clipsToBounds = YES;
        
        imageBackgroundView.backgroundColor = [MAINCOLOER colorWithAlphaComponent:0.8f];
        
        imageBackgroundView.userInteractionEnabled = NO;
        
        [self addSubview:imageBackgroundView];
        
       
        
        //初始化图片
        
        _imageView = [[UIImageView alloc]initWithImage:[[UIImage imageNamed:@"1.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
        
        
        // UIImageRenderingModeAutomatic  // 根据图片的使用环境和所处的绘图上下文自动调整渲染模式。
        // UIImageRenderingModeAlwaysOriginal   // 始终绘制图片原始状态,不使用Tint Color。
        // UIImageRenderingModeAlwaysTemplate   // 始终根据Tint Color绘制图片,忽略图片的颜色信息。
        
        
        _imageView.tintColor = [UIColor whiteColor];
        
        _imageView.frame = CGRectMake(0, 0, 30, 30);
        
        _imageView.center = CGPointMake(kDownLoadWidth / 2 , kDownLoadWidth / 2);
        
        [self addSubview:_imageView];
        
       
        
        //将正方形的view变成圆形
        
        self.layer.cornerRadius = kDownLoadWidth / 2;
        
        
        //开启呼吸动画
        
        [self HighlightAnimation];
        
    }
    
    return self;
    
}


// 触摸事件的触摸点
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    
    //得到触摸点
    
    UITouch *startTouch = [touches anyObject];
    
    //返回触摸点坐标
    
    self.startPoint = [startTouch locationInView:self.superview];
    
    // 移除之前的所有行为
    // 如果不移除,之前移动所触发的物理吸附事件就会一直执行
    [self.animator removeAllBehaviors];
    
    
}

//触摸移动
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
    
    //得到触摸点
    // 这里只有一个手指,移动的坐标只有一个
    UITouch *startTouch = [touches anyObject];
    
    //将触摸点赋值给touchView的中心点 也就是根据触摸的位置实时修改view的位置
    
    self.center = [startTouch locationInView:self.superview];
    
}

//结束触摸

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    
    //得到触摸结束点
    
    UITouch *endTouch = [touches anyObject];
    
    //返回触摸结束点
    // 这个方法是指:触摸的点时在哪个视图,xuanfuwu 是加在viewController上的,所以用superView
    self.endPoint = [endTouch locationInView:self.superview];
    
    //判断是否移动了视图 (误差范围5)
    
    CGFloat errorRange = 5;
    
    if (( self.endPoint.x - self.startPoint.x >= -errorRange && self.endPoint.x - self.startPoint.x <= errorRange ) && ( self.endPoint.y - self.startPoint.y >= -errorRange && self.endPoint.y - self.startPoint.y <= errorRange )) {
        // 如果移动范围不超过 5 ,就不进行物理事件的响应
        

        
    } else {
        
        //移动
        
        self.center = self.endPoint;
        
        //计算距离最近的边缘 吸附到边缘停靠
        
        CGFloat superwidth = self.superview.bounds.size.width;
        
        CGFloat superheight = self.superview.bounds.size.height;
        
        CGFloat endX = self.endPoint.x;
        
        CGFloat endY = self.endPoint.y;
        
        CGFloat topRange = endY;//上距离
        
        CGFloat bottomRange = superheight - endY;//下距离
        
        CGFloat leftRange = endX;//左距离
        
        CGFloat rightRange = superwidth - endX;//右距离
        
        
        //比较上下左右距离 取出最小值
        
        CGFloat minRangeTB = topRange > bottomRange ? bottomRange : topRange;//获取上下最小距离
        
        CGFloat minRangeLR = leftRange > rightRange ? rightRange : leftRange;//获取左右最小距离
        
        CGFloat minRange = minRangeTB > minRangeLR ? minRangeLR : minRangeTB;//获取最小距离
        
        
        //判断最小距离属于上下左右哪个方向 并设置该方向边缘的point属性
        
        CGPoint minPoint;
        
        if (minRange == topRange) {
            
            //上
            
            endX = endX - kOffSet < 0 ? kOffSet : endX;
            
            endX = endX + kOffSet > superwidth ? superwidth - kOffSet : endX;
            
            minPoint = CGPointMake(endX , 0 + kOffSet);
            
        } else if(minRange == bottomRange){
            
            //下
            
            endX = endX - kOffSet < 0 ? kOffSet : endX;
            
            endX = endX + kOffSet > superwidth ? superwidth - kOffSet : endX;
            
            minPoint = CGPointMake(endX , superheight - kOffSet);
            
        } else if(minRange == leftRange){
            
            //左
            
            endY = endY - kOffSet < 0 ? kOffSet : endY;
            
            endY = endY + kOffSet > superheight ? superheight - kOffSet : endY;
            
            minPoint = CGPointMake(0 + kOffSet , endY);
            
        } else if(minRange == rightRange){
            
            //右
            
            endY = endY - kOffSet < 0 ? kOffSet : endY;
            
            endY = endY + kOffSet > superheight ? superheight - kOffSet : endY;
            
            minPoint = CGPointMake(superwidth - kOffSet , endY);
            
        }
        
        
        //添加吸附物理行为
        
        UIAttachmentBehavior *attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:self attachedToAnchor:minPoint];
        
        // 吸附行为中的两个吸附点之间的距离,通常用这个属性来调整吸附的长度,可以创建吸附行为之后调用。系统基于你创建吸附行为的方法来自动初始化这个长度
        [attachmentBehavior setLength:0];
        
        // 阻尼,相当于回弹过程中额阻力效果
        [attachmentBehavior setDamping:0.1];
        
        // 回弹的频率
        [attachmentBehavior setFrequency:3];
        
        [self.animator addBehavior:attachmentBehavior];
        
        
    }
    
    
}

#pragma mark ---UIDynamicAnimatorDelegate

- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator{
    
    
    
}



#pragma mark ---LazyLoading

- (UIDynamicAnimator *)animator
{
    
    if (!_animator) {
        
        // 创建物理仿真器(ReferenceView : 仿真范围)
        
        _animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.superview];
        
        //设置代理
        
        _animator.delegate = self;
        
    }
    
    return _animator;
    
}



#pragma mark ---BreathingAnimation 呼吸动画

// 实质就是一个循环动画,透明度来回改变
- (void)HighlightAnimation{
    
    // 修改block块里面的值
    __block typeof(self) Self = self;
    
    [UIView animateWithDuration:1.5f animations:^{
        
        Self.backgroundView.backgroundColor = [Self.backgroundView.backgroundColor colorWithAlphaComponent:0.1f];
        
    } completion:^(BOOL finished) {
        
        [Self DarkAnimation];
        
    }];
    
}

- (void)DarkAnimation{
    
    __block typeof(self) Self = self;
    
    [UIView animateWithDuration:1.5f animations:^{
        
        Self.backgroundView.backgroundColor = [Self.backgroundView.backgroundColor colorWithAlphaComponent:0.6f];
        
    } completion:^(BOOL finished) {
        
        [Self HighlightAnimation];
        
    }];
    
}



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

推荐阅读更多精彩内容