實用小技巧(十三):一種類酷我音樂盒動畫實現(xiàn)

版本記錄

版本號 時間
V1.0 2017.07.02

前言

在app中,我們經(jīng)常需要點擊別人分享發(fā)布的圖片或者頭像梳凛,然后放大縮小等年枕,還可以保存到本地相冊等。感興趣的可以看看我寫的其他小技巧懊纳。
1. 實用小技巧(一):UIScrollView中上下左右滾動方向的判斷

2. 實用小技巧(二):屏幕橫豎屏的判斷和相關(guān)邏輯
3.實用小技巧(三):點擊手勢屏蔽子視圖的響應(yīng)
4.實用小技巧(四):動態(tài)的增刪標(biāo)簽視圖
5.實用小技巧(五):通過相冊或者相機(jī)更改圖標(biāo)
6.實用小技巧(六):打印ios里所有字體
7. 實用小技巧(七):UITableViewCell自適應(yīng)行高的計算
8. 實用小技巧(八):數(shù)字余額顯示的分隔
9.實用小技巧(九):類頭條模糊背景的實現(xiàn)
10.實用小技巧(十):晃動手機(jī)換后臺服務(wù)器網(wǎng)絡(luò)
11.實用小技巧(十一):scrollView及其子類顯示的一些異常處理
12.實用小技巧(十二):頭像圖片縮放以及保存到相冊簡單功能的實現(xiàn)

功能需求

??酷我音樂盒有的頁面可以左右用手拖動,當(dāng)大于一定的角度就可以dismiss或者其他操作亡容,類似的在阿里巴巴的釘釘中也有應(yīng)用嗤疯,其實簡單來說就是一個pan手勢以及錨點和position的應(yīng)用,下面我們就簡單的實現(xiàn)個效果闺兢。

功能實現(xiàn)

下面我們就直接看代碼吧茂缚。

先看一下代碼結(jié)構(gòu)。

代碼結(jié)構(gòu)

下面我們就看一下詳細(xì)代碼屋谭。

1. AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    JJMusicVC *musicVC = [[JJMusicVC alloc] init];
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:musicVC];
    self.window.rootViewController = nav;
    [self.window makeKeyAndVisible];
    return YES;
}


2. JJMusicVC.h
#import <UIKit/UIKit.h>

@interface JJMusicVC : UIViewController

@end

3.JJMusicVC.m

#import "JJMusicVC.h"
#import "JJMusciPlayVC.h"

@interface JJMusicVC ()

@property (nonatomic, strong) UIButton *playButton;


@end

@implementation JJMusicVC

#pragma mark - Object Private Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self setupUI];
}

#pragma mark - Object Private Function

- (void)setupUI
{
    self.view.backgroundColor = [UIColor lightGrayColor];
    
    UIButton *playButton = [UIButton buttonWithType:UIButtonTypeCustom];
    [playButton setTitle:@"播放音樂" forState:UIControlStateNormal];
    [playButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
    playButton.titleLabel.font = [UIFont boldSystemFontOfSize:20.0];
    playButton.frame = CGRectMake((self.view.bounds.size.width - CGRectGetWidth(playButton.frame)) * 0.5, (self.view.bounds.size.height - CGRectGetHeight(playButton.frame)) * 0.5, 100.0, 30.0);
    [playButton addTarget:self action:@selector(playButtonDidClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:playButton];
    self.playButton = playButton;

}

#pragma mark - Action && Notification

- (void)playButtonDidClick
{
    JJMusciPlayVC *playVC = [[JJMusciPlayVC alloc] init];
    
    [self presentViewController:playVC animated:YES completion:nil];
}

@end
4. JJMusciPlayVC.h

#import <UIKit/UIKit.h>

@interface JJMusciPlayVC : UIViewController

@end

5. JJMusciPlayVC.m
#import "JJMusciPlayVC.h"
#import "JJMusicAnimation.h"

@interface JJMusciPlayVC ()

@property (nonatomic, strong) JJMusicAnimation *animation;
@property (nonatomic, strong) UIImageView *playImageView;

@end

@implementation JJMusciPlayVC


- (instancetype)init
{
    self = [super init];
    if (self) {
        self.modalPresentationStyle = UIModalPresentationCustom;
        
        //這幾行不能放在viewDidLoad里面脚囊,否則后面就是黑屏的
        JJMusicAnimation *animation = [[JJMusicAnimation alloc] init];
        self.transitioningDelegate = animation;
        self.animation = animation;
    }
    return self;
}


- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self setupUI];
}

#pragma mark - Object Private Function

- (void)setupUI
{
    self.view.backgroundColor = [UIColor lightGrayColor];
    
    UIImageView *playImageView = [[UIImageView alloc] initWithFrame:self.view.frame];
    playImageView.userInteractionEnabled = YES;
    playImageView.image = [UIImage imageNamed:@"music"];
    [self.view addSubview:playImageView];
    self.playImageView = playImageView;
    
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)];
    
    [self.view addGestureRecognizer:pan];
}

#pragma mark - Action && Notification

- (void)panAction:(UIPanGestureRecognizer *)sender
{
    // 1.獲取用戶的手指拖拽的偏移量
    CGFloat offsetX = [sender translationInView:self.view].x;
    
    // 2.將獲取的偏移量,轉(zhuǎn)為角度
    CGFloat angle = offsetX / self.view.bounds.size.width * M_PI_4;
    
    switch (sender.state)
    {
            
        case UIGestureRecognizerStateBegan:
            
            // 修改錨點,進(jìn)行軸的偏移;
            self.view.layer.anchorPoint = CGPointMake(0.5, 1.5);
            // 修改position,實現(xiàn)正常顯示!
            self.view.layer.position = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height * 1.5);
            
        case UIGestureRecognizerStateChanged:

            self.view.transform = CGAffineTransformMakeRotation(angle);
            break;
            
        case UIGestureRecognizerStateEnded:
        {
            // 需要判斷,旋轉(zhuǎn)的角度 如果大于了某一值  dimiss  否則,歸位!
            if (ABS(angle) > 0.33) {
                [self dismissViewControllerAnimated:YES completion:nil];
                break;
            }
        }
        case UIGestureRecognizerStateCancelled:
        case UIGestureRecognizerStateFailed:
        {
            // 歸位!
            [UIView animateWithDuration:.3 animations:^{
                self.view.transform = CGAffineTransformIdentity;
            } completion:^(BOOL finished) {
                
                // 恢復(fù)錨點
                self.view.layer.anchorPoint = CGPointMake(0.5, 0.5);
                
                //不能使用self.view.center->它的值目前還是之前修改過的position的值!太大!
                self.view.layer.position = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height * 0.5);
            }];
        }
            break;
            
        default:
            break;
    }
}

@end

6. JJMusicAnimation.h

#import <UIKit/UIKit.h>

@interface JJMusicAnimation : NSObject <UIViewControllerTransitioningDelegate>

@end



7. JJMusicAnimation.m
#import "JJMusicAnimation.h"

@interface JJMusicAnimation () <UIViewControllerAnimatedTransitioning>


@end

@implementation JJMusicAnimation

#pragma mark - UIViewControllerTransitioningDelegate

/*
 參數(shù)1 presented  被展示出來的控制器對象!      twoController
 參數(shù)2 presenting 正在顯示別的控制器的那個對象! viewController
 參數(shù)3 source     源控制器,一般跟參數(shù)2是同一個對象! viewController
 */
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
    NSLog(@"參數(shù)1%@ 參數(shù)2%@ 參數(shù)3%@", presented, presenting, source);
    
    return self;
}

#pragma mark - UIViewControllerAnimatedTransitioning

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
    return 0.5;
}

// 轉(zhuǎn)場動畫的效果!
// transitionContext 轉(zhuǎn)場的上下文
// 相當(dāng)于是轉(zhuǎn)場動畫的舞臺! 里面提供了我們做動畫需要的所有信息!
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    
    // 1.獲取容器視圖
    UIView *containerV = [transitionContext containerView];
    
    // 2.獲取要顯示的控制器
    UIViewController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    toVc.view.frame = containerV.bounds;
    
    // 3.展示控制器的視圖到界面!
    [containerV addSubview:toVc.view];
    
    // 4.通過動畫進(jìn)行實現(xiàn)旋轉(zhuǎn)的方式顯示視圖!
        // 1.修改旋轉(zhuǎn)的軸!
    toVc.view.layer.anchorPoint = CGPointMake(0.5, 1.5);
    toVc.view.layer.position = CGPointMake(toVc.view.bounds.size.width * 0.5, toVc.view.bounds.size.height * 1.5);
    
        // 2.以旋轉(zhuǎn)的方式展示
        // - 轉(zhuǎn)動 -90°
    toVc.view.transform = CGAffineTransformMakeRotation(-M_PI_2);
    
        // - 動畫顯示
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        toVc.view.transform = CGAffineTransformIdentity;
    } completion:^(BOOL finished) {
        toVc.view.layer.anchorPoint = CGPointMake(0.5, 0.5);
        toVc.view.layer.position = CGPointMake(toVc.view.bounds.size.width * 0.5, toVc.view.bounds.size.height * 0.5);
        
        //告訴系統(tǒng)動畫執(zhí)行完了!要不然界面不能點!
        [transitionContext completeTransition:YES];
    }];
}

@end

至此,所有代碼就結(jié)束了桐磁。

功能效果

下面我們就看一下功能效果悔耘。

效果1
效果2
整體效果

知識點補(bǔ)充

我們這里用到了兩個重要的概念就是anchor point 和 position。

  • anchorPoint: 每一個UIView都默認(rèn)關(guān)聯(lián)著一個CALayer, UIView有frame我擂、bounds和center三個屬性衬以,CALayer也有類似的屬性,分別為frame校摩、bounds看峻、position、anchorPoint衙吩,而anchorPoint會移動layer的位置互妓。下面我們就舉例說一下anchorPoint的使用。

??從一個例子開始入手吧坤塞,想象一下冯勉,把一張A4白紙用圖釘訂在書桌上,如果訂得不是很緊的話尺锚,白紙就可以沿順時針或逆時針方向圍繞圖釘旋轉(zhuǎn)珠闰,這時候圖釘就起著支點的作用。我們要解釋的anchorPoint就相當(dāng)于白紙上的圖釘瘫辩,它主要的作用就是用來作為變換的支點伏嗜,旋轉(zhuǎn)就是一種變換坛悉,類似的還有平移、縮放承绸。繼續(xù)擴(kuò)展裸影,很明顯,白紙的旋轉(zhuǎn)形態(tài)隨圖釘?shù)奈恢貌煌煌瑘D釘訂在白紙的正中間與左上角時分別造就了兩種旋轉(zhuǎn)形態(tài)轩猩,這是由圖釘(anchorPoint)的位置決定的。如何衡量圖釘(anchorPoint)在白紙中的位置呢荡澎?在iOS中均践,anchorPoint點的值是用一種相對bounds的比例值來確定的,在白紙的左上角摩幔、右下角彤委,anchorPoint分為為(0,0), (1, 1),也就是說anchorPoint是在單元坐標(biāo)空間(同時也是左手坐標(biāo)系)中定義的或衡。類似地焦影,可以得出在白紙的中心點、左下角和右上角的anchorPoint為(0.5,0.5), (0,1), (1,0)封断。如下圖所示斯辰。

效果1
效果1旋轉(zhuǎn)
效果2
效果2旋轉(zhuǎn)
  • position: 確切地說,position是layer中的anchorPoint點在superLayer中的位置坐標(biāo)坡疼。因此可以說, position點是相對superLayer的彬呻,anchorPoint點是相對layer的,兩者是相對不同的坐標(biāo)空間的一個重合點柄瑰。 再來看看position的原始定義: The layer’s position in its superlayer’s coordinate space废岂。 可以理解成position是layer相對superLayer坐標(biāo)空間的位置,很顯然狱意,這里的位置是根據(jù)anchorPoint來確定的.

  • anchorPoint湖苞、position、frame:anchorPoint的默認(rèn)值為(0.5,0.5)详囤,也就是anchorPoint默認(rèn)在layer的中心點财骨。默認(rèn)情況下,使用addSublayer函數(shù)添加layer時藏姐,如果已知layer的frame值隆箩,根據(jù)上面的結(jié)論,那么position的值便可以用下面的公式計算:

position.x = frame.origin.x + 0.5 * bounds.size.width羔杨; 
position.y = frame.origin.y + 0.5 * bounds.size.height捌臊;

里面的0.5是因為anchorPoint取默認(rèn)值,更通用的公式應(yīng)該是下面的:

position.x = frame.origin.x + anchorPoint.x *
bounds.size.width兜材; 
position.y = frame.origin.y + anchorPoint.y *
bounds.size.height;
  • 特別注意:下面再來看另外兩個問題,如果單方面修改layer的position位置祈纯,會對anchorPoint有什么影響呢?修改anchorPoint又如何影響position呢寇荧?
    根據(jù)代碼測試,兩者互不影響执隧,受影響的只會是frame.origin揩抡,也就是layer坐標(biāo)原點相對superLayer會有所改變。換句話說镀琉,frame.origin由position和anchorPoint共同決定峦嗤,上面的公式可以變換成下面這樣的:
frame.origin.x = position.x - anchorPoint.x *
bounds.size.width; 
frame.origin.y = position.y - anchorPoint.y *
bounds.size.height屋摔;

這就解釋了為什么修改anchorPoint會移動layer寻仗,因為position不受影響,只能是frame.origin做相應(yīng)的改變凡壤,因而會移動layer。

  • 總結(jié)
    • position是layer中的anchorPoint在superLayer中的位置坐標(biāo).
    • 互不影響原則:單獨修改position與anchorPoint中任何一個屬性都不影響另一個屬性耙替。
    • frame亚侠、position與anchorPoint有以下關(guān)系:
frame.origin.x = position.x - anchorPoint.x *
bounds.size.width; 
frame.origin.y = position.y - anchorPoint.y *
bounds.size.height俗扇;

后記

未完硝烂,待續(xù)~~~

鎂鋁
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市铜幽,隨后出現(xiàn)的幾起案子滞谢,更是在濱河造成了極大的恐慌,老刑警劉巖除抛,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狮杨,死亡現(xiàn)場離奇詭異,居然都是意外死亡到忽,警方通過查閱死者的電腦和手機(jī)橄教,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喘漏,“玉大人护蝶,你說我怎么就攤上這事◆媛酰” “怎么了持灰?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長负饲。 經(jīng)常有香客問我堤魁,道長喂链,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任姨涡,我火速辦了婚禮衩藤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涛漂。我一直安慰自己赏表,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布匈仗。 她就那樣靜靜地躺著瓢剿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悠轩。 梳的紋絲不亂的頭發(fā)上间狂,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機(jī)與錄音火架,去河邊找鬼鉴象。 笑死,一個胖子當(dāng)著我的面吹牛何鸡,可吹牛的內(nèi)容都是我干的纺弊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼骡男,長吁一口氣:“原來是場噩夢啊……” “哼淆游!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起隔盛,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤犹菱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后吮炕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腊脱,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年龙亲,在試婚紗的時候發(fā)現(xiàn)自己被綠了虑椎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡俱笛,死狀恐怖捆姜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情迎膜,我是刑警寧澤泥技,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站磕仅,受9級特大地震影響珊豹,放射性物質(zhì)發(fā)生泄漏簸呈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一店茶、第九天 我趴在偏房一處隱蔽的房頂上張望蜕便。 院中可真熱鬧,春花似錦贩幻、人聲如沸轿腺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽族壳。三九已至,卻和暖如春趣些,著一層夾襖步出監(jiān)牢的瞬間仿荆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工坏平, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留拢操,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓舶替,卻偏偏與公主長得像令境,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子坎穿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

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