Flutter, một công cụ phát triển ứng dụng di động mã nguồn mở do Google tạo ra, đã nhanh chóng trở thành lựa chọn hàng đầu cho các nhà phát triển muốn tạo ra các ứng dụng đẹp mắt và hiệu quả trên cả hai nền tảng iOS và Android từ một mã nguồn duy nhất. Trong trái tim của Flutter là một hệ thống widget linh hoạt, cho phép bạn tạo ra các giao diện người dùng phức tạp và tùy chỉnh cao. Sự sắp xếp (layout) của các widget này trên màn hình không chỉ ảnh hưởng đến vẻ ngoài của ứng dụng mà còn đến trải nghiệm người dùng, hiệu suất ứng dụng và khả năng duy trì mã nguồn.
Từ khi mà khái niệm cốt lõi của Flutter là “Everything is widget” – mọi thứ là widget thì Flutter đã kết hợp chức năng giao diện người dùng vào chính widget của nó. Flutter cung cấp rất nhiều widget đặc biệt đã được thiết kế sẵn như Container, Center, Align,v.v… với mục đích duy nhất là tạo nên giao diện người dùng. Widget được xây dựng bằng cách kết hợp các widget khác với nhau và sử dụng layout widget.
Trong bài viết này, chúng ta sẽ tìm hiểu sâu hơn về cách Flutter xử lý layout và khám phá các loại layout chính mà nó cung cấp. Bắt đầu từ single-child layout widgets, cho phép bạn định vị một widget duy nhất trong một số cách, đến multi-child layout widgets, mà sắp xếp một danh sách các widget theo hàng ngang, hàng dọc, hoặc theo cấu trúc lớp. Mục tiêu của bài viết này là không chỉ giới thiệu về các loại layout và widget cụ thể mà còn cung cấp cho bạn những kiến thức cơ bản để bạn có thể tận dụng chúng một cách hiệu quả nhất trong các dự án Flutter của mình.
Nguyên tắc cơ bản của Layout
Trong Flutter, việc xử lý layout tuân theo nguyên tắc “từ trên xuống dưới” và “từ trong ra ngoài”, một cách tiếp cận trực quan và hiệu quả khi xây dựng giao diện người dùng. Khi một widget được xây dựng, Flutter bắt đầu quá trình định vị nó bằng cách xem xét các ràng buộc từ widget cha của nó, sau đó điều chỉnh kích thước của chính nó và cuối cùng là của các widget con. Quá trình này tiếp diễn cho đến khi tất cả các widget trên cây widget đều được định vị chính xác. Cây widget, cấu trúc dữ liệu cốt lõi của Flutter, thể hiện mối quan hệ giữa các widget, từ root widget xuống đến các leaf widget. Trong cây này, mỗi widget đều là một khối xây dựng của UI, có thể là stateless hoặc stateful.
Stateless widgets là những widget không yêu cầu lưu trữ trạng thái; chúng được xây dựng một lần và không thay đổi trong suốt vòng đời của chúng. Chúng thích hợp cho các phần UI đơn giản và tĩnh, như biểu tượng và văn bản. Ngược lại, stateful widgets có thể thay đổi trạng thái trong suốt vòng đời của chúng, cho phép tạo ra các giao diện người dùng động và tương tác cao, như biểu mẫu và các trò chơi. Sự phân biệt này giữa stateless và stateful widgets là nền tảng cho việc xây dựng các ứng dụng phức tạp và tương tác, cho phép các nhà phát triển tạo ra các trải nghiệm người dùng mượt mà và phản hồi nhanh chóng.
Các loại Layout Widget
Widget hỗ trợ duy nhất một con (Single Child Widgets)
Single-child layout widgets cho phép bạn định vị hoặc biến đổi một widget con duy nhất trong nhiều cách. Ví dụ, Container
cho phép bạn tạo ra một hộp với kích thước, màu sắc, và padding tùy chỉnh. Center
và Align
lần lượt giúp canh giữa và căn chỉnh widget con của chúng dựa trên các tham số được cung cấp.
- Trong loại này thì widget sẽ có duy nhất một widget là con và tất cả widget sẽ có một chức năng layout riêng biệt.
- Ví dụ như widget Center chỉ căn giữa con của nó với widget gốc và widget container cung cấp một sự linh hoạt để đặt con của nó vào bất cứ chổ nào bên trong bằng cách sử dụng các thuộc tính như padding, decoration, v.v…
- Single child widget là một lựa chọn tuyệt vời nhằm tạo nên một widget chất lượng và có một chức năng duy nhất ví dụ như button, label, v.v…
- dưới đây là ví dụ sử dụng widget Container:
- Ở đây ta sử dụng 2 widget, một là widget Container và một widget Text. Kết quả của widget này như hình bên dưới đây
Các thành phần quan trọng trong widget này:
- Padding – được sử dụng để sắp xếp các widget con bằng các thông số padding được cung cấp. Ở trên thì padding có thể được cung cấp bằng lớp EdgeInsets.
- Align – căn chỉnh widget con bên trong chính nó sử dụng giá trị của thuộc tính alignment. Giá trị cho thuộc tính alignment có thể được cung cấp bằng lớp FractionalOffset. Lớp FractionalOffset cung cấp hiệu số về khoảng cách từ phía trái trên.
- Ứng dụng hello world của chúng ta sử dụng các layout widget theo dạng chuẩn material design để thiết kế trang chính. Giờ chúng ta cùng điều chỉnh lại ứng dụng của chúng ta để xây dựng một trang chính sử dụng layout widget cơ bản như bên dưới:
- Container – Đây là widget có sẵn các chức năng căn lề, chỉnh khung, duy nhất một con và có đa dạng các định dạng.
- Center – widget một con cực đơn giản, có thể căn giữa widget con của nó.
- Widget Container là widget trên level cao hoặc là gốc. Container được hiệu chỉnh bằng thuộc tính decoration và padding để bố trí nội dung của nó.
- BoxDecoration có rất nhiều thuộc tính như color, border, v.v… để trang trí widget Container và ở đây thuộc tính color được sử dụng để đặt màu cho container.
- padding của widget Container được đặt bằng cách sử dụng lớp dgeInsets, nó cung cấp các lựa chọn để xác định giá trị cho padding.
- Center là widget con của widget Container. Sau đó thì widget Text là con của widget Center. Widget Text được sử dụng để hiện thị thông điệp và Center được dùng để căn giữa văn bản đó và nối chúng với widget gốc là Container.
Kết quả của đoạn code trên:
Widget hỗ trợ nhiều con(Multi-child Layout Widgets)
Đối với các giao diện cần sắp xếp nhiều widget con, Flutter cung cấp các widgets như Row
, Column
, Stack
, ListView
, và GridView
. Row
và Column
lần lượt sắp xếp các widget con của chúng theo chiều ngang và chiều dọc. Stack
cho phép chồng các widget lên nhau, trong khi ListView
và GridView
hỗ trợ việc hiển thị danh sách hoặc lưới các widget có khả năng cuộn.
- Trong loại này thì widget sẽ có nhiều hơn một widget con và layout của từng widget là độc nhất.
- Ví dụ như widget Row cho phép xếp các con của nó theo đường thẳng xuống, còn widget Column thì xếp các con của nó theo đường ngang. Bằng cách kết hợp Row và Column, widget với bất kỳ độ phức tạo nào cũng có thể được xây dựng.
Chúng ta hãy cùng xem qua các widget được sử dụng phổ biến nhất trong phần này:
- Row – cho phép xếp các con của nó theo đường thẳng xuống
- Column – cho phép xếp các con của nó theo đường ngang
- ListView – Cho phép xếp các con của nó theo dạng danh sách
- GridView – cho phép xếp các con nó theo dạng từng ô
- Expanded – được sử dụng để tạo các widget con của Row và column nhằm chiếm diện tích nhiều nhất có thể
- Table – widget theo dạng table
- Flow – widget theo dạng Flow
- Stack – widget theo dạng stack
Sliver Widgets
Sliver widgets là một loại widget phức tạp hơn, được thiết kế để hoạt động trong một môi trường cuộn (scrollable environment), cung cấp khả năng tùy chỉnh cao và hiệu suất tốt cho các danh sách lớn và động. Chúng thường được sử dụng trong các CustomScrollView
để tạo các hiệu ứng cuộn phức tạp và tùy chỉnh, như app bars co giãn hoặc danh sách và lưới có kích thước động.
CustomScrollView( slivers: <Widget>[ SliverAppBar( expandedHeight: 200.0, floating: false, pinned: true, flexibleSpace: FlexibleSpaceBar( title: Text('Sliver AppBar'), ), ), SliverList( delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return ListTile( title: Text('Item #$index'), ); }, childCount: 100, ), ), ], )
Thông qua việc kết hợp và tùy chỉnh các loại widget này, bạn có thể tạo ra các giao diện người dùng đẹp mắt và phức tạp trong các ứng dụng Flutter của mình.
Ứng dụng Layout nâng cao
- Trong phần này, chúng ta sẽ học về cách tạo một UI phức tạo với danh sách sản phẩm và dùng thiết kế tùy chỉnh sử dụng cả hại single và multiple child layout widget.
- Để thực hiện mục tiêu này, ta cần thực hiện như sau:
- Tạo một ứng dụng flutter mới
- Thay thế code trong main.dart bằng code như sau:
Sau đó ta sẽ tạo một widget MyHomePage kế thừa từ StatelessWidget thay vì mặc định là StatefulWidget và xóa các dòng code liên quan đi.
Tạo một widget mới là ProductBox dựa trên thiết kế như sau:
Sau đây sẽ là code của ProductBox.
- Widget ProductBox có sử dụng 4 đối số sau:
- name – tên sản phẩm
- description – mô tả sản phẩm
- price – giá của sản phẩm
- image – hình ảnh của sản phẩm
- ProductBox sử dụng 7 widget như sau:
- Container
- Expanded
- Row
- Column
- Card
- Text
- Image
- ProductBox được thiết kế dựa trên các widget được đề tên ở trên. Cách sắp xếp và phân cấp của các widget sẽ như hình bên dưới đây:
- Ta tạo thư mục assets của ứng dụng và hiệu chỉnh thư mục ấy trong pubspec.yaml như sau:
- và cây thư mục như sau (lưu ý: tạo thêm 1 folder appimages trong thư mục assets):
Các hình ảnh trong thư mục assets
Cuối cùng là sử dụng widget ProductBox trong MyHomePage như sau:
- Ở đây chúng ta sử dụng ProductBox như là con của widget ListView
- Kết quả khi chạy ứng dụng:
Tối ưu hóa hiệu năng của layout trong Flutter
Tối ưu hóa hiệu năng của layout trong Flutter là yếu tố quan trọng để đảm bảo rằng ứng dụng của bạn chạy mượt mà trên nhiều thiết bị với các tài nguyên hệ thống khác nhau. Việc sử dụng hiệu quả các widget như Expanded
, Flexible
, và Spacer
có thể giúp điều chỉnh layout một cách linh hoạt mà không làm ảnh hưởng đến hiệu năng.
Expanded
widget giúp cho widget con của nó mở rộng để chiếm lấy không gian trống trong một Row
, Column
, hoặc Flex
. Điều này hữu ích khi bạn muốn một widget chiếm lấy phần lớn không gian mà không cần chỉ định kích thước cố định, giúp giao diện người dùng của bạn phản ứng linh hoạt hơn với các kích thước màn hình khác nhau.
Row( children: <Widget>[ Expanded( child: Container( color: Colors.amber, child: Text('Item 1 - pretty big!'), ), ), Container( color: Colors.blue, child: Text('Item 2'), ), ], )
Flexible
tương tự như Expanded
, nhưng nó cho phép bạn điều chỉnh mức độ linh hoạt thông qua tham số flex
, giúp bạn phân chia không gian một cách chính xác hơn giữa các widget con trong một Row
hoặc Column
.
Row( children: <Widget>[ Flexible( flex: 2, child: Container( color: Colors.red, child: Text('Item 1'), ), ), Flexible( flex: 1, child: Container( color: Colors.green, child: Text('Item 2'), ), ), ], )
Spacer
tạo ra không gian trống giữa các widget trong một Row
hoặc Column
. Nó có thể được sử dụng để tạo ra khoảng cách linh hoạt giữa các widget mà không cần phải sử dụng Container
với kích thước cố định, giúp layout của bạn trở nên gọn gàng và dễ quản lý hơn.
Row( children: <Widget>[ Text('Item 1'), Spacer(), Text('Item 2'), ], )
Bằng cách áp dụng những lời khuyên này, bạn không chỉ tạo ra các layout linh hoạt phản ứng tốt với mọi kích thước màn hình mà còn giúp cải thiện hiệu năng của ứng dụng Flutter bằng cách giảm thiểu việc sử dụng tài nguyên không cần thiết.
Tool và công cụ layout của Flutter
Trong quá trình phát triển ứng dụng với Flutter, việc sử dụng các công cụ và tài nguyên hỗ trợ có thể đáng kể cải thiện hiệu quả công việc và giúp giải quyết các thách thức liên quan đến thiết kế layout. Một trong những công cụ không thể thiếu là Flutter Inspector, một công cụ mạnh mẽ tích hợp trong Flutter DevTools, cho phép các nhà phát triển kiểm tra cây widget của ứng dụng và hiểu rõ cách các widget được xếp chồng lên nhau trên màn hình. Điều này giúp trong việc xác định và giải quyết các vấn đề liên quan đến layout, như kích thước widget và padding.
Layout Explorer, một tính năng khác của Flutter DevTools, cung cấp một giao diện trực quan để xem và điều chỉnh layout của ứng dụng. Nó cho phép bạn điều chỉnh thuộc tính của các widget như kích thước, khoảng cách, và canh chỉnh, ngay lập tức thấy thay đổi trên giao diện mà không cần phải lập trình lại từ đầu.
Ngoài ra, cộng đồng Flutter cũng là một nguồn tài nguyên quý giá, với một loạt các tài liệu hướng dẫn, bài viết, video, và diễn đàn hỗ trợ. Trang web chính thức của Flutter và các kênh như Stack Overflow, Reddit, và các nhóm trên Discord và Telegram, cung cấp một môi trường để trao đổi kiến thức, đặt câu hỏi và nhận hỗ trợ từ các nhà phát triển khác.
Sử dụng các công cụ như Flutter Inspector và Layout Explorer cùng với việc tham gia vào cộng đồng Flutter có thể giúp bạn nhanh chóng nắm bắt và áp dụng các phương pháp tốt nhất trong thiết kế layout, từ đó xây dựng nên những ứng dụng Flutter chất lượng cao và hiệu quả.