一. 為什麼要寫這篇文章?
這是一個很古老的話題,從兩年前新浪微博開始使用多層動畫制作iOS App的啟動引導頁讓人眼前一亮(當然,微博是不是歷史第一個這個問題值得商榷)之後,各種類型的引導頁層出不窮,到如今,github上也有了各種的成型的library存在供選擇,同時不少app也已經慢慢的開始返璞歸真回歸單一靜態引導頁。雖然時尚的潮流不停的在變化,但是我一直在思索,這種多圖層的啟動引導動畫到底是什麼個結構?實現起來究竟有多難?本文,將試圖探尋這個話題。
二. 我們要做成什麼樣子的?
首先定下目標,我們要實現的是啟動引導畫面中的一種——多層次動畫。然後我們需要設定一個動畫的主題,我們需要表達我們的情感,或者抒情~或者動人~或者逗比~。當然這大部分是設計師的工作。
好吧,既然是demo,而我又不懂設計又不懂美學又不懂PS大法,那麼,就大概也許做成一個這樣子的把~~~
總結一下最終目標要有幾個要點:
1、4個頁。
2、每個頁都可能有若干分層,動畫速度不同。
3、整個滑動的手感應該是順滑並且是頁面式的。
三. 用什麼控件做?
開頭我講過,這是要探索,而不是為了實現,所以絕對不能借助任何3rd的library來完成。最大限度的利用apple的原生控件,是解決問題之道。
So,我們當然選用UIScrollView咯~~~除非你是個手工控。。。就要用最基本的UIView實現一個類似的滑動效果的UIScrollView。
啥?你問我UIScrollView是啥?
。。。。。。
下面是UIScrollView的幾個關鍵屬性,我相信你是明白的。需要注意的是,伴隨著scrollview的左右拖動,contentOffset是在一直變換的。數值范圍:(0,0) – (320 * 3, 0)。而這個屬性,是我們需要使用的關鍵數值。
四. 怎麼做?
上面我啰嗦了半天,最後告訴大家要用UIScrollView做,那麼問題來了,挖掘機技術哪家強?啊不,到底應該怎麼做?下面是干貨~
1. 首先我們要把我們承載整個動畫場面的scrollView造出來
如下,需要設置scrollView的幾個關鍵屬性:frame, contentSize, alwaysBounceHorizontal, paginEnabled(這個如果是NO,那麼頁面間的彈性效果就沒了), delegate(需要設置從而獲取scrollview的滾動狀態)等等。
//初始化 scrollview - (void)initScrollView { CGSize screenSize = [UIScreen mainScreen].bounds.size; _scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, screenSize.height)]; //我們的scrollView的frame應該是屏幕大小 _scrollView.contentSize = CGSizeMake(screenSize.width * 4, screenSize.height); //但是我們希望我們scrollView的可被展現區域是4個屏幕橫排那麼大 _scrollView.alwaysBounceHorizontal = YES;//橫向一直可拖動 _scrollView.pagingEnabled = YES;//關鍵屬性,打開page模式。 _scrollView.delegate = self; _scrollView.showsHorizontalScrollIndicator = NO;//不要顯示滾動條~ [self.view addSubview:_scrollView]; }
現在我們已經准備好了動畫的畫布,下面開始將每一頁的元素加上去。
2. 加入頁面元素
還是不要全篇幅貼代碼了,以第一頁為例把。
前面掉渣天的蛇雞屎(我)的demo圖已經表明,第一頁,我們要有3個UILabel,一個UIImageView。
那麼好,這些元素我們就給他聲明出來。
@interface ViewController () @property (strong, nonatomic) UIScrollView *scrollView;//這是基本! @property (strong, nonatomic) UIImageView *girlImageView; @property (strong, nonatomic) UILabel *label_page1_1; @property (strong, nonatomic) UILabel *label_page1_2; @property (strong, nonatomic) UILabel *label_page1_3; @end
然後把第一頁的元素,加進來~
//為了更方便的初始化UILabel,我為UILabel增加了一個簡易的類方法。是為了讓代碼更簡潔可讀。 + (instancetype)labelWithText:(NSString *)text font:(UIFont *)font color:(UIColor *)color origin:(CGPoint)origin { UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(origin.x, origin.y, 1000, 20)]; label.text = text; label.font = font; label.textColor = color; [label sizeToFit]; return label; } //然後我們將第一頁的元素加進來。 self.label_page1_1 = [UILabel labelWithText:@"我要買iPhone6!" font:[UIFont systemFontOfSize:18.0f] color:[UIColor redColor] origin:CGPointMake(140, 200)]; [self.scrollView addSubview:self.label_page1_1]; self.label_page1_2 = [UILabel labelWithText:@"我要看醫生演唱會~~~~" font:[UIFont systemFontOfSize:18.0f] color:[UIColor blackColor] origin:CGPointMake(140, 240)]; [self.scrollView addSubview:self.label_page1_2]; self.label_page1_3 = [UILabel labelWithText:@"我要去大理!" font:[UIFont systemFontOfSize:18.0f] color:[UIColor orangeColor] origin:CGPointMake(140, 280)]; [self.scrollView addSubview:self.label_page1_3]; self.girlImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"image_girl"]]; self.girlImageView.frame = CGRectMake(100, kScreenHeight - 200 - 50, 100, 200); [self.scrollView addSubview:self.girlImageView];
3. 讓第一頁動起來~~
在第一頁剛剛顯示的時候,我們就希望第一頁的元素能夠有一個動起來的效果。那我們在上面剛剛加入第一頁元素之後,可以緊接著做下面的事情:
self.girlImageView.transform = CGAffineTransformMakeTranslation(-200, 0); self.label_page1_1.transform = CGAffineTransformMakeTranslation(- 100, 0); self.label_page1_2.transform = CGAffineTransformMakeTranslation(100, 0); self.label_page1_3.transform = CGAffineTransformMakeTranslation(- 120, 0); [UIView animateWithDuration:0.7 animations:^{ self.girlImageView.transform = CGAffineTransformMakeTranslation(0, 0); self.label_page1_1.transform = CGAffineTransformMakeTranslation(0, 0); self.label_page1_2.transform = CGAffineTransformMakeTranslation(0, 0); self.label_page1_3.transform = CGAffineTransformMakeTranslation(0, 0); }];
可以看到,我們分別給第一頁的四個元素不同的水平位移,然後希望它用0.7秒的時間,移動到之前init他們時候的位置。這樣就完成了第一個4層的錯位動畫。
然後,我們希望在手指滑動scrollview 的時候,第一頁的四個元素可以有相應的分層錯位動畫,那麼我們第一需要拿到當前scrollView的位移量,也就是前面提到的很重要的contentOffset。這個值,在:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
中,可以實時的獲取。
具體來看,怎麼做。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView { CGFloat currentX = scrollView.contentOffset.x; if (currentX <= kScreenWidth) { self.girlImageView.transform = CGAffineTransformMakeTranslation((kScreenWidth + 100.0f) * currentX / kScreenWidth, 0); self.label_page1_2.transform = CGAffineTransformMakeTranslation(- 200 * currentX / kScreenWidth, 0); } }
呵呵,是不是看不懂,那就對了。。。
下面解釋下,首先拋出兩個定理:
定理一:在scrollview的滑動過程中,視覺上看,scrollview上的元素的移動方向與手指滑動方向相反,並且移動的距離與手指滑動的距離相等。但所有元素在scrollview上的物理位置並未改變。
定理二:在scrollview的滑動過程中,當且僅當scrollview上的元素的物理移動距離與手指滑動距離相等並且移動方向相反時,scrollview的元素視覺位置保持不變。
然後我們有兩個需求:
第一,希望那個小女孩跟隨手指滑動的時候,視覺上不是向左移動一直到消失,而是向右移動,待滑動到第二頁的時候,小女孩出現在屏幕右側。
我們應該明確,小女孩的移動,只能是在scrollview上位置的移動。根據定理二,我們知道,如果保持視覺上小女孩位置不變,小女孩在scrollView上的實際物理位移應該是:
公式 4.3.1 baseDistance = kScreenWidth 屏幕寬度
那麼如果我們希望在移動到第二頁之後,小女孩的視覺位置右移了100像素,那麼小女孩在scrollView上的實際物理位移應該是:
公式 4.3.2 distance = baseDistance + 100
第一頁到第二頁,scrollView一共位移是 kScreenWidth ,當前scrollView位移是 contentOffset.x ,可以得出,當前位移的比例:
公式 4.3.3 status = scrollView.contentOffest.x / distance
由 4.3.1 4.3.2 4.3.3可得,我們設置小女孩位移的方式:
self.girlImageView.transform = CGAffineTransformMakeTranslation((kScreenWidth + 100.0f) * currentX / kScreenWidth, 0);
第二個需求,希望第一頁中,第二個label的向左移動速度快於其他兩個label。
根據定理二,和類似於上面的推倒(推導)方式,也易得第二個label的位移方式:
self.label_page1_2.transform = CGAffineTransformMakeTranslation(- 200 * currentX / kScreenWidth, 0);
五. 總結
綜上所述,我們知道了分層動畫的基本原理。如果使用更多的圖層,更多的位移或者角度變化,就能組合出更加復雜的分層動畫。
可以看到,分層動畫的基本原理並不復雜,但是為什麼那麼多人傾向於借助3rd的library來實現呢?一個字,懶。
現如今移動開發領域對於美感和交互的要求越來越高,而開發出一款精美的app,設計師所需要付出的靈感和努力也越發顯得重要。作為一個不怎麼有美感的iOS工程師,想要在移動浪潮中立於不敗之地,不斷嘗試更多新的可能遠比實現更多的功能更加重要。
最後的最後,附一個demo運行效果:
點擊查看demo