一個類似于QQ語音聊天時的拖拽移動懸浮小球

閑來無事,分享一個最近在某個地方借鑒的一個demo(原諒我真的忘了在哪里看到的了洁奈,不然也就貼地址了)這個demo的邏輯思路并不是很難厢钧,推敲一下滥玷,很快就能理解妇蛀,只是覺得這樣的一個組合控件用起來蠻能增色自己的APP的,所以也就記下了。
先給你們看一下效果圖。

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

這里的懸浮小球其實是一個組合控件魄缚,可以在上面加上其他效果。之后我會上傳demo。如果要做成QQ語音的那種冶匹,可以把圖片移除习劫,換上一個label,在label上加上時間久可以了嚼隘,用的時候诽里,可以直接把這個類拖進工程,直接加到想要添加的仕途上就可以啦飞蛹。

這個demo我也是學習過程中做了很多注釋谤狡,基本上都應該能看懂,還是一樣不多解釋桩皿,看注釋看代碼豌汇。_
這是還是說一下具體的實現(xiàn)功能吧:
第一個:是營造一個呼吸的效果幢炸,這個呼吸效果實際上只是一個假象泄隔,UI控件哪有什么呼吸的嘛,都是自己YY的宛徊。這個呼吸效果實際上是通過動畫效果改變Alpha佛嬉,不斷循環(huán),達到一致循環(huán)改變Alpha闸天,感覺就像是在呼吸一樣暖呕。

第二個:是圖片效果,這里是采用了UIImageRenderingModeAlwaysTemplate苞氮,不懂得同學可以查一下湾揽,不過我demo里面也有部分解釋。這里主要是忽略的圖片的顏色笼吟,設置成自己想要的顏色(這里的圖片原本是黑色的库物,我通過這個枚舉,讓他的顏色變成了白色)

第三個:是移動贷帮,這里是通過-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 戚揭, -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event以及 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 這三個方法來實現(xiàn)移動的,大致就是觸摸時取出觸摸點坐標撵枢,然后作為懸浮小球的center民晒,實現(xiàn)了小球的移動。

//將觸摸點賦值給touchView的中心點 也就是根據(jù)觸摸的位置實時修改view的位置
    
    self.center = [startTouch locationInView:self.superview];

還有一個注意的是在開始移動之前一定要把之前的效果給移除掉锄禽,不然會很奇葩的潜必,不信你也可以試一試。

 // 移除之前的所有行為
    // 如果不移除沃但,之前移動所觸發(fā)的物理吸附事件就會一直執(zhí)行
    [self.animator removeAllBehaviors];

第四:就是計算計算距離最近的邊緣 吸附到邊緣凸伪悖靠,具體的計算就不說了绽慈,因為太繁瑣了恨旱,沒有想著優(yōu)化辈毯,看看啥時候閑下來再來看看能不能優(yōu)化吧。主要就是考慮的情況稍微多了點搜贤,但是邏輯上還是很清晰的谆沃。這里有一個吸附的物理行為,其實也就是一個動畫效果仪芒,直接加上就好了唁影,也不是很復雜。

好的掂名,具體的都解釋完了据沈。上代碼!=让铩锌介!,突然發(fā)現(xiàn)并不能傳文件猾警,好吧孔祸,賦值過來看看吧

這是.h里面的代碼

這里只是給外界留了個借口,這些屬性也可以.m里面发皿,我只是在外界用到了這個而已崔慧。


#import <UIKit/UIKit.h>

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



@property (nonatomic ,assign) CGPoint startPoint;//觸摸起始點

@property (nonatomic ,assign) CGPoint endPoint;//觸摸結(jié)束點

@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  // 根據(jù)圖片的使用環(huán)境和所處的繪圖上下文自動調(diào)整渲染模式穴墅。
        // UIImageRenderingModeAlwaysOriginal   // 始終繪制圖片原始狀態(tài)惶室,不使用Tint Color。
        // UIImageRenderingModeAlwaysTemplate   // 始終根據(jù)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];
    
    // 移除之前的所有行為
    // 如果不移除,之前移動所觸發(fā)的物理吸附事件就會一直執(zhí)行
    [self.animator removeAllBehaviors];
    
    
}

//觸摸移動
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
    
    //得到觸摸點
    // 這里只有一個手指誉结,移動的坐標只有一個
    UITouch *startTouch = [touches anyObject];
    
    //將觸摸點賦值給touchView的中心點 也就是根據(jù)觸摸的位置實時修改view的位置
    
    self.center = [startTouch locationInView:self.superview];
    
}

//結(jié)束觸摸

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    
    //得到觸摸結(jié)束點
    
    UITouch *endTouch = [touches anyObject];
    
    //返回觸摸結(jié)束點
    // 這個方法是指:觸摸的點時在哪個視圖鹅士,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];
        
        // 吸附行為中的兩個吸附點之間的距離,通常用這個屬性來調(diào)整吸附的長度以舒,可以創(chuàng)建吸附行為之后調(diào)用趾痘。系統(tǒng)基于你創(chuàng)建吸附行為的方法來自動初始化這個長度
        [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) {
        
        // 創(chuàng)建物理仿真器(ReferenceView : 仿真范圍)
        
        _animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.superview];
        
        //設置代理
        
        _animator.delegate = self;
        
    }
    
    return _animator;
    
}



#pragma mark ---BreathingAnimation 呼吸動畫

// 實質(zhì)就是一個循環(huán)動畫蔓钟,透明度來回改變
- (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];
        
    }];
    
}



最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末永票,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌侣集,老刑警劉巖键俱,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異世分,居然都是意外死亡编振,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門臭埋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來踪央,“玉大人,你說我怎么就攤上這事瓢阴〕澹” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵荣恐,是天一觀的道長液斜。 經(jīng)常有香客問我,道長募胃,這世上最難降的妖魔是什么旗唁? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任畦浓,我火速辦了婚禮痹束,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘讶请。我一直安慰自己祷嘶,他們只是感情好,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布夺溢。 她就那樣靜靜地躺著论巍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪风响。 梳的紋絲不亂的頭發(fā)上嘉汰,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機與錄音状勤,去河邊找鬼鞋怀。 笑死,一個胖子當著我的面吹牛持搜,可吹牛的內(nèi)容都是我干的密似。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼葫盼,長吁一口氣:“原來是場噩夢啊……” “哼残腌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤抛猫,失蹤者是張志新(化名)和其女友劉穎蟆盹,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闺金,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡日缨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了掖看。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匣距。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哎壳,靈堂內(nèi)的尸體忽然破棺而出毅待,到底是詐尸還是另有隱情,我是刑警寧澤归榕,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布尸红,位于F島的核電站,受9級特大地震影響刹泄,放射性物質(zhì)發(fā)生泄漏外里。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一特石、第九天 我趴在偏房一處隱蔽的房頂上張望盅蝗。 院中可真熱鬧,春花似錦姆蘸、人聲如沸墩莫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狂秦。三九已至,卻和暖如春推捐,著一層夾襖步出監(jiān)牢的瞬間裂问,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工牛柒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留堪簿,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓焰络,卻偏偏與公主長得像戴甩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子闪彼,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容