Flutter

Flutter Stack và Positioned

Trong bài trước, tôi có giới thiệu sơ qua về Stack và Positioned. Tuy nhiên, các lí thuyết chỉ được đưa ra chỉ đủ cho yêu cầu của ứng dụng. Hôm nay, tôi sẽ cung cấp các kiến thức khác cho các bạn về hai Widget này, đặc biệt là Positioned.

1. Stack

Có thể cho là Stack trong Flutter tương tự như FrameLayout trong Android Sdk hay CoordinatorLayout trong Android Support Lib. Ngoài chung nhiệm vụ cho phép bạn đặt các Widget/View con bên trong tùy ý xếp chồng lên nhau, chúng còn tương đồng nhau ở tầng cao của các Widget/View con nữa. Tức là Widget/View con nào được thêm vào sau thì sẽ được xếp trên những Widget/View đã được thêm vào trước.

Stack cũng như đại đa số các Flutter Layout Widget khác, nó nhận các Widget con với property children dưới class List<Widget>. Ngoài ra nó cũng có các properties khác sau đây trong constructor:

  • alignment: Định nghĩa bố cục các children Widget của nó sẽ được căn chỉnh ra sao. Prop này có class là AlignmentGeometry, nhưng có thực ra có tới 99.5% cơ hội bạn không dùng trực tiếp nó, mà thay vào đó, bạn sử dụng Alignment, hoặc một class khác tương tự nó nhưng hỗ trợ tốt hơn cho các ngôn ngữ RTL chẳng hạn như tiếng Arab là AlignmentDirectional. Về hai class này, tôi sẽ nói ngắn gọn bên dưới. Giá trị mặc định của prop này là AlignmentDirectional.topStart, có nghĩa là phía trên bên trái đối với các ngôn ngữ LTR như tiếng Việt, tiếng Anh, tiếng Pháp, tiếng Thái, tiếng Trung, v.v… và là phía trên bên phải với các ngôn ngữ RTL như tiếng Ả rập hoặc Do Thái.
  • fit: Định nghĩa kích thước của các children không được chỉ định position cụ thể (phần này liên quan tới Positioned, tôi sẽ nói bên dưới). Nó nhận giá trị thuộc enum Stackfit, với giá trị mặc định là StackFit.loose. Về enum Stackfit, tôi sẽ nói thêm bên dưới.
  • overflow: Định nghĩa hành vi của Stack nếu kích thước của một child Widget bị tràn (overflow) khỏi kích thước của chính Stack. Nó nhận giá trị thuộc enum Overflow là hoặc Overflow.clip (mặc định) là sẽ cắt các phần bị dôi ra, và Overflow.visible là sẽ giữ nguyên cho người dùng nhìn thấy phần kích thước bị dôi ra. Bạn cần phân biệt enum Overflow và enum TextOverflow sử dụng cho Text.
  • textDirection: Hướng ghi chữ, mặc định là theo ngôn ngữ thiết bị.

1.1. Alignment và AlignmentDirectional:

Hai class này đều có chung một kiểu constructor gồm hai tham số double là x và y. Điểm khác nhau giữa chúng là với AlignmentDirectional, thì x bắt đầu được tính theo hướng bắt đầu ghi của ngôn ngữ. Tức là đối với các ngôn ngữ LTR như tiếng Việt, tiếng Anh, tiếng Pháp, tiếng Thái, tiếng Trung, v.v… thì x bắt đầu từ cạnh trái màn hình và tăng dần sang cạnh phải, còn đối với các ngôn ngữ như tiếng Ả rập, tiếng Do Thái theo hướng RTL thì x bắt đầu từ cạnh phải và tăng dần về cạnh trái. Với Alignment thì x luôn bắt đầu từ cạnh trái màn hình, không phụ thuộc ngôn ngữ thiết bị.

Dù vậy, trên thực tế ta thường hay sử dụng các constants được định nghĩa sẵn trong các class này để sử dụng cho gọn, chẳng hạn Alignment.bottomCenter có giá trị là Alignment(0.0, 1.0). Còn các methods của chúng, bạn không cần quan tâm trừ trường hợp bạn extends chúng ra để thay đổi các hành vi mặc định của chúng. Về chi tiết hai class này, bạn tham khảo tại trang chính của chúng về AlignmentAlignmentDirectional.

1.2.  Stackfit

Stackfit có tổng cộng ba giá trị mà bạn cần phải biết. Tuy nhiên, trước khi đọc tiếp, bạn cần lưu ý là fit chỉ áp dụng đối với các children Widget không được chỉ định vị trí cụ thể, tức là chúng không phải/không nằm trong Positioned. Chúng là:

  • loose (mặc định cho Stack): Cho phép kích thước tối đa của Widget con bằng với kích thước của Stack.
  • expand: Buộc kích thước của các Widget con phải bằng với kích thước của Stack.
  • passthrough: Giữ nguyên các constraints trong trường hợp Stack nằm trong Expanded nằm trong Flex (thường là Row hoặc Column).

2. Positioned

Như bạn đã được hướng dẫn bên trên, thì nếu không sử dụng Positioned, thì mọi Widget con trong Stack đều được đặt theo vị trí được chỉ định bởi alignment và fit. Tuy nhiên, trên thực tế, rất hiếm các trường hợp như vậy. Và đây là lúc mà ta “thuê mướn” Positioned.

Như tôi đã nói ở bài trước, Positioned tương tự với FrameLayout.LayoutParams trong Android Sdk, hay các constraints đối với iOS Auto layout và Android ConstraintLayout. Thực tế, nó có tới 5 constructor, tuy nhiên tất cả chúng đều quy về 6 properties mà tôi sẽ giải thích bên dưới, ứng với constructor mặc định. Đối với các constructor khác, bạn có thể xem thêm tại trang chính của Positioned.

Positioned có các properties bao gồm hai nhóm có class là double gồm [left, right, width], [top, bottom, height] tương tự như CSS. Trong mỗi nhóm, chỉ tối đa định nghĩa được hai props. Chẳng hạn, bạn phải buộc định nghĩa một prop, vd tôi định nghĩa left, thì tối đa chỉ có thể định nghĩa thêm một trong right và width không null. Nếu định nghĩa cả ba trong cùng nhóm thì sẽ báo lỗi không render được. Nếu cả ba prop trong cùng nhóm cùng null, thì Positioned sẽ căn cứ vào Stack.alignment để đặt child. Nếu tất cả các props đều null thì Stack sẽ xem như Positioned không có position, hay Positioned chỉ là một Widget bình thường.

Các prop left, right, top, bottom tương ứng với các cạnh của Stack. Chẳng hạn, right = 0 tương ứng với cạnh phải của Positioned nằm trùng với cạnh phải của Stack. Chiều tăng của các prop trên là hướng vào trung tâm của Stack. Nếu left == 0 && right == 0 thì width của Positioned sẽ bằng đúng chiều rộng của Stack, và nếu top == 0 && bottom == 0 thì height của Positioned sẽ bằng đúng chiều cao của Stack. Do đó, phải có ít nhất một prop trong nhóm double phải null, vì nó sẽ được tính toán từ hai props kia.

Để tìm hiểu thêm về Stack và Positioned, bạn hãy thay đổi các tham số trong ví dụ ở bài trước. Chúc các bạn code vui vẻ.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.