圖層幾何學 -- iOS Core Animation 系列二

《圖層樹和寄宿圖 -- iOS Core Animation 系列一》介紹了圖層的基礎知識和一些屬性方法。這篇主要內容是學習下圖層在父圖層上怎麼控制位置和尺寸的。segmentfault

1.佈局

首先看一張例圖:佈局

clipboard.png

對於圖上的frameboundscenterpostion的概念我就不贅述了。若是有不明白的自行搜索下了解一下。post

frame表明了圖層的外部座標(也就是在父圖層上佔據的空間), bounds是內部座標( {0, 0}一般是圖層的左上角), centerposition都表明了相對於父圖層 anchorPoint所在的位置

視圖的frameboundscenter屬性僅僅是存取方法,當操縱視圖的frame時,其實是在改變視圖對應的CALayerframe, 不能獨立於圖層以外改變視圖的frame.學習

若是對圖層作了變換,好比旋轉縮放等。frame的值實際指的是圖層旋轉以後整個軸對齊的矩形區域。此時frame的寬高可能和bounds的寬高不一致:測試

clipboard.png


2.錨點

默認來講,anchorPoint位於圖層的中點。這個屬性沒有被UIView直接暴露出來。可是圖層的anchorPoint能夠被移動。咱們能夠把anchorPoint置於圖層frame的左上角。將會出現下圖右側的狀況:動畫

clipboard.png

注意上圖,改變 anchorPointposition的值並沒變。

和系列一中提到的contentsRect相似,anchorPoint單位座標來表示(默認狀況是{0.5, 0.5})。能夠經過指定x和y值小於0或者大於1,使它放置在圖層範圍以外。atom

2.1 示例

爲了學習這個anchorPoint屬性,下面建立一個鬧鐘的示例demo。
資源文件我是從原文上截圖下來的spa

clipboard.png
建立4個UIImageView並設置好約束(都是居中顯示)。指針

clipboard.png
咱們用NSTimer來更新鬧鐘,使用視圖的transform屬性來旋轉鐘錶。
代碼以下:code

@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];
  
  self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];

  [self tick];
}
- (void)tick
{
  //獲取對應的hours mins seconds
  NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
  NSUInteger units = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
  NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];
  
  CGFloat hoursAngle = (components.hour / 12.0) * M_PI * 2.0;
  CGFloat minsAngle = (components.minute / 60.0) * M_PI * 2.0;
  CGFloat secsAngle = (components.second / 60.0) * M_PI * 2.0;
  //旋轉對應的視圖
  self.hourHand.transform = CGAffineTransformMakeRotation(hoursAngle);
  self.minuteHand.transform = CGAffineTransformMakeRotation(minsAngle);
  self.secondHand.transform = CGAffineTransformMakeRotation(secsAngle);
}

運行項目以下圖:

clipboard.png

除了指針圖片的位置,其餘的都正常。
可能這時候咱們最早想到的方法,是調整對應圖片的位置來解決。可是這樣的話,你能夠試試,並不能解決問題。不用賣關子了。這時候就是要用到anchorPoint的時候。處理代碼以下:

// 在viewdidload中添加
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);

運行完美。


3. 座標系

衆所周知,一個圖層的position依賴於父圖層的bounds,若是父圖層移動,全部子圖層也會跟着移動。
CALayer也給咱們提供了一些獲取一個圖層的絕對位置的方法,或者相對於另外一圖層的位置(而不是它當前父圖層的位置):

- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer;
- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;
常規來講,一個圖層的 postion位於父圖層的左上角,但在 Mac OS 中,一般位於左下角。

3.1 z座標軸

UIView的二維座標不一樣,CALayer存在於一個三維空間中,它還提供了zPostionanchorPointz屬性。
zPosition屬性大多數不經常使用,除了三維動畫以外,它最實用的功能是能夠改變圖層的顯示順序。

3.2 zPosition演示代碼

咱們演示下改變zPosition會怎麼改變視圖的顯示順序。
首先我在SB中設置兩個視圖,以下圖:

clipboard.png

若是咱們不作任何操做,運行後,兩個視圖顯示的順序就是咱們如今設置的這樣。可是假如咱們對yellowView設置zPosition,哪怕很小的值,都會發現顯示的順序反了。

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *cyanView;
@property (weak, nonatomic) IBOutlet UIView *yellowView;

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  self.yellowView.layer.zPosition = 1.f;
}

如今的顯示效果以下:

clipboard.png

雖然說圖層基本沒有厚度,可是咱們也儘可能不要設置 zPosition = 0.01f之類的。由於浮點類型的四捨五入可能致使難以察覺的麻煩。

4. Hit Testing

雖然說CALayer不關心響應鏈事件,可是它提供了一些方法讓咱們處理事件-containsPoint:-hitTest:

4.1 -containsPoint:

-containsPoint:接受一個在本圖層座標系下的CGPoint,若是這個點在圖層frame範圍內就返回YES.咱們可使用這個方法判斷是哪一個圖層被觸摸了。

4.1.1 containsPoint 示例

代碼以下:

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *layerView;
@property (nonatomic, strong) CALayer *blueLayer;

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  
  self.blueLayer = [CALayer layer];
  self.blueLayer.frame = CGRectMake(20.f, 20.f, 100.f, 100.f);
  self.blueLayer.backgroundColor = [UIColor blueColor].CGColor;
  
  [self.layerView.layer addSublayer:self.blueLayer];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  // 獲取觸摸點
  CGPoint point = [[touches anyObject] locationInView:self.view];
  // 轉換觸摸點在layerView的圖層的位置
  point = [self.layerView.layer convertPoint:point fromLayer:self.view.layer];
  // 判斷是否包含在layerview裏面
  if ([self.layerView.layer containsPoint:point]) {
    point = [self.blueLayer convertPoint:point fromLayer:self.layerView.layer];
    if ([self.blueLayer containsPoint:point]) {
      NSLog(@"點擊藍色圖層");
    } else {
      NSLog(@"點擊了白色圖層");
    }
  }
}

運行點擊能夠在控制檯看到NSLog的輸出信息。

4.2. -hitTest:

-hitTest:方法一樣接受一個CGPoint參數,可是返回的是圖層自己,而不是BOOL類型。這使咱們不用像-containsPoint:同樣每一個子圖層去測試點擊的座標。若是這個點是在最外面的圖層,則返回nil

4.2.1 hitTest示例

把上面-containsPoint:示例的代碼下面的部分修改一下便可:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  // 獲取點擊點
  CGPoint point = [[touches anyObject] locationInView:self.view];
  // 獲取這個點所在的圖層
  CALayer *layer = [self.layerView.layer hitTest:point];
  if (layer == self.blueLayer) {
    NSLog(@"點擊藍色圖層");
  } else if (layer == self.layerView.layer) {
    NSLog(@"點擊了白色圖層");
  }
}
嘗試修改 self.layerViewzPosition,會有不一樣的結果。有興趣的能夠本身測試一下。

-- 系列二完 --

相關文章
相關標籤/搜索