iOS Core Animation

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 UIViewCALayer 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, boundsposition.

layout.png

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:-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 borderWidthborderColor 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.

cgaaffine_transform.png

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 CALayerCATextLayer, 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;

reflection.png

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 3D
  • CAScrollLayer: 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 layer
  • CATiledLayer: 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ết
  • CAEmitterLayer: 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