Trong đồ họa máy tính, hành động giới hạn kết xuất cho một khu vực cụ thể được gọi là Clipping
. Một vùng clip được cung cấp cho Canvas
nên công cụ kết xuất sẽ chỉ “vẽ” các pixel bên trong vùng đã xác định. Không có gì "được sơn" bên ngoài khu vực đó sẽ được hiển thị.
Là nhà phát triển, chúng ta sử dụng clipping để tạo giao diện người dùng tùy chỉnh tuyệt đẹp với các hiệu ứng đặc biệt. Như tiêu đề đã nói, ở đây chúng ta sẽ nói về cách thực hiện cắt - clipping trong Flutter và giúp bạn tạo giao diện hấp dẫn ngay lập tức.
Bắt đầu
Hãy bắt đầu bằng cách tạo một ứng dụng đơn giản - vẽ một hình chữ nhật màu đỏ 200x200 ở chính giữa của Scaffold
. Code của chúng ta sẽ trông như thế này:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: MyApp(),
));
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Container(
color: Colors.red,
width: 200.0,
height: 200.0,
),
),
);
}
}
Kết quả khi chạy app, ta được màn hình như sau:
Custom Clipper
CustomClipper
là class cơ sở để cắt trong Flutter và nó được sử dụng bởi 4 widget: ClipRect
, ClipRRect
, ClipOval
và ClipPath
. Mỗi widget này có một hành vi xác định, vì vậy nếu ta bao một red container
trong một ClipOval
, ta sẽ được một hình tròn như sau:
body: Center(
child: ClipOval(
child: Container(
color: Colors.red,
width: 200.0,
height: 200.0,
),
),
)
Thay đổi chiều cao hoặc chiều rộng của Container
, nó sẽ bắt đầu trở thành hình bầu dục. Điều này thật đơn giản khi bạn muốn tạo một hình bầu dục tùy chỉnh trên giao diện ứng dụng của bạn. Tất cả chỉ cần thay đổi width
hoặc height
, sau đó Ctrl + S
để reload lại UI, bạn sẽ thấy màn hình ứng dụng phản ánh ngay những thay đổi trong code.
Bây giờ nếu bạn muốn tùy chỉnh kích thước và vị trí của clip thì sao? đó là những gì CustomClipper
tạo ra. Hãy tạo một cái cho widget ClipOval
của chúng ta:
class CustomRect extends CustomClipper<Rect>{
@override
Rect getClip(Size size) {
// TODO: implement getClip
throw UnimplementedError();
}
@override
bool shouldReclip(covariant CustomClipper<Rect> oldClipper) {
// TODO: implement shouldReclip
throw UnimplementedError();
}
}
- Override lại hàm
getClip()
sẽ cung cấp cho ta kích thước củaRenderBox
được cắt và yêu cầu ta trả về mộtRect
.Rect
này sẽ xác định kích thước và vị trí của clip. Hãy nhớ rằng Mọi widget hiển thị đều có một RenderBox. Vì vậy ta cần return lại như sau:
@override
Rect getClip(Size size) {
Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
return rect;
}
Điều này sẽ không thay đổi gì vì Rect
ở đây giống như giới hạn của child widget.
Nhưng nếu bạn chỉ muốn hiển thị một nửa hình bầu dục? Chúng ta có thể làm điều đó dễ dàng bằng cách thay đổi điểm left
của hình chữ nhật thành -size.width
và nếu bạn muốn nhìn thấy nửa còn lại, chỉ cần thay đổi điểm bên phải thành size.width * 2
.
Lưu ý: Khi kiểm tra, hãy đảm bảo rằng shouldReclip
trả về true
. Nếu không, hãy đặt nó một cách rõ ràng để Hot reload sẽ thay đổi đúng vùng clip.
ClipRect
Được sử dụng để cắt một vùng hình chữ nhật ra khỏi một hình ảnh lớn hơn. Ví dụ, bạn có thể sử dụng ClipRect
nếu bạn chỉ muốn hiển thị 1 vùng hình chữ nhật của cả một hình ảnh lớn.
Container(
child: Align(
alignment: Alignment.bottomRight,
heightFactor: 0.5,
widthFactor: 0.5,
child: Image.network("https://static.vinepair.com/wp-content/uploads/2017/03/darts-int.jpg"),
),
),
ClipRRect
Nó tương tự với ClipRect
với các góc bo tròn. Ta có thể cài đặt độ cong khác nhau cho mỗi góc, không cần thiết phải tạo 4 góc có cùng một bán kính. Trong ví dụ dưới đây, chỉ cần set giá trị cho 3 góc, ngoại trừ góc bottom-left
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25.0),
topRight: Radius.circular(25.0),
bottomRight: Radius.circular(25.0),
),
child: Align(
alignment: Alignment.bottomRight,
heightFactor: 0.5,
widthFactor: 0.5,
child: Image.network("https://static.vinepair.com/wp-content/uploads/2017/03/darts-int.jpg"),
),
)
ClipPath
Sử dụng để cắt một khu vực tùy chỉnh bằng cách sử dụng path
. Giả sử ta cần cắt một hình tam giác:
clipBehavior
Thuộc tính clipBehavior
là mới và bạn sẽ thấy nó rất nhiều trong Material Widgets
vì sự thay đổi đột ngột đã được thảo luận vài tháng trước. Với thay đổi này, các ứng dụng Flutter nhanh hơn 30%, nhưng một phần của thay đổi đột phá này là nó đã vô hiệu hóa lệnh gọi tự động đến saveLayer và làm lộ thuộc tính clipBehavior
. Với nó, bạn có thể thiết lập cách cắt nội dung widget. Behavior mặc định cho hầu hết các widget là Clip.none
.
Clip.hardEdge
Clip.antiAlias
Như bạn có thể thấy, không có sự khác biệt có thể nhận ra giữa antiAliasWithSaveLayer
và antiAlias
, nhưng có một sự khác biệt rõ ràng với hardEdge
. Về mặt hiệu suất, có sự khác biệt lớn giữa chúng, nhanh nhất sẽ là hardEdge
và antiAliasWithSaveLayer
là chậm nhất.
Rendering
Tất cả các widget cắt đều áp dụng vùng clip của chúng trong PaintingContext
trong quá trình xây dựng cây Lớp. Mỗi lớp chúng tôi thêm vào cũng tăng thêm độ phức tạp khi GPU thu hút nội dung kết quả vào bộ đệm khung. Luôn luôn khuyến khích cắt giảm ở mức tối thiểu, vì điều này thêm bộ đệm stencil cho mỗi lớp vào GPU, điều này có thể làm tăng thêm chi phí về thời gian hiển thị. Tốt nhất là nên cắt bớt những gì được yêu cầu và chúng ta cần cẩn thận sử dụng nó một cách tiết kiệm trong các hoạt ảnh. Nếu bạn muốn biết thì hãy xem Flutter’s Rendering Pipeline do Adam Barth trình bày vào năm 2016.
Hẹn gặp lại các bạn trong những bài chia sẻ tiếp theo.