iOS Core Animation
Bài đăng này đã không được cập nhật trong 3 năm
iOS Core Animation
1. Giới thiệu
Core Animation là engine tạo ra các nội dung trực quan trên màn hình, và nó có nhiệm vụ làm điều đó càng nhanh càng tốt. Các nội dung được chia thành lớp (layer) riêng biệt và được lưu trữ trong một hệ thống được gọi là cây lớp (layer tree). Cây này hình thành nền tảng cho tất cả các đối tượng UIKit, và cho tất cả mọi thứ mà bạn nhìn thấy trên màn hình trong một ứng dụng iOS.
Trong iOS, tất cả các lớp giao diện đều được kế thừa từ lớp cơ sở chung UIView
. UIView
xử lý các sự kiện chạm và hỗ trợ các hàm vẽ dựa trên Core Graphics, các phép biến hình (như xoay, thay đổi tỷ lệ), và các ảnh động (animation) đơn giản như trượt hay mờ dần.
UIView
không xử lý hầu hết các công việc đó. Dựng hình, bố trí, và hình ảnh động đều được quản lý bởi một lớp Core Animation gọi CALayer
.
CALayer
(lớp Core Animation) khá tương đồng với UIView
. Lớp, giống như view, là các đối tượng hình chữ nhật được sắp xếp thành một cây phân cấp, có thể chứa các nội dung như hình ảnh, văn bản, màu sắc và quản lý vị trí của các lớp con (sublayer). Chúng có các hàm và thuộc tính để thực hiện các phép biến đổi và xử lý ảnh động. Điểm khác biệt lớn nhất với UIView
là CALayer
không xử lý các thao tác người dùng. Tuy nhiên, CALayer
lại có khả năng làm những việc mà UIView
không thể như:
- Đổ bóng, bo tròn, màu viền
- Biến đổi 3D và đặt vị trí
- Biên không phải hình chữ nhật
- Các nội dung trong suốt
- Các chuyển động nhiều bước và phi tuyến tính
2. The Backing Image
2.1. Nội dung
CALayer
có thuộc tính contents
, có kiểu là id
, tức là có thể nhận giá trị là bất kỳ đối tượng object nào, tuy nhiên nếu đối tượng không phải là CGImage
thì lớp sẽ hiển thị trống.
layer.contents = (__bridge id)image.CGImage;
Để tùy chỉnh việc hiển thị hình ảnh ta có thể thay đổi thuộc tính contentsGravity
của lớp:
kCAGravityCenter
kCAGravityTop
kCAGravityBottom
kCAGravityLeft
kCAGravityRight
kCAGravityTopLeft
kCAGravityTopRight
kCAGravityBottomLeft
kCAGravityBottomRight
kCAGravityResize
kCAGravityResizeAspect
kCAGravityResizeAspectFill
2.2. Vẽ
Ngoài việc hiển thị nội dung với CGImage
, chúng ta còn có thể vẽ trực tiếp, sử dụng Core Graphics. Ta thực hiện việc này thông qua CALayerDelegate
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//create sublayer
CALayer *blueLayer = [CALayer layer];
blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f); blueLayer.backgroundColor = [UIColor blueColor].CGColor;
blueLayer.delegate = self;
blueLayer.contentsScale = [UIScreen mainScreen].scale; //add layer
[self.layerView.layer addSublayer:blueLayer];
// force redraw layer
[blueLayer display];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
//draw a thick red circle
CGContextSetLineWidth(ctx, 10.0f); CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor); CGContextStrokeEllipseInRect(ctx, layer.bounds);
}
@end
3. Layer Geometry
3.1. Layout
CALayer
sử dụng 3 thuộc tính để xác định layout là frame
, bounds
và position
.
3.2. Hệ thống tọa độ
Các lớp trong cây có vị trí liên hệ với bao của lớp cha. Nếu lớp cha di chuyển thì các lớp con của nó cũng di chuyển theo.
CALayer
cung cấp 1 số hàm cho việc chuyển đổi tọa độ giữa các lớp
- (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;
3.3. Hit Testing
Do CALayer
không thể xử lý các thao tác người dùng, nên nó cung cấp 1 số hàm cho phép lập trình viên có thể tự tạo ra các hàm xử lý thao tác là -containsPoint:
và -hitTest:
Hàm containsPoint
nhận tham số điểm có kiểu là CGPoint
, trả về YES
nếu điểm đó nằm trong frame của layer.
BOOL contained = [layer containsPoint:point];
Hàm hitTest
cũng nhận 1 tham số có kiểu CGPoint
, nhưng thay vì trả về kiểu BOOL
, nó trả về hoặc là bản thân layer, hoặc là sublayer sâu nhất có chứa điểm đó.
CALayer *layer = [self.layerView.layer hitTest:point];
4. Visual Effects
4.1. Bo góc
CALayer
có thuộc tính cornerRadius
quy định góc bo của lớp, mặc định thuộc tính này bằng 0 tương ứng với không bo góc.
layer.cornerRadius = 10.0f;
4.2. Đường viền
Để thay đổi đường viền của lớp, CALayer
cung cấp 2 thuộc tính borderWidth
và borderColor
có kiểu là CGColorRef
layer.borderWidth = 5.0f;
layer.borderColor = [UIColor greenColor].CGColor;
4.3. Đổ bóng
Một tính năng phổ biến trong iOS đó là đổ bóng (drop shadow). Đổ bóng được tạo ra bên dưới view nhằm tạo hiệu ứng chiều sâu.
Đổ bóng có thể được tạo ra bên dưới 1 layer bất kỳ bằng cách thiết lập thuộc tính shadowOpacity
nhận một giá trị trong khoảng 0.0 đến 1.0
Ta có thể tinh chỉnh đổ bóng qua các thuộc tính shadowColor
, shadowOffset
, và shadowRadius
.
layer.shadowColor = [[UIColor greenColor] CGColor];
layer.shadowOffset = CGSizeMake(5.0, 5.0);
layer.shadowOpacity = 1.0;
layer.shadowRadius = 0.0;
4.4. Trong suốt
Trong khi UIView
có thuộc tính alpha
dùng để điều chỉnh độ trong suốt, CALayer
có thuộc tính tương tự là opacity
. Cả 2 thuộc tính này đều áp dụng cho các đối tượng con, nên nếu bạn áp dụng cho layer thì nó sẽ áp dụng cho các sublayer.
Để áp dụng trong suốt theo nhóm (group opacity) cho layer với nhiều sublayer, ta có thể cấu hình UIViewGroupOpacity
bằng YES
trong file Info.plist
, tuy nhiên việc này sẽ áp dụng cho toàn bộ ứng dụng và sẽ ảnh hưởng tới hiệu năng ứng dụng.
Một cách khác để áp dụng group opacity đó là dùng thuộc tính shouldRasterize
của CALayer
. Khi đặt là YES
, layer và các sublayer sẽ được gộp thành một ảnh phẳng duy nhất trước khi áp dụng tính trong suốt. Để tránh hình ảnh hiển thị không đẹp trên màn hình Retina, ta cần cấu hình thêm thuộc tính rasterizationScale
của CALayer
cho khớp với màn hình.
layer.shouldRasterize = YES;
layer.rasterizationScale = [UIScreen mainScreen].scale;
5. Transforms
5.1. CGAffineTransform
Thuộc tính transform của UIView
, có kiểu là CGAffineTransform
cho phép xoay đối tượng, phóng to thu nhỏ và dịch chuyển. CGAffineTransform
là một ma trận 2 cột, 3 dòng có thể nhân với một vector 2D (CGPoint
) để thay đổi giá trị của nó.
Phép nhân được thực hiện bằng cách nhân từng giá trị trong vector CGPoint
mới. với giá trị trong ma trận CGAffineTransform
, kết quả cuối cùng sẽ tạo ra một CGPoint
mới.
Core Graphics cung cấp các hàm cho phép biến đổi đối tượng:
CGAffineTransformMakeRotation(CGFloat angle)
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)
Đoạn code dưới đây cho phép xoay đối tượng 45 độ:
//rotate the layer 45 degrees
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4);
self.layerView.layer.affineTransform = transform;
M_PI_4
là một hằng số. Các hàm biến đổi trong iOS sử dụng radian thay vì dùng độ cho tất cả các đối tượng góc.
5.2. Kết hợp các phép biến đổi
//create a new transform
CGAffineTransform transform = CGAffineTransformIdentity;
//scale by 50%
transform = CGAffineTransformScale(transform, 0.5, 0.5);
//rotate by 30 degrees
transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0);
//translate by 200 points
transform = CGAffineTransformTranslate(transform, 200, 0);
//apply transform to layer
self.layerView.layer.affineTransform = transform;
6. Các lớp đặc biệt
6.1. CAShapeLayer
CAShapeLayer
là một lớp con hiển thị các đồ họa vector thay cho ảnh bitmap. Ngoài các thuộc tính như là màu sắc, độ dầy của các đường, ta cần thiết lập hình dạng (shape) cho layer, sử dụng CGPath
.
Ưu điểm:
- Nhanh:
CAShapeLayer
sử dụng phần cứng để vẽ nên nó nhanh hơn nhiều lần việc dùng Core Graphics để vẽ ảnh. - Tối ưu bộ nhớ:
CAShapeLayer
không sử dụng ảnh làm nền nên bộ nhớ sử dụng không phụ thuộc vào kích thước layer. - Không bị giới hạn biên:
CAShapeLayer
có thể vẽ ra ngoài biên (bound) của layer. - Có thể phóng to layer thoải mái mà không sợ "pixelation" (nhìn rõ từng pixel của ảnh phóng to)
6.2. CATextLayer
Để hiển thị nội dung văn bản, Core Animation cung cấp 1 lớp con của CALayer
là CATextLayer
, cho phép thực hiện phần lớn các tính năng của UILabel
cũng như bổ sung thêm 1 số tính năng khác.
CATextLayer
cũng cho phép hiển thị nhanh hơn UILabel
. Lý do là từ iOS6 về trước, UIalbel
dùng WebKit cho việc vẽ ký tự, việc này sẽ khiến hiệu năng bị suy giảm trầm trọng khi phải vẽ 1 lượng lớn ký tự. Trong khi dó CATextLayer
dùng Core Text nhanh hơn nhiều.
//create a text layer
CATextLayer *textLayer = [CATextLayer layer];
textLayer.frame = self.labelView.bounds;
[self.labelView.layer addSublayer:textLayer];
//set text attributes
textLayer.foregroundColor = [UIColor blackColor].CGColor;
textLayer.alignmentMode = kCAAlignmentJustified;
textLayer.wrapped = YES;
//choose a font
UIFont *font = [UIFont systemFontOfSize:15];
//set layer font
CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFontRef fontRef = CGFontCreateWithFontName(fontName);
textLayer.font = fontRef;
textLayer.fontSize = font.pointSize;
CGFontRelease(fontRef);
//choose some text
NSString *text = @"Lorem ipsum dolor sit amet";
//retina display
textLayer.contentsScale = [UIScreen mainScreen].scale;
//set layer text
textLayer.string = text;
6.3. CAGradientLayer
CAGradientLayer
dùng để tạo ra hiệu ứng chuyển màu giữa 2 hay nhiều màu. Ví dụ dưới đây cho phép tạo 1 gradient từ màu đỏ sang màu xanh dương.
//create gradient layer and add it to our container view
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.containerView.bounds;
[self.containerView.layer addSublayer:gradientLayer];
//set gradient colors
gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor];
//set gradient start and end points
gradientLayer.startPoint = CGPointMake(0, 0);
gradientLayer.endPoint = CGPointMake(1, 1);
6.4. CAReplicatorLayer
CAReplicatorLayer
được thiết kế nhằm tạo ra một tập hợp các lớp giống nhau một cách hữu hiệu nhất. Nó hoạt động dựa trên việc vẽ lại 1 hay nhiều bản sao của từng sublayer, áp dụng các phép biến hình lên từng bản sao.
Ví dụ dưới đây sẽ giả lập hiệu ứng đổ bóng phản chiếu (phản chiếu qua gương).
//configure replicator
CAReplicatorLayer *layer = (CAReplicatorLayer *)self.layer;
layer.instanceCount = 2;
//move reflection instance below original and flip vertically
CATransform3D transform = CATransform3DIdentity;
CGFloat verticalOffset = self.bounds.size.height + 2;
transform = CATransform3DTranslate(transform, 0, verticalOffset, 0);
transform = CATransform3DScale(transform, 1, -1, 0);
layer.instanceTransform = transform;
//reduce alpha of reflection layer
layer.instanceAlphaOffset = -0.6;
6.5. AVPlayerLayer
Mặc dù AVPlayerLayer
không thuộc Core Animation, AVPlayerLayer
liên kết chặt chẽ với Core Animation qua việc cung cấp lớp con của CALayer
để hiển thị nội dung.
AVPlayerLayer
dùng để hiển thị nội dung video, được sử dụng trong các API bậc cao như MPMoviePlayer
.
Để sử dụng ta cần thêm AVFoundation framework vào dự án. Ví dụ dưới đây sẽ cho phép chúng ta tạo 1 chương trình chơi video. Video ví dụ được đưa thẳng vào resource của dự án ("Ship.mp4")
//get video URL
NSURL *URL = [[NSBundle mainBundle] URLForResource:@"Ship" withExtension:@"mp4"];
//create player and player layer
AVPlayer *player = [AVPlayer playerWithURL:URL];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
//set player layer frame and attach it to our view
playerLayer.frame = self.containerView.bounds; [self.containerView.layer addSublayer:playerLayer];
//play the video
[player play];
6.6. Các layer đặc biệt khác
Ngoài các CALayer trên, còn có các layer khác mà chúng ta có thể sử dụng:
CATransformLayer
: dùng để tạo các đối tượng 3DCAScrollLayer
: cho phép tạo hiệu ứng "scroll" cho các nội dung trong layer, dùng để hiển thị các nội dung lớn hơn biên (bound) của layerCATiledLayer
: dùng để hiện thị 1 ảnh rất lớn bằng cách tách nhỏ thành các ảnh nhỏ hơn để hiển thị khi cần thiếtCAEmitterLayer
: dùng để tạo ra các hiệu ứng theo thời gian thực như khói, lửa, mưa ...CAEAGLLayer
: dùng để hiển thị đồ họa OpenGL
(kết thúc phần 1, phần 2 sẽ trình bày về các hiệu ứng chuyển động và dự án demo)
Tham khảo từ iOS Core Animation, Nick Lockwood
All rights reserved