<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 屬性動畫 `CAAnimationDelegate`在任何頭文件中都找不到,但是可以在`CAAnimation`頭文件或者蘋果開發者文檔中找到相關函數。在這個例子中,我們用`-animationDidStop:finished:`方法在動畫結束之后來更新圖層的`backgroundColor`。 當更新屬性的時候,我們需要設置一個新的事務,并且禁用圖層行為。否則動畫會發生兩次,一個是因為顯式的`CABasicAnimation`,另一次是因為隱式動畫,具體實現見訂單8.3。 清單8.3 動畫完成之后修改圖層的背景色 ~~~ @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //create sublayer self.colorLayer = [CALayer layer]; self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f); self.colorLayer.backgroundColor = [UIColor blueColor].CGColor; //add it to our view [self.layerView.layer addSublayer:self.colorLayer]; } - (IBAction)changeColor { //create a new random color CGFloat red = arc4random() / (CGFloat)INT_MAX; CGFloat green = arc4random() / (CGFloat)INT_MAX; CGFloat blue = arc4random() / (CGFloat)INT_MAX; UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0]; //create a basic animation CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"backgroundColor"; animation.toValue = (__bridge id)color.CGColor; animation.delegate = self; //apply animation to layer [self.colorLayer addAnimation:animation forKey:nil]; } - (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag { //set the backgroundColor property to match animation toValue [CATransaction begin]; [CATransaction setDisableActions:YES]; self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue; [CATransaction commit]; } @end ~~~ 對`CAAnimation`而言,使用委托模式而不是一個完成塊會帶來一個問題,就是當你有多個動畫的時候,無法在在回調方法中區分。在一個視圖控制器中創建動畫的時候,通常會用控制器本身作為一個委托(如清單8.3所示),但是所有的動畫都會調用同一個回調方法,所以你就需要判斷到底是那個圖層的調用。 考慮一下第三章的鬧鐘,“圖層幾何學”,我們通過簡單地每秒更新指針的角度來實現一個鐘,但如果指針動態地轉向新的位置會更加真實。 我們不能通過隱式動畫來實現因為這些指針都是`UIView`的實例,所以圖層的隱式動畫都被禁用了。我們可以簡單地通過`UIView`的動畫方法來實現。但如果想更好地控制動畫時間,使用顯式動畫會更好(更多內容見第十章)。使用`CABasicAnimation`來做動畫可能會更加復雜,因為我們需要在`-animationDidStop:finished:`中檢測指針狀態(用于設置結束的位置)。 動畫本身會作為一個參數傳入委托的方法,也許你會認為可以控制器中把動畫存儲為一個屬性,然后在回調用比較,但實際上并不起作用,因為委托傳入的動畫參數是原始值的一個深拷貝,從而不是同一個值。 當使用`-addAnimation:forKey:`把動畫添加到圖層,這里有一個到目前為止我們都設置為`nil`的`key`參數。這里的鍵是`-animationForKey:`方法找到對應動畫的唯一標識符,而當前動畫的所有鍵都可以用`animationKeys`獲取。如果我們對每個動畫都關聯一個唯一的鍵,就可以對每個圖層循環所有鍵,然后調用`-animationForKey:`來比對結果。盡管這不是一個優雅的實現。 幸運的是,還有一種更加簡單的方法。像所有的`NSObject`子類一樣,`CAAnimation`實現了KVC(鍵-值-編碼)協議,于是你可以用`-setValue:forKey:`和`-valueForKey:`方法來存取屬性。但是`CAAnimation`有一個不同的性能:它更像一個`NSDictionary`,可以讓你隨意設置鍵值對,即使和你使用的動畫類所聲明的屬性并不匹配。 這意味著你可以對動畫用任意類型打標簽。在這里,我們給`UIView`類型的指針添加的動畫,所以可以簡單地判斷動畫到底屬于哪個視圖,然后在委托方法中用這個信息正確地更新鐘的指針(清單8.4)。 清單8.4 使用KVC對動畫打標簽 ~~~ @interface ViewController () @property (nonatomic, weak) IBOutlet UIImageView *hourHand; @property (nonatomic, weak) IBOutlet UIImageView *minuteHand; @property (nonatomic, weak) IBOutlet UIImageView *secondHand; @property (nonatomic, weak) NSTimer *timer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //adjust anchor points self.secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f); self.minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f); self.hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f); //start timer self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES]; //set initial hand positions [self updateHandsAnimated:NO]; } - (void)tick { [self updateHandsAnimated:YES]; } - (void)updateHandsAnimated:(BOOL)animated { //convert time to hours, minutes and seconds NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit; NSDateComponents *components = [calendar components:units fromDate:[NSDate date]]; CGFloat hourAngle = (components.hour / 12.0) * M_PI * 2.0; //calculate hour hand angle //calculate minute hand angle CGFloat minuteAngle = (components.minute / 60.0) * M_PI * 2.0; //calculate second hand angle CGFloat secondAngle = (components.second / 60.0) * M_PI * 2.0; //rotate hands [self setAngle:hourAngle forHand:self.hourHand animated:animated]; [self setAngle:minuteAngle forHand:self.minuteHand animated:animated]; [self setAngle:secondAngle forHand:self.secondHand animated:animated]; } - (void)setAngle:(CGFloat)angle forHand:(UIView *)handView animated:(BOOL)animated { //generate transform CATransform3D transform = CATransform3DMakeRotation(angle, 0, 0, 1); if (animated) { //create transform animation CABasicAnimation *animation = [CABasicAnimation animation]; [self updateHandsAnimated:NO]; animation.keyPath = @"transform"; animation.toValue = [NSValue valueWithCATransform3D:transform]; animation.duration = 0.5; animation.delegate = self; [animation setValue:handView forKey:@"handView"]; [handView.layer addAnimation:animation forKey:nil]; } else { //set transform directly handView.layer.transform = transform; } } - (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag { //set final position for hand view UIView *handView = [anim valueForKey:@"handView"]; handView.layer.transform = [anim.toValue CATransform3DValue]; } ~~~ 我們成功的識別出每個圖層停止動畫的時間,然后更新它的變換到一個新值,很好。 不幸的是,即使做了這些,還是有個問題,清單8.4在模擬器上運行的很好,但當真正跑在iOS設備上時,我們發現在`-animationDidStop:finished:`委托方法調用之前,指針會迅速返回到原始值,這個清單8.3圖層顏色發生的情況一樣。 問題在于回調方法在動畫完成之前已經被調用了,但不能保證這發生在屬性動畫返回初始狀態之前。這同時也很好地說明了為什么要在真實的設備上測試動畫代碼,而不僅僅是模擬器。 我們可以用一個`fillMode`屬性來解決這個問題,下一章會詳細說明,這里知道在動畫之前設置它比在動畫結束之后更新屬性更加方便。 ## 關鍵幀動畫 `CABasicAnimation`揭示了大多數隱式動畫背后依賴的機制,這的確很有趣,但是顯式地給圖層添加`CABasicAnimation`相較于隱式動畫而言,只能說費力不討好。 `CAKeyframeAnimation`是另一種UIKit沒有暴露出來但功能強大的類。和`CABasicAnimation`類似,`CAKeyframeAnimation`同樣是`CAPropertyAnimation`的一個子類,它依然作用于單一的一個屬性,但是和`CABasicAnimation`不一樣的是,它不限制于設置一個起始和結束的值,而是可以根據一連串隨意的值來做動畫。 *關鍵幀*起源于傳動動畫,意思是指主導的動畫在顯著改變發生時重繪當前幀(也就是*關鍵*幀),每幀之間剩下的繪制(可以通過關鍵幀推算出)將由熟練的藝術家來完成。`CAKeyframeAnimation`也是同樣的道理:你提供了顯著的幀,然后Core Animation在每幀之間進行插入。 我們可以用之前使用顏色圖層的例子來演示,設置一個顏色的數組,然后通過關鍵幀動畫播放出來(清單8.5) 清單8.5 使用`CAKeyframeAnimation`應用一系列顏色的變化 ~~~ - (IBAction)changeColor { //create a keyframe animation CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"backgroundColor"; animation.duration = 2.0; animation.values = @[ (__bridge id)[UIColor blueColor].CGColor, (__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor greenColor].CGColor, (__bridge id)[UIColor blueColor].CGColor ]; //apply animation to layer [self.colorLayer addAnimation:animation forKey:nil]; } ~~~ 注意到序列中開始和結束的顏色都是藍色,這是因為`CAKeyframeAnimation`并不能自動把當前值作為第一幀(就像`CABasicAnimation`那樣把`fromValue`設為`nil`)。動畫會在開始的時候突然跳轉到第一幀的值,然后在動畫結束的時候突然恢復到原始的值。所以為了動畫的平滑特性,我們需要開始和結束的關鍵幀來匹配當前屬性的值。 當然可以創建一個結束和開始值不同的動畫,那樣的話就需要在動畫啟動之前手動更新屬性和最后一幀的值保持一致,就和之前討論的一樣。 我們用`duration`屬性把動畫時間從默認的0.25秒增加到2秒,以便于動畫做的不那么快。運行它,你會發現動畫通過顏色不斷循環,但效果看起來有些*奇怪*。原因在于動畫以一個*恒定的步調*在運行。當在每個動畫之間過渡的時候并沒有減速,這就產生了一個略微奇怪的效果,為了讓動畫看起來更自然,我們需要調整一下*緩沖*,第十章將會詳細說明。 提供一個數組的值就可以按照顏色變化做動畫,但一般來說用數組來描述動畫運動并不直觀。`CAKeyframeAnimation`有另一種方式去指定動畫,就是使用`CGPath`。`path`屬性可以用一種直觀的方式,使用Core Graphics函數定義運動序列來繪制動畫。 我們來用一個宇宙飛船沿著一個簡單曲線的實例演示一下。為了創建路徑,我們需要使用一個*三次貝塞爾曲線*,它是一種使用開始點,結束點和另外兩個*控制點*來定義形狀的曲線,可以通過使用一個基于C的Core Graphics繪圖指令來創建,不過用UIKit提供的`UIBezierPath`類會更簡單。 我們這次用`CAShapeLayer`來在屏幕上繪制曲線,盡管對動畫來說并不是必須的,但這會讓我們的動畫更加形象。繪制完`CGPath`之后,我們用它來創建一個`CAKeyframeAnimation`,然后用它來應用到我們的宇宙飛船。代碼見清單8.6,結果見圖8.1。 清單8.6 沿著一個貝塞爾曲線對圖層做動畫 ~~~ @interface ViewController () @property (nonatomic, weak) IBOutlet UIView *containerView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //create a path UIBezierPath *bezierPath = [[UIBezierPath alloc] init]; [bezierPath moveToPoint:CGPointMake(0, 150)]; [bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)]; //draw the path using a CAShapeLayer CAShapeLayer *pathLayer = [CAShapeLayer layer]; pathLayer.path = bezierPath.CGPath; pathLayer.fillColor = [UIColor clearColor].CGColor; pathLayer.strokeColor = [UIColor redColor].CGColor; pathLayer.lineWidth = 3.0f; [self.containerView.layer addSublayer:pathLayer]; //add the ship CALayer *shipLayer = [CALayer layer]; shipLayer.frame = CGRectMake(0, 0, 64, 64); shipLayer.position = CGPointMake(0, 150); shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage; [self.containerView.layer addSublayer:shipLayer]; //create the keyframe animation CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position"; animation.duration = 4.0; animation.path = bezierPath.CGPath; [shipLayer addAnimation:animation forKey:nil]; } @end ~~~ ![](https://box.kancloud.cn/2015-12-24_567bc20accab4.png) 圖8.1 沿著一個貝塞爾曲線移動的宇宙飛船圖片 運行示例,你會發現飛船的動畫有些不太真實,這是因為當它運動的時候永遠指向右邊,而不是指向曲線切線的方向。你可以調整它的`affineTransform`來對運動方向做動畫,但很可能和其它的動畫沖突。 幸運的是,蘋果預見到了這點,并且給`CAKeyFrameAnimation`添加了一個`rotationMode`的屬性。設置它為常量`kCAAnimationRotateAuto`(清單8.7),圖層將會根據曲線的切線自動旋轉(圖8.2)。 清單8.7 通過`rotationMode`自動對齊圖層到曲線 ~~~ - (void)viewDidLoad { [super viewDidLoad]; //create a path ... //create the keyframe animation CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position"; animation.duration = 4.0; animation.path = bezierPath.CGPath; animation.rotationMode = kCAAnimationRotateAuto; [shipLayer addAnimation:animation forKey:nil]; } ~~~ ![](https://box.kancloud.cn/2015-12-24_567bc20accab4.png) 圖8.2 匹配曲線切線方向的飛船圖層 ## 虛擬屬性 之前提到過屬性動畫實際上是針對于關鍵*路徑*而不是一個鍵,這就意味著可以對子屬性甚至是*虛擬屬性*做動畫。但是*虛擬*屬性到底是什么呢? 考慮一個旋轉的動畫:如果想要對一個物體做旋轉的動畫,那就需要作用于`transform`屬性,因為`CALayer`沒有顯式提供角度或者方向之類的屬性,代碼如清單8.8所示 清單8.8 用`transform`屬性對圖層做動畫 ~~~ @interface ViewController () @property (nonatomic, weak) IBOutlet UIView *containerView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //add the ship CALayer *shipLayer = [CALayer layer]; shipLayer.frame = CGRectMake(0, 0, 128, 128); shipLayer.position = CGPointMake(150, 150); shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage; [self.containerView.layer addSublayer:shipLayer]; //animate the ship rotation CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"transform"; animation.duration = 2.0; animation.toValue = [NSValue valueWithCATransform3D: CATransform3DMakeRotation(M_PI, 0, 0, 1)]; [shipLayer addAnimation:animation forKey:nil]; } @end ~~~ 這么做是可行的,但看起來更因為是運氣而不是設計的原因,如果我們把旋轉的值從`M_PI`(180度)調整到`2 * M_PI`(360度),然后運行程序,會發現這時候飛船完全不動了。這是因為這里的矩陣做了一次360度的旋轉,和做了0度是一樣的,所以最后的值根本沒變。 現在繼續使用`M_PI`,但這次用`byValue`而不是`toValue`。也許你會認為這和設置`toValue`結果一樣,因為0 + 90度 == 90度,但實際上飛船的圖片變大了,并沒有做任何旋轉,這是因為變換矩陣不能像角度值那樣疊加。 那么如果需要獨立于角度之外單獨對平移或者縮放做動畫呢?由于都需要我們來修改`transform`屬性,實時地重新計算每個時間點的每個變換效果,然后根據這些創建一個復雜的關鍵幀動畫,這一切都是為了對圖層的一個獨立做一個簡單的動畫。 幸運的是,有一個更好的解決方案:為了旋轉圖層,我們可以對`transform.rotation`關鍵路徑應用動畫,而不是`transform`本身(清單8.9)。 清單8.9 對虛擬的`transform.rotation`屬性做動畫 ~~~ @interface ViewController () @property (nonatomic, weak) IBOutlet UIView *containerView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //add the ship CALayer *shipLayer = [CALayer layer]; shipLayer.frame = CGRectMake(0, 0, 128, 128); shipLayer.position = CGPointMake(150, 150); shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage; [self.containerView.layer addSublayer:shipLayer]; //animate the ship rotation CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"transform.rotation"; animation.duration = 2.0; animation.byValue = @(M_PI * 2); [shipLayer addAnimation:animation forKey:nil]; } @end ~~~ 結果運行的特別好,用`transform.rotation`而不是`transform`做動畫的好處如下: * 我們可以不通過關鍵幀一步旋轉多于180度的動畫。 * 可以用相對值而不是絕對值旋轉(設置`byValue`而不是`toValue`)。 * 可以不用創建`CATransform3D`,而是使用一個簡單的數值來指定角度。 * 不會和`transform.position`或者`transform.scale`沖突(同樣是使用關鍵路徑來做獨立的動畫屬性)。 `transform.rotation`屬性有一個奇怪的問題是它其實*并不存在*。這是因為`CATransform3D`并不是一個對象,它實際上是一個結構體,也沒有符合KVC相關屬性,`transform.rotation`實際上是一個`CALayer`用于處理動畫變換的*虛擬*屬性。 你不可以直接設置`transform.rotation`或者`transform.scale`,他們不能被直接使用。當你對他們做動畫時,Core Animation自動地根據通過`CAValueFunction`來計算的值來更新`transform`屬性。 `CAValueFunction`用于把我們賦給虛擬的`transform.rotation`簡單浮點值轉換成真正的用于擺放圖層的`CATransform3D`矩陣值。你可以通過設置`CAPropertyAnimation`的`valueFunction`屬性來改變,于是你設置的函數將會覆蓋默認的函數。 `CAValueFunction`看起來似乎是對那些不能簡單相加的屬性(例如變換矩陣)做動畫的非常有用的機制,但由于`CAValueFunction`的實現細節是私有的,所以目前不能通過繼承它來自定義。你可以通過使用蘋果目前已近提供的常量(目前都是和變換矩陣的虛擬屬性相關,所以沒太多使用場景了,因為這些屬性都有了默認的實現方式)。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看