Core Animation 第三章 圖層幾何

往期回顧:
序章
第一章
第二章
項目中使用的代碼

布局


UIView中比較重要的布局屬性為frame, bounds, center缚陷。CALayer中對應(yīng)的成為frame, bounds, position怪瓶。
其中frame表示的是圖層的外部坐標(biāo),即圖層在父視圖上占據(jù)的空間疲迂。center(position)代表的是相對于父視圖anchorPoint所在的位置放典,anchorPoint會在后面講到晒衩,默認(rèn)的話就是視圖的中心點涌攻。

View和Layer的坐標(biāo)關(guān)系.png

UIViewframe, bounds, center僅僅是存取方法,實際訪問和改變的是位于試圖下方的layer诀蓉。并且 frame 是通過 bounds, positiontransform 所計算出來的栗竖。

住當(dāng)對圖層做變換的時候,比如旋轉(zhuǎn)或者縮放,實際上代表了覆蓋在圖層旋轉(zhuǎn)之后的整個軸對齊的矩形區(qū)域,也就是說的寬高可能和 bounds 的寬高不再一致了。

旋轉(zhuǎn)后視圖的frame與bounds.png

錨點(anchorPoint)


前面說過 center(position)實際是anchorPoint相對于父視圖的位置渠啤。也就是說可以通過修改anchorPoint來控制frame的位置狐肢。

修改anchorPoint后的frame對比.png

anchorPoint使用的是單位坐標(biāo),圖層左上角為{0, 0}沥曹,右下角為{1, 1}份名,默認(rèn)值為{0.5, 0.5}。當(dāng)然妓美,anchorPoint也可以位于圖層之外僵腺,也就是取值可以小于0或者大于1;

下面我們來做一個時鐘:


我們可以啟用一個NSTimer壶栋,然后每隔一秒獲取一次本地時間辰如,并且對應(yīng)的使用transform來旋轉(zhuǎn)指針(transform屬性會在后面講到)。

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *hourImageView;
@property (weak, nonatomic) IBOutlet UIImageView *minuteImageView;
@property (weak, nonatomic) IBOutlet UIImageView *secondImageView;
@property (weak, nonatomic) NSTimer *timer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(timeTick) userInfo:nil repeats:YES];
    //初始化指針的位置
    [self timeTick];
}

- (void)timeTick {
    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierChinese];
    NSUInteger units = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
    NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];
    CGFloat hourAngle = (components.hour / 12.0) * M_PI * 2.0;
    CGFloat minuteAngle = (components.minute / 60.0) * M_PI * 2.0;
    CGFloat secondAngle = (components.second / 60.0) * M_PI * 2.0;
    self.hourImageView.transform = CGAffineTransformMakeRotation(hourAngle);
    self.minuteImageView.transform = CGAffineTransformMakeRotation(minuteAngle);
    self.secondImageView.transform = CGAffineTransformMakeRotation(secondAngle);
}
@end
沒有修改anchorPoint的時鐘.png

可以看到旋轉(zhuǎn)的支點以及指針的位置都十分奇怪贵试,因為我們圖層默認(rèn)的anchorPoint是{0.5, 0.5}琉兜,但是現(xiàn)實中時鐘的指針卻很少有以中心為支點旋轉(zhuǎn)的凯正,所以我們可以修改一下anchorPoint來調(diào)整我們指針的位置和旋轉(zhuǎn)的支點。在viewDidLoad中添加如下代碼

    self.hourImageView.layer.anchorPoint = CGPointMake(0.5, 0.7);
    self.minuteImageView.layer.anchorPoint = CGPointMake(0.5, 0.7);
    self.secondImageView.layer.anchorPoint = CGPointMake(0.5, 0.7);
調(diào)整anchorPoint后的時鐘.png

坐標(biāo)系


和視圖一樣豌蟋,圖層在父視圖中也是按照層級關(guān)系放置的廊散,如果父圖層移動了,那么所有的子視圖也是跟著一起移動梧疲。這樣就可以將若干圖層作為一個整體一起移動允睹,但是有時候你需要知道一個圖層的絕對位置,或者是相對于另一個圖層的位置,而不是它當(dāng)前父圖層的位置,CALayer提供了不同坐標(biāo)系之間相互轉(zhuǎn)換的方法:

- (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;

翻轉(zhuǎn)的幾何結(jié)構(gòu)

我們都知道圖層的原點是左上角幌氮,但是在macOS中視圖原點卻是左下角缭受,Core Animation可以通過 geometryFlipped屬性來適配這兩種情況,他決定了一個圖層以及其子圖層是否垂直翻轉(zhuǎn)浩销。

Z坐標(biāo)軸

CALayerUIView的一大主要區(qū)別就是CALayer是存在于三維空間中的贯涎,除了positionanchorPoint之外,CALayer還有zPositionzAnchorPoint這兩個屬性慢洋,二者都是在Z軸上描述圖層位置的浮點類型。這里并沒有引申出z軸方向上的bounds等屬性陆盘,因為圖層并沒有厚度普筹。寫一個簡單的例子,下面有紅色和藍色兩個圖層隘马,紅色圖層在藍色圖層上方太防,我們稍微改變一下藍色圖層的zPosition,可以看到藍色圖層就會跑到紅色圖層上方

@interface ZAxleController ()
@property (weak, nonatomic) CALayer *blueLayer;
@property (weak, nonatomic) CALayer *redLayer;
@end

@implementation ZAxleController

- (void)viewDidLoad {
    [super viewDidLoad];
    CALayer *blueLayer = [CALayer layer];
    blueLayer.frame = CGRectMake(100, 50, 100, 100);
    blueLayer.backgroundColor = [UIColor colorWithRed:0.00 green:0.55 blue:1.00 alpha:1.00].CGColor;
    [self.view.layer addSublayer:blueLayer];
    self.blueLayer = blueLayer;
    
    CALayer *redLayer = [CALayer layer];
    redLayer.frame = CGRectMake(150, 100, 100, 100);
    redLayer.backgroundColor = [UIColor colorWithRed:1.00 green:0.45 blue:0.43 alpha:1.00].CGColor;
    [self.view.layer addSublayer:redLayer];
    self.redLayer = redLayer;
}
紅色圖層在藍色圖層上方.png

更改一下藍色圖層的zPosition(這里改變了0.1酸员,理論上哪怕只是改變0.0000001效果也是一樣的蜒车,但是由于編譯器的原因,精度過小可能會出現(xiàn)問題幔嗦,所以最好還是不要寫的太心鹄ⅰ)。

- (IBAction)zChangeClick:(id)sender {
    self.blueLayer.zPosition += 0.1;
}
改變了藍色圖層的zPosition.png

Hit Testing


CALayer 并不關(guān)心任何響應(yīng)鏈?zhǔn)录?所以不能直接處理觸摸事件或者手勢邀泉。但是它有一系列的方法幫你處理事件:-containsPoint:-hitTest: 嬉挡。
-containsPoint:傳入一個點,如果這個點在當(dāng)前圖層的frame范圍內(nèi)汇恤,就返回YES庞钢,比如下面的例子,我們在-touchesBegan: withEvent:中判斷點擊是否位于藍色圖層內(nèi)部因谎。

@interface HitTestingController ()
@property (weak, nonatomic) IBOutlet UIView *layerView;
@property (weak, nonatomic) CALayer *blueLayer;
@end

@implementation HitTestingController

- (void)viewDidLoad {
    [super viewDidLoad];
    CALayer *blueLayer = [CALayer layer];
    blueLayer.frame = CGRectMake(50, 50, 100, 100);
    blueLayer.backgroundColor = [UIColor colorWithRed:0.00 green:0.55 blue:1.00 alpha:1.00].CGColor;
    [self.layerView.layer addSublayer:blueLayer];
    self.blueLayer = blueLayer;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    CGPoint point = [touches.anyObject locationInView:self.view];
//    轉(zhuǎn)換為layerView中的坐標(biāo)
    point = [self.blueLayer convertPoint:point fromLayer:self.view.layer];
    NSString *message = @"";
    if ([self.blueLayer containsPoint:point]) {
        message = @"點擊位于藍色視圖中";
    } else {
        message = @"點擊位于藍色視圖外";
    }
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:alertController animated:YES completion:nil];
    
}
@end
藍色圖層被點擊.png

-hitTest:同樣接收一個CGPoint類型的參數(shù)基括,返回圖層本身或者包含這個節(jié)點的子圖層,如果這個點在圖層之外則返回nil财岔。所以上面的代碼也可以寫成這個樣子风皿。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //-hitTest:
    CGPoint point = [touches.anyObject locationInView:self.view];
    CALayer *layer = [self.layerView.layer hitTest:point];
    NSString *message = @"";
    if (layer == self.blueLayer) {
        message = @"點擊位于藍色視圖中";
    } else {
        message = @"點擊位于藍色視圖外";
    }
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:alertController animated:YES completion:nil];
}
  • 需要注意的是雖然我們可以通過zPosition改變我們看到的圖層的位置饭冬,但實際上響應(yīng)鏈的順序并不會改變。

自動布局

iOS6中蘋果引入了自動布局揪阶,在使用UIView的時候你可以使用NSLayoutConstraint的API使用自動布局昌抠,但如果想要隨意控制CALayer的布局就需要手動操作。最簡單的方法就是使用CALayerDelegate的方法:

- (void)layoutSublayersOfLayer:(CALayer *)layer;

當(dāng)圖層的bounds發(fā)生變化或者調(diào)用-setNeedsLayout的時候鲁僚,這個方法就會被執(zhí)行炊苫,你可以在這里手動的調(diào)整子圖層的大小與位置。

總結(jié)


本章涉及了CALayer 的幾何結(jié)構(gòu),包括它的 frame ,positionbounds ,介紹了三維空間內(nèi)圖層的概念,以及如何在獨立的圖層內(nèi)響應(yīng)事件,最后簡單說明了在iOS平臺,Core Animation對自動調(diào)整和自動布局支持的缺乏冰沙。在第四章“視覺效果”當(dāng)中,我們接著介紹一些圖層外表的特性侨艾。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拓挥,隨后出現(xiàn)的幾起案子唠梨,更是在濱河造成了極大的恐慌,老刑警劉巖侥啤,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件当叭,死亡現(xiàn)場離奇詭異,居然都是意外死亡盖灸,警方通過查閱死者的電腦和手機蚁鳖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赁炎,“玉大人醉箕,你說我怎么就攤上這事♂愕妫” “怎么了讥裤?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長姻报。 經(jīng)常有香客問我己英,道長,這世上最難降的妖魔是什么逗抑? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任剧辐,我火速辦了婚禮,結(jié)果婚禮上邮府,老公的妹妹穿的比我還像新娘荧关。我一直安慰自己,他們只是感情好褂傀,可當(dāng)我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布忍啤。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪同波。 梳的紋絲不亂的頭發(fā)上鳄梅,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機與錄音未檩,去河邊找鬼戴尸。 笑死,一個胖子當(dāng)著我的面吹牛冤狡,可吹牛的內(nèi)容都是我干的孙蒙。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼悲雳,長吁一口氣:“原來是場噩夢啊……” “哼挎峦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起合瓢,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤坦胶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后晴楔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顿苇,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年滥崩,在試婚紗的時候發(fā)現(xiàn)自己被綠了岖圈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡钙皮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出顽决,到底是詐尸還是另有隱情短条,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布才菠,位于F島的核電站茸时,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏赋访。R本人自食惡果不足惜可都,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蚓耽。 院中可真熱鬧渠牲,春花似錦、人聲如沸步悠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鼎兽。三九已至答姥,卻和暖如春铣除,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鹦付。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工尚粘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人敲长。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓郎嫁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親潘明。 傳聞我的和親對象是個殘疾皇子行剂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容