《圖層樹和寄宿圖 -- iOS Core Animation 系列一》介紹了圖層的基礎知識和一些屬性方法。這篇主要內容是學習下圖層在父圖層上怎麼控制位置和尺寸的。segmentfault
首先看一張例圖:佈局
對於圖上的frame
、bounds
、center
、postion
的概念我就不贅述了。若是有不明白的自行搜索下了解一下。post
frame
表明了圖層的外部座標(也就是在父圖層上佔據的空間),bounds
是內部座標({0, 0}
一般是圖層的左上角),center
和position
都表明了相對於父圖層anchorPoint
所在的位置
視圖的frame
、bounds
、center
屬性僅僅是存取方法,當操縱視圖的frame
時,其實是在改變視圖對應的CALayer
的frame
, 不能獨立於圖層以外改變視圖的frame
.學習
若是對圖層作了變換,好比旋轉縮放等。frame
的值實際指的是圖層旋轉以後整個軸對齊的矩形區域。此時frame
的寬高可能和bounds
的寬高不一致:測試
默認來講,anchorPoint
位於圖層的中點。這個屬性沒有被UIView
直接暴露出來。可是圖層的anchorPoint
能夠被移動。咱們能夠把anchorPoint
置於圖層frame
的左上角。將會出現下圖右側的狀況:動畫
注意上圖,改變anchorPoint
後position
的值並沒變。
和系列一中提到的contentsRect
相似,anchorPoint
用單位座標來表示(默認狀況是{0.5, 0.5}
)。能夠經過指定x和y值小於0或者大於1,使它放置在圖層範圍以外。atom
爲了學習這個anchorPoint
屬性,下面建立一個鬧鐘的示例demo。
資源文件我是從原文上截圖下來的spa
建立4個UIImageView
並設置好約束(都是居中顯示)。指針
咱們用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); }
運行項目以下圖:
除了指針圖片的位置,其餘的都正常。
可能這時候咱們最早想到的方法,是調整對應圖片的位置來解決。可是這樣的話,你能夠試試,並不能解決問題。不用賣關子了。這時候就是要用到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);
運行完美。
衆所周知,一個圖層的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 中,一般位於左下角。
和UIView
的二維座標不一樣,CALayer
存在於一個三維空間中,它還提供了zPostion
和anchorPointz
屬性。zPosition
屬性大多數不經常使用,除了三維動畫以外,它最實用的功能是能夠改變圖層的顯示順序。
咱們演示下改變zPosition
會怎麼改變視圖的顯示順序。
首先我在SB中設置兩個視圖,以下圖:
若是咱們不作任何操做,運行後,兩個視圖顯示的順序就是咱們如今設置的這樣。可是假如咱們對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; }
如今的顯示效果以下:
雖然說圖層基本沒有厚度,可是咱們也儘可能不要設置
zPosition = 0.01f
之類的。由於浮點類型的四捨五入可能致使難以察覺的麻煩。
雖然說CALayer
不關心響應鏈事件,可是它提供了一些方法讓咱們處理事件-containsPoint:
和-hitTest:
。
-containsPoint:
接受一個在本圖層座標系下的CGPoint
,若是這個點在圖層frame
範圍內就返回YES
.咱們可使用這個方法判斷是哪一個圖層被觸摸了。
代碼以下:
@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
的輸出信息。
-hitTest:
方法一樣接受一個CGPoint
參數,可是返回的是圖層自己,而不是BOOL
類型。這使咱們不用像-containsPoint:
同樣每一個子圖層去測試點擊的座標。若是這個點是在最外面的圖層,則返回nil
。
把上面-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.layerView
的zPosition
,會有不一樣的結果。有興趣的能夠本身測試一下。