Chế độ xem tùy chỉnh là nền tảng của giao diện độc đáo cho bất kỳ ứng dụng di động nào. Chế độ xem tùy chỉnh trong ứng dụng di động cho phép bạn tạo đồ họa, hoạt ảnh và các yếu tố riêng không có trong các thành phần tiêu chuẩn. Theo Apple Developer, 2025, drawRect vẫn là phương thức vẽ tùy chỉnh chính trong phát triển di động iOS. Trên Android, onDraw với Canvas đóng vai trò tương tự, còn trong Flutter — CustomPainter.
Những điểm chính
Chế độ xem tùy chỉnh là các thành phần giao diện do người dùng định nghĩa ghi đè các phương thức vẽ và đo lường tiêu chuẩn để tạo đồ họa độc đáo. Biểu đồ, hoạt ảnh, sơ đồ, yếu tố trò chuyện tùy chỉnh và mặt nạ AR — đây là những trường hợp sử dụng điển hình của chế độ xem tùy chỉnh trong ứng dụng di động. Các thành phần UI tiêu chuẩn (Button, TextView, Label) bao phủ 80% nhiệm vụ, nhưng 20% còn lại yêu cầu kiểm soát hoàn toàn việc vẽ trong phát triển di động. Vẽ trong phát triển di động là kỹ năng phân biệt nhà phát triển junior với senior.
Chế độ xem tùy chỉnh trong ứng dụng di động cần thiết cho đồ họa 2D độc đáo, hoạt ảnh phức tạp, biểu đồ và trực quan hóa dữ liệu. Nếu UIKit tiêu chuẩn không cung cấp yếu tố cần thiết — một chế độ xem tùy chỉnh được tạo ra. Trong ứng dụng di động, chế độ xem tùy chỉnh được sử dụng cho thanh tiến trình, nút tùy chỉnh, tô màu chuyển sắc, biểu tượng hoạt ảnh và canvas vẽ bằng ngón tay. Quan trọng là không lạm dụng — đối với kiểu dáng đơn giản, chỉ cần cấu hình các thành phần tiêu chuẩn qua API Giao diện.
Trên iOS, chế độ xem tùy chỉnh được tạo bằng cách kế thừa UIView và ghi đè phương thức drawRect. drawRect được gọi ở lần hiển thị đầu tiên và sau mỗi lần gọi setNeedsDisplay(). Bên trong drawRect, nhà phát triển nhận được CGContext qua UIGraphicsGetCurrentContext() — một canvas cho đường thẳng, hình dạng, gradient và văn bản. Core Graphics (Quartz 2D) là công cụ kết xuất cấp thấp dựa trên CPU cho phép kiểm soát hoàn toàn từng pixel. UIBezierPath là một trình bao bọc tiện lợi để tạo đường dẫn với cú pháp ngắn gọn.
class CircleView: UIView {
private var fillColor: UIColor = .systemBlue
override func drawRect(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
ctx.setFillColor(fillColor.cgColor)
ctx.addEllipse(in: bounds.insetBy(dx: 4, dy: 4))
ctx.drawPath(using: .fill)
}
func updateColor(_ newColor: UIColor) {
fillColor = newColor
setNeedsDisplay()
}
}
Core Graphics cung cấp CGContext — một canvas cho tất cả các thao tác vẽ. CGGradient tạo gradient tuyến tính và xuyên tâm với hỗ trợ CGColorSpace (sRGB, Display P3). Các phép biến đổi được thực hiện qua CGAffineTransform — ma trận 3x3 cho xoay, co giãn và dịch chuyển. CGContextTranslateCTM, CGContextRotateCTM và CGContextScaleCTM sửa đổi hệ tọa độ. Core Graphics chạy trên CPU, vì vậy đồ họa phức tạp có thể làm quá tải bộ xử lý — hãy sử dụng Metal để tăng tốc GPU.
UIBezierPath là một trình bao bọc hướng đối tượng trên CGPath. Phương thức addArc(withCenter:radius:startAngle:endAngle:clockwise:) tạo cung và hình tròn, addCurve tạo đường cong Bézier. Các đường dẫn hỗ trợ các thao tác hợp nhất, giao nhau và trừ qua addClip. UIBezierPath tiện lợi cho mặt nạ tùy chỉnh (maskView), đường cắt và hình dạng phức tạp — ngôi sao, trái tim, biểu tượng tùy chỉnh.
Trên Android, chế độ xem tùy chỉnh mở rộng View với onDraw(Canvas) được ghi đè. Canvas chứa tất cả các phương thức vẽ trong phát triển di động: drawCircle, drawRect, drawLine, drawText, drawBitmap, drawPath và drawRoundRect. Paint cấu hình các tham số — màu sắc, độ dày đường, kiểu (FILL, STROKE, FILL_AND_STROKE), khử răng cưa (isAntiAlias) và kích thước văn bản. onDraw được gọi mỗi lần invalidate() và không được chứa các thao tác nặng — tất cả tính toán đều được lưu vào bộ nhớ đệm.
class CustomCircleView(context: Context, attrs: AttributeSet?)
: View(context, attrs) {
private val paint = Paint().apply {
style = Paint.Style.FILL
color = Color.BLUE
isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val cx = width / 2f
val cy = height / 2f
val radius = minOf(cx, cy) - 4f
canvas.drawCircle(cx, cy, radius, paint)
}
}
Path trên Android tương đương với UIBezierPath: addCircle, addRect, addOval, lineTo, cubicTo và quadTo. PathMeasure tính toán độ dài đường dẫn và tọa độ tại bất kỳ điểm nào — hữu ích để tạo hoạt ảnh cho chuyển động của đối tượng dọc theo đường dẫn. Đối với hoạt ảnh, sử dụng ValueAnimator với invalidate() trong callback. Trình hoạt ảnh có thể thay đổi màu sắc, bán kính, vị trí — bất kỳ thuộc tính nào ảnh hưởng đến việc vẽ. Chế độ xem tùy chỉnh trên Android cũng hỗ trợ StateListDrawable và animated-vector cho hoạt ảnh qua XML.
Gradient trên Android được đặt qua Shader — LinearGradient, RadialGradient và SweepGradient. Shader được gán cho Paint qua paint.setShader(shader). LinearGradient tạo gradient tuyến tính giữa hai điểm với các vị trí màu tùy chọn. RadialGradient — xuyên tâm từ trung tâm ra các cạnh. SweepGradient — hình nón quanh một điểm trung tâm. Shader.TileMode (CLAMP, REPEAT, MIRROR) xác định hành vi bên ngoài ranh giới khu vực.
Flutter sử dụng CustomPainter — một lớp với các phương thức paint(Canvas canvas, Size size) và shouldRepaint. Canvas trong Flutter cung cấp các phương thức tương tự như trong Android: drawCircle, drawRect, drawLine, drawPath, drawParagraph và drawImage. Paint trong Flutter cấu hình màu sắc, độ dày, kiểu và khử răng cưa. Flutter không sử dụng drawRect hay onDraw — tất cả việc vẽ đều thông qua CustomPainter, được nhúng trong widget CustomPaint.
class CirclePainter extends CustomPainter {
final Color color;
CirclePainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.fill
..isAntiAlias = true;
final radius = size.shortestSide / 2 - 4;
canvas.drawCircle(size.center(Offset.zero), radius, paint);
}
@override
bool shouldRepaint(CirclePainter oldDelegate) =>
oldDelegate.color != color;
}
shouldRepaint xác định liệu CustomPainter có cần vẽ lại hay không. Chỉ trả về true khi dữ liệu ảnh hưởng đến kết xuất thay đổi — màu sắc, kích thước, dữ liệu biểu đồ. RepaintBoundary cách ly việc vẽ lại vào một lớp riêng biệt — sử dụng cho các widget hiếm khi thay đổi. Flutter không có setNeedsDisplay hay invalidate — việc vẽ lại được kích hoạt qua setState hoặc ValueNotifier với lắng nghe thay đổi.
Flutter hỗ trợ LinearGradient, RadialGradient và SweepGradient — các loại tương tự như trong Android. Gradient tạo Shader cho Paint qua createShader(Rect bounds). Các phép biến đổi được thực hiện qua canvas.rotate, canvas.scale, canvas.translate và canvas.skew. Flutter sử dụng ma trận biến đổi 4x4 (Matrix4) cho widget Transform. Hoạt ảnh vẽ trong Flutter được thực hiện với AnimationController kết hợp với setState để vẽ lại CustomPainter.
Đo lường chính xác chế độ xem tùy chỉnh là nền tảng của hành vi giao diện có thể dự đoán. Trên iOS, kích thước của Chế độ xem được xác định qua sizeThatFits và intrinsicContentSize. sizeThatFits trả về kích thước tối ưu cho các ràng buộc nhất định. intrinsicContentSize — kích thước tự nhiên của nội dung mà không có ràng buộc bên ngoài. Trên Android, đo lường được thực hiện trong onMeasure với các tham số MeasureSpec (UNSPECIFIED, EXACTLY, AT_MOST). Trong Flutter, kích thước được truyền cho paint(Canvas, Size) và có thể được tính qua computeDryLayout hoặc LayoutBuilder.
intrinsicContentSize trả về CGSize — kích thước tự nhiên của Chế độ xem dựa trên nội dung của nó. setContentHuggingPriority và setContentCompressionResistancePriority kiểm soát hành vi trong khi kéo giãn và nén. sizeThatFits được hệ thống gọi để tính toán kích thước trong Auto Layout. Đối với chế độ xem tùy chỉnh, hãy ghi đè intrinsicContentSize nếu thành phần của bạn có kích thước tự nhiên — ví dụ, một hình tròn luôn phải là 100x100.
onMeasure chấp nhận widthMeasureSpec và heightMeasureSpec. MeasureSpec chứa chế độ và kích thước: UNSPECIFIED (không ràng buộc, Chế độ xem tự chọn kích thước), EXACTLY (kích thước chính xác từ cha), AT_MOST (kích thước tối đa). setMeasuredDimension(width, height) lưu kết quả đo lường. Khi dữ liệu thay đổi, hãy gọi requestLayout() để đo lại. ViewGroup lồng nhau yêu cầu ghi đè onLayout để sắp xếp các phần tử con.
| Nền tảng | Phương thức đo lường | Gọi khi thay đổi | Trả về |
|---|---|---|---|
| iOS | sizeThatFits / intrinsicContentSize | setNeedsLayout / invalidateIntrinsicContentSize | CGSize |
| Android | onMeasure / setMeasuredDimension | requestLayout / invalidate | void (lưu kích thước) |
| Flutter | computeDryLayout | setState / LayoutBuilder | Size |
computeDryLayout là phương thức RenderObject để tính toán kích thước mà không cần chu kỳ bố cục đầy đủ. LayoutBuilder.getSize — một giải pháp thay thế để lấy không gian có sẵn trong phương thức build. Trong Flutter, kích thước canvas được truyền cho paint dưới dạng Size và không yêu cầu đo lường riêng — CustomPainter vẽ trong các ranh giới được truyền. Tuy nhiên, đối với RenderObject tùy chỉnh, bạn cần ghi đè performLayout và computeDryLayout.
Các câu hỏi thường gặp
drawRect là phương thức UIView để vẽ tùy chỉnh qua Core Graphics. Nó được gọi ở lần hiển thị đầu tiên của Chế độ xem và sau mỗi lần gọi setNeedsDisplay(). Sử dụng CGContext trong drawRect cho đường thẳng, hình dạng và văn bản.
Tạo một lớp mở rộng View, ghi đè onDraw(Canvas canvas) và sử dụng Canvas API: drawCircle, drawRect, drawPath. Paint cấu hình màu sắc, độ dày và kiểu. Để cập nhật, hãy gọi invalidate().
CustomPainter là một lớp Flutter để vẽ tùy chỉnh trên Canvas. Triển khai các phương thức paint(Canvas, Size) và shouldRepaint. Nó được đặt qua widget CustomPaint. Flutter không có drawRect — tất cả việc vẽ đều thông qua CustomPainter.
Trên iOS sử dụng sizeThatFits và intrinsicContentSize. Trên Android — onMeasure với MeasureSpec. Trong Flutter, kích thước được truyền cho paint(Canvas, Size) hoặc tính qua computeDryLayout. Mỗi nền tảng sử dụng phương thức đo lường riêng của mình.
Chế độ xem tùy chỉnh cần thiết cho đồ họa độc đáo, hoạt ảnh, biểu đồ và các yếu tố không thể triển khai bằng UIKit hoặc View tiêu chuẩn. Đối với kiểu dáng đơn giản, chỉ cần cấu hình các thành phần tiêu chuẩn — đừng làm phức tạp kiến trúc một cách không cần thiết.
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.