+1

Clipping trong Flutter

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, ClipOvalClipPath. 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ủa RenderBox được cắt và yêu cầu ta trả về một Rect. 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.

image.png

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

image.png

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

image.png

Clip.antiAlias

image.png

Như bạn có thể thấy, không có sự khác biệt có thể nhận ra giữa antiAliasWithSaveLayerantiAlias, 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à hardEdgeantiAliasWithSaveLayer 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.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí