+2

Những layout rule mà ai code Flutter cũng nên biết

Bài viết này được dịch từ bài Flutter: The Advanced Layout Rule Even Beginners Must Know của tác giả Marcelo Glasberg

Khi có một ai đó đang học Flutter và hỏi bạn vì sao họ đã set width = 100px cho một cái widget nào đó nhưng khi render ra lại không như vậy, và bạn thường nói với họ rằng "Hãy thử bọc cái widget trong Center xem" đúng không?

Nhưng bạn không nên làm như vậy, bởi vì họ sẽ không hiểu được bản chất vấn đề và sẽ liên tục hỏi bạn như:

Tại sao FittedBox của mình lại không hoạt động?

Tại sao cái Column này lại bị overflow?

IntrinsicWidth dùng để làm cái méo gì thế?

Thay vào đó, hãy thử nói với họ rằng: Flutter layout rất khác so với HTML (vì thường thì những người hay có những thắc mắc này thường từ web, hoặc một nền tảng nào đó dựa trên html, vd như jsx của React/React Native hay xml của Android chẳng hạn), và hãy chắc chắn rằng cho họ biết điều này:

Constraint chạy từ trên xuống, Size thay đổi từ nhỏ đến lớn, Position được quy định bởi widget cha

Để dễ hình dung thì một Widget sẽ có một "constraint" riêng từ widget cha. "constraint" hiểu đơn giản thì nó bao gồm 4 giá trị: min width, max width, min height, max height.

Sau đó Widget này sẽ cung cấp cho mỗi widget con của nó một constraint riêng biệt, đọc xem size mà chúng muốn là bao nhiêu , sau đó xác định vị trí của từng widget con trong nó.

Sau khi đã xác định được constraint, size và position của tất cả các widget con, nó sẽ gửi thông tin về size của nó cho widget cha để tiếp tục tính toán.

Sau đây là một ví dụ cụ thể: Một layout đơn giản gồm Column, Padding, bên trong nó gồm 2 widget như hình:

Sau đây là mô phỏng vui về cách thức các widget phía trên hoạt động.

Widget — Ê thằng cha, constraints của tao là gì thế?

Parent — Kích thước của mày sẽ là: rộng từ 90px đến 300px, cao từ 30px đến 85px.

Widget — Xem nào... vì tao cần phải có padding 5px, nên những thằng con của tao sẽ chỉ được phép rộng tối đa là 290px và cao tối đa 75px.

Widget — Này thằng First Child, chiều rộng của mày chỉ được nằm trong khoảng 0px đến 290px, cao từ 0px đến 75px thôi đấy. Liệu mà tính nhé.

First Child — OK, vì tao chỉ cần cao 20px nên 20px là ổn rồi, chiều rộng tao không quan tâm lắm nên cứ lấy hết 290px đi.

Widget — Xem nào... vì tao muốn đặt thằng Second Child bên dưới thằng First Child, vậy thì sẽ chỉ còn lại 55px chiều cao cho nó thôi.

Widget — Này thằng Second Child, chiều rộng của mày chỉ được nằm trong khoảng 0px đến 290px, cao từ 0px đến 55px thôi đấy.

Second Child — OK, tao thì cụ thể cần rộng 140px, cao 30px chứ không cần nhiều đâu.

Widget — Thế thì tốt quá, vậy vì có thêm padding 5px nên tao sẽ đặt thằng First Child ở vị trí x:5 y:5, còn thằng Second Child sẽ là x:80 y:25.

Widget — Này thằng cha, tao sắp xếp xong rồi đấy. Tính ra thì chiều rộng của tao sẽ 300px, cao 60px.

Hạn chế

Bởi những quy định về layout đã nói phía trên nên nó sẽ tồn tại một vài hạn chế:

  • Widget có thể lựa chọn size mà nó muốn nằm trong khoảng constraint được cung cấp bởi widget cha, nên thường thì các widget sẽ không đúng với size mà nó muốn
  • Widget không biết và cũng không thể tự lựa chọn vị trí mà nó muốn, vì nó được đặt bởi widget cha
  • Bởi vì các widget bị phụ thuộc chặt chẽ vào widget cha, sẽ không thể đoán chính xác size và position của bất kì widget nào nếu như không xem tổng thể trong cả cây layout

Ví dụ thực tế

Trans: Về các ví dụ điển hình, bạn hãy tham khảo mục Examples tại bài viết gốc. Mình cực kì, cực kì recommend các bạn nên đọc hết toàn bộ các ví dụ trong bài để có thể hiểu sâu xa bản chất của nó để áp dụng một cách hiệu quả!

Tight × Loose Constraints

Trong quá trình làm, bạn sẽ dễ dàng bắt gặp các cụm từ "tight" và "loose" khi dùng đến constrant, vì vậy bạn cần nắm rõ ý nghĩa của chúng. Tight constraint rất dễ hiểu, đó là một size cụ thể, chính xác. Nói theo cách khác, tight constraint có max width = min width, max height = min height.. nếu bạn mở file box.dart trong source code của Flutter và tìm kiếm constructor của class BoxConstraints, bạn sẽ thấy như sau:

BoxConstraints.tight(Size size)
   : minWidth = size.width,
     maxWidth = size.width,
     minHeight = size.height,
     maxHeight = size.height;

Nếu bạn xem lại Example 2 phía bên trên, nó có đề cập đến việc màn hình ép cái Container màu đỏ kia phải có cùng size với nó. Trong trường hợp này, màn hình đã truyền cho Container một tight constraint.

Loose constraint thì lại khác, có max width và max height, nhưng sẽ không giới hạn width, height tối thiểu của widget. Nói cách khác, loose constraint có min width và min height bằng 0:

BoxConstraints.loose(Size size)
   : minWidth = 0.0,
     maxWidth = size.width,
     minHeight = 0.0,
     maxHeight = size.height;

Trong Example 3, Center cho phép Container có thể nhỏ tuỳ ý, nhưng không được lớn hơn màn hình. Center trong trường hợp này đã truyền vào Container một loose constraint.

Hãy tìm hiểu về layout rule của một số widget đặc biệt

Hiểu về rule chung rất cần thiết, nhưng vẫn chưa đủ.

Mỗi widget đều có thể apply một rule riêng, vì vậy không thể biết được nó sẽ làm những gì chỉ bằng cách nhìn tên của chúng.

Đừng cố đoán rule của chúng qua tên vì sẽ rất có thể bạn sẽ đoán sai, dẫn đến dùng sai nó. Bạn bắt buộc phải đọc document cũng như tìm hiểu qua code của chúng để nắm rõ hơn.

Hi vọng qua bài này bạn có thể nắm rõ hơn về cách mà Flutter render layout cũng như ứng dụng nó để không còn bỡ ngỡ khi chạy app mà UI lại khác với code nhé 😄


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í