你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS編程技術 >> 仿寫及比較標哥的iOS時鐘動畫

仿寫及比較標哥的iOS時鐘動畫

編輯:IOS編程技術

一、前言

  以前看各種絢麗的UI特效動畫代碼,采用的方法是會先運行一篇,然後直接去看實現代碼。初學時抱著瞻仰的態度去接觸,去認識,是沒有錯的。但是在了解了像素、動畫渲染機制,CoreAnimation API,推導過二維、三維的仿射矩陣之後,我們可以改變閱讀UI動畫博文或者是源碼的方式了。

  Talk is cheap, show me the code——Linus Torvalds。

  大量的仿寫;一定一定要多寫——葉孤城__ 在CodeReview線下大會上的發言。

  最近安居客、猿題庫、蘑菇街、滴滴都有在談iOS客戶端的架構設計,很多童鞋在說看不懂或者根本就是viper之類的話,是不是舉重若輕不敢輕易評論。但只有經歷過多人合作,沒有統一架構規范,不斷填充ViewController, 使得vc從幾十行增長到千余行再拆分至幾百行;經歷過近百個VC類的各種產品跳轉需求創(瞎)新(搞),才能了解Massive ViewController的痛和頁面跳轉邏輯cyclomatic complexity超量的難以承受吧。

二、仿寫的UI動畫結果比較

  原文鏈接:http://www.henishuo.com/clock-animation/

標哥博文提供的工程運行截圖 筆者的工程運行截圖

  從呈現效果的直觀認識來看,質量是相近的;

  從UI美觀上來看,標哥集中在核心功能編碼,我有些注重無謂的美學外觀,因此對指針和鐘心的指針蓋冒都做了路徑繪制,看起來會漂亮一點麼^^

  從運行性能上來看,CPU的消耗都是0,內存、動畫流暢性等方面是差不多的

  從組件可用性來看,標哥當然不該浪費精力做這麼個簡單的組件,所以我提供的組件API還是比較多的,提供了代碼xib兼容初始化,鐘表時間的設置,暫停,運行等,鐘表時間值的手動KVO,表盤背景圖的設置等,基本上有虛擬鐘表的需求時,我的這個組件是可以直接拿來用的。

  從編碼思路上看,標哥將現實世界問題直接轉換到機器實質,比如直接指定指針動畫的duration;而我的組件開發思路一直是搭建現實世界到機器世界的中間橋梁,這樣任何現實世界的規律都能通過中間橋梁轉換到工程方法和UI顯示。任何運行狀態都能通過中間橋梁映射到現實世界,被人類邏輯所理解。標哥的思路定然是高效的,但我的思路更貼近人類思維。還是那句話吧,編程之路法無定法,但由你自己選擇。

三、UI與技術需求分析

  所有的需求分析和編碼工作是在閱讀標哥提供的源碼Demo之前的,以鍛煉個人獨立分析問題、解決的問題能力。

  UI實現上,因為不提供交互,所以選擇輕量級的CALayer,用到的OC類主要是UIView、CAShapeLayer、UIBezierPath。另外在中心蓋帽的繪制上,我用了CAGradientLayer。

  邏輯實現上,我的思路是周期一秒鐘後,人為去驅動鐘表時間屬性變化和UI更新,因此用到了NSTimer。這裡NSTimer有強引用的問題,基本有四種解決方案:弱引用,關聯對象,中間代理,GCD Timer等。標哥選擇了第一種,我的看法是我需要強執有我要用的東西,當然這也是從哲學思辨來考慮。因此,我用了中間代理這種方法,以前有寫過,就直接拿來用了。在KVO的實現上,我使用了手動KVO,因為time屬性提供給使用方用setter方法來設置更改,接入方肯定不想觀察到自己設置時的KVO,還得先移除,再添加。因此,我編碼時setter方法時不發布變化信息,而是在鐘表自動運行時time的改變提供手動KVO.

  其它需要注意的是,NSTimer的創建與提交需要消耗CPU,因此不要頻繁的創建銷毀,只在接入方設置更改當前時間時,更換Timer。

四、類設計與編碼

  在其它語言中,有接口的概念但OC沒有。那麼如何面向接口編程呢,我想Protocol是一種可取的方法。在寫一個類之前,如果有時間還是要做一下接口設計比較好。示例如下:

@protocol HSClockViewProtocol <NSObject>
/**
 *  一個時鐘與外界的通信,就是它的時間。
 *  要有setter/getter, KVO-compliance
 */
@property (nonatomic, assign) NSTimeInterval time;
/**
 *  暫停時鐘運行
 */
- (void) pause;
/**
 *  繼續或者開始時鐘運行
 */
- (void) work;

/**
 *  設置表盤背景圖
 *
 *  @param image 表盤背景圖,UIImage對象
 */
- (void) setDialBackGroundImage:(UIImage *) image;

@end

五、現實世界與機器世界的轉換關系

  在虛擬時鐘這個問題上還是比較簡單的,主要在於時間字符串或者Unix時間戳到三個指針的弧度角行向量的轉換,代碼如下:

/**
 *  時針、分針、秒針的弧度角(z軸向外)
 */
typedef struct HSClockHandRadian {
    double hourRadian;
    double minuteRadian;
    double secondRadian;
} HSClockHandRadian;

HSClockHandRadian HSRadianFromTimeInterval(NSTimeInterval time) {
    time += 8 * 60 * 60; //北京時間 +8
    NSInteger offsetIn12Hour = (NSInteger)time % (12 * 60 * 60); // 以12小時為周期時,偏移的秒數,時針
    NSInteger offsetIn1Hour = (NSInteger)time % (1 * 60 * 60); // 以1小時為周期時,偏移的秒數,分針
    NSInteger offsetIn1Minute = (NSInteger)time % (1 * 60); // 以1分鐘為周期時,偏移的秒數,秒針
    
    HSClockHandRadian handRadian;
    handRadian.hourRadian = offsetIn12Hour * 1.0 / (12 * 60 * 60) * M_PI * 2- M_PI_2;
    handRadian.minuteRadian = offsetIn1Hour * 1.0  / (1 * 60 * 60) * M_PI * 2 - M_PI_2;
    handRadian.secondRadian = offsetIn1Minute * 1.0  / (1 * 60) * M_PI * 2 - M_PI_2;
    return handRadian;
}

HSClockHandRadian HSTimeFromTimeStr(NSString *timeStr) {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.dateFormat = @"yyyy-MM-dd hh:mm:ss";
    NSString *dateStr = [NSString stringWithFormat:@"1970-01-01 %@", timeStr];
    NSDate *date = [dateFormatter dateFromString:dateStr];
    NSTimeInterval timeStamp = [date timeIntervalSince1970];
    return HSRadianFromTimeInterval(timeStamp);
}

HSClockHandRadian HSTimeFromDate(NSDate *date) {
    NSTimeInterval timeStamp = [date timeIntervalSince1970];
    return HSRadianFromTimeInterval(timeStamp);
}

六、指針弧度角到仿射矩陣的變換

  筆者建議,還是自己去把轉換關系推導出來,因此不打算提供轉換矩陣^^。二維中的平移,變換,平面原點旋轉,平面任何點旋轉,三維中的平移,變換,繞坐標軸旋轉,繞任意軸旋轉。

  在這裡提供幾點思路和注意點:

  1.cor_new = cor_old * M,其中cor_new、cor_old均為行向量,一個是原值,一個是期望值,這兩個我們知道後,可以把仿射矩陣M推導出來。

  2.iOS在CA中采用與UIKit相同的左手坐標系,三維坐標系時Z軸向外。旋轉正方向二維時為逆時針,三維時看向旋轉軸的負方向,逆時針為旋轉角的增長方向。

  3.繞任意軸旋轉時,先將坐標系轉換,使得旋轉軸與一坐標軸重合,在此坐標系完成旋轉後,再做坐標系逆轉換。

  4.推導過程涉及到矩陣運算,相乘,求逆等;涉及到三角函數和差化積等。

七、工程中聲明的私有屬性、成員變量和私有方法

  關於在Extension裡寫私有屬性還是在implement後的花括號裡寫成員變量,唐巧大神有過論述,有興趣的可以去看下唐巧的技術博客。私有方法是否在Extension裡聲明呢,我的看法是盡量寫一下,別人看你代碼的時候能夠迅速的知道你實現了哪些私有方法。代碼示例如下:

@interface HSClockView()

/**
 *  內部標識時鐘是否在運行中
 */
@property (nonatomic, assign, getter=isWorking) BOOL working;

/**
 *  初始化當前時間,背景,指針, 供代碼創建與xib創建共用
 */
- (void) p_initClockView;

/**
 *  初始化指針並返回
 *
 *  @param width      指針寬度
 *  @param height     指針高度
 *  @param tailLength 指針尾部長度
 *  @param tickLength 指針尖部長度
 *
 *  @return 初始化好path的ShapeLayer
 */
- (CAShapeLayer *) p_handLayerWithWidth:(CGFloat)width height:(CGFloat)height tailLength:(CGFloat)tailLength tickLength:(CGFloat)tickLength;

/**
 *  不含時鐘運行標識判斷與修改的私有方法,動畫執行與UI更新主方法
 *
 *  @param time 要設置的時間戳
 */
- (void) p_setTime:(NSTimeInterval)time;

/**
 *  定時器的觸發處理,更新鐘表時間
 */
- (void) p_handleTimeSource;

@end

@implementation HSClockView {
    CAShapeLayer *_hourLayer;
    CAShapeLayer *_minuteLayer;
    CAShapeLayer *_secondLayer;
    NSTimer *_timer;
}

八、結語

  寫這個工程Demo差不多用了5個小時,編碼速度還有待提高;在編碼思路上,再思考是搭建現實世界橋梁,還是直接轉換成機器思維,或者是將兩者良好的綜合運用。

  本文的工程源碼:https://github.com/1962449521/OCDemos/tree/master/ClockDemo 

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved