Flutter : Common layout widgets

Bài tìm hiểu và dịch này là phần cuối trong bài Building Layouts in Flutter.

Flutter hỗ trợ bộ thư viện phong phú cho việc layout widget. Tuy nhiên cũng như các ngôn ngữ native khác, đơn giản chúng ta chỉ cần tìm hiểu một vài thư viện hay được sử dụng cũng là khá ok để có thể viết được một ứng dựng với Flutter.

Có thể chia các bộ thư viện (library) hỗ trợ cho layout widgets làm 2 loại :

Có một lưu ý với các bạn đó là bất kì các ứng dụng đều có thể sử dụng widgets library, nhưng chỉ có ứng dụng Material là có thể sử dụng material library

Trong bài viết hôm nay chúng ta sẽ cùng tìm hiểu về 1 số Standard widgetsMaterial Components sau:

Standard widgets

  • Container : Thêm padding, margins, borders, background color, hoặc các decorations tới widget.
  • GridView : dùng để layout cho một scrollable grid .
  • ListView : dùng để layout cho một scrollable list.
  • Stack : Hiển thị một widget lên phía trên một widget khác.

Material Components

  • Card : Sắp xếp thông tin liên quan vào trong cùng một box có rounded cornersshadow.
  • ListTile : Hiển thị thông tin cố định như một Row với text (tối đa 3 dòng văn bản) và option leading hoặc trailing icon .

Chúng ta sẽ cùng đi sâu tìm hiểu từng loại widget.

Standard widgets

Container

Containers được sử dụng để phân chia bố cục các widgets với padding hoặc là thêm borders hay margins. Bạn có thể thay đổi device’s background bằng việc đặt toàn bộ nội dụng hiển thị vào một Container và thay đổi background color hoặc image.

Container summary:

  • Thêm padding, margins, borders
  • Thay đổi được background color hoặc image
  • Chỉ chứa một widget con duy nhất, nhưng widget con này có thể là một Row, Column, thậm chí là root của một cây widget.

Ok, để hiểu rõ hơn, chúng ta sẽ xem qua ví dụ sau.

Như ví dụ này chúng ra có 4 images với thiết kế 1 Column, 2 Rows.

  • Mỗi Row sẽ hiển thị 2 images. Mỗi một image sẽ là 1 Container được rounded grey border và xét margins.
  • Column chứa tất cả images sử dụng 1 Container để thay đổi background color : lighter grey

Các bạn cùng xem qua code triển khai layout với việc sử dụng Container nhé.

class _MyHomePageState extends State<MyHomePage> {
  
  Widget build(BuildContext context) {
    var container = Container( // main Container
      decoration: BoxDecoration(
        color: Colors.black26,
      ),
      child: Column(
        children: [
          Row( // Hàng số 1 
            children: [
              Expanded(
                child: Container( // Container image 1 
                  decoration: BoxDecoration(
                    border: Border.all(width: 10.0, color: Colors.black38),
                    borderRadius:
                        const BorderRadius.all(const Radius.circular(8.0)),
                  ),
                  margin: const EdgeInsets.all(4.0),
                  child: Image.asset('images/pic1.jpg'),
                ),
              ),
              Expanded(
                child: Container( // Container image 2
                  decoration: BoxDecoration(
                    border: Border.all(width: 10.0, color: Colors.black38),
                    borderRadius:
                        const BorderRadius.all(const Radius.circular(8.0)),
                  ),
                  margin: const EdgeInsets.all(4.0),
                  child: Image.asset('images/pic2.jpg'),
                ),
              ),
            ],
          ),
          Row( //Hàng số 2 
            children: [
              Expanded(
                child: Container( // Container image 3
                  decoration: BoxDecoration(
                    border: Border.all(width: 10.0, color: Colors.black38),
                    borderRadius:
                        const BorderRadius.all(const Radius.circular(8.0)),
                  ),
                  margin: const EdgeInsets.all(4.0),
                  child: Image.asset('images/pic3.jpg'),
                ),
              ),
              Expanded(
                child: Container( // Container image 4
                  decoration: BoxDecoration(
                    border: Border.all(width: 10.0, color: Colors.black38),
                    borderRadius:
                        const BorderRadius.all(const Radius.circular(8.0)),
                  ),
                  margin: const EdgeInsets.all(4.0),
                  child: Image.asset('images/pic4.jpg'),
                ),
              ),
            ],
          ),
        ],
      ),
    );

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: container,
      ),
    );
  }
}

Dart code: main.dart

Images: images

Pubspec: pubspec.yaml

Đây chỉ là 1 ví dụ nhỏ về việc sử dụng Container trong layout Flutter. Để tìm được nhiều ví dụ khác, các bạn có thể tham khảo tại Flutter Gallery.

GridView

Với ví dụ với việc sử dụng Container cho việc hiển thị 4 images, vậy nếu bài toán bây giờ bạn cần hiển thị với layout như vậy, nhưng số lượng images là nhiều hơn thì sao? Flutter sẽ hỗ trợ để chúng ta có thể xây dựng layout với việc sử dụng GridView GridView được sử dụng để đặt các widgets dưới dạng danh sách grid. GridView cung cấp hai danh sách được tạo sẵn hoặc bạn có thể tạo tùy chỉnh của riêng mình. GridView sẽ tự động cuộn khi nội dung hiển thị vượt quá khung hiển thị.

GridView summary:

  • Sử dụng để làm các content có nội dung hiển thị dạng grid
  • Phát hiện khi nội dung màn hình hiển thị thì tự động cho phép cuộn.
  • Bạn có thể xây dựng custom grid cho bạn, ví dụ bạn có thể thay đổi các thông số: GridView.count : Số lượng columns GridView.extent : Kích thước width lớn nhất tính theo pixel của item

Chúng ta sẽ cùng nhau tìm hiểu 2 ví dụ về cách sử dụng GridView.countGridView.extent

Một ví dụ nhỏ với việc sử dụng GridView.extent để hiển thị các item trong GridView có kích thước maximum là 150 pixels.

Code triển khai đơn giản như sau :

// The images are saved with names pic1.jpg, pic2.jpg...pic30.jpg.
// The List.generate constructor allows an easy way to create
// a list when objects have a predictable naming pattern.
List<Container> _buildGridTileList(int count) {

  //generate list images
  return List<Container>.generate(
      count,
      (int index) =>
          Container(child: Image.asset('images/pic${index+1}.jpg')));
}

Widget buildGrid() {
  return GridView.extent(// custom extent 
      maxCrossAxisExtent: 150.0, // maximum pixel width of a item
      padding: const EdgeInsets.all(4.0),
      mainAxisSpacing: 4.0,
      crossAxisSpacing: 4.0,
      children: _buildGridTileList(30));
}

class _MyHomePageState extends State<MyHomePage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: buildGrid(),
      ),
    );
  }
}

Bạn có thể sử dụng full sourcode và resources :

Dart code: main.dart

Images: images

Pubspec: pubspec.yaml

Kết quả :

Có thể thấy ở đây chung ta chỉ giới hạn kích thước 1 item trong Grid, vì thế có bao nhiêu item trong 1 hàng sẽ tuỳ thuộc vào kích thước device (width). Vậy nếu muốn cố định số item trong 1 hàng thì sao, tương ứng có bao nhiêu cột trong Gridview? Bạn sẽ cần sử dụng GridView.count

Như ví dụ hiện tại chúng ta muốn hiển thị grid với 4 cột thì chúng ta sẽ chỉnh sửa code trong buildGrid() như sau :

Widget buildGrid() {
  return GridView.count(
      crossAxisCount: 4,
      padding: const EdgeInsets.all(4.0),
      mainAxisSpacing: 4.0,
      crossAxisSpacing: 4.0,
      children: _buildGridTileList(30));
}

Và kết quả hiển thị:

ListView

ListView tương đồng như Column widget, nhưng khác biệt là tự động cung cấp cuộn khi nội dung của nó quá dài đối với hộp hiển thị của nó.

Chúng ta sẽ đi qua ví dụ nhỏ sau hiển thị danh sách các hạng mục kinh doanh (rạp hát và nhà hàng), mỗi item con sẽ xây dựng với class ListTile. Và có đường chia giữa các rạp hát và nhà hàng

Phân tích source code xây dựng ListView cho ví dụ này chút :

List<Widget> list = <Widget>[ 
  ListTile( // Mỗi item con là một ListTile
    title: Text('CineArts at the Empire',
        style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0)),
    subtitle: Text('85 W Portal Ave'),
    leading: Icon(
      Icons.theaters,
      color: Colors.blue[500],
    ),
  ),
  ListTile(
    title: Text('The Castro Theater',
        style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0)),
    subtitle: Text('429 Castro St'),
    leading: Icon(
      Icons.theaters,
      color: Colors.blue[500],
    ),
  ),
  // ... tương tự với các item con khác 
];

class _MyHomePageState extends State<MyHomePage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      // ...
      body: Center(
        child: ListView( // Sử dụng ListView để hiển thị 
          children: list,
        ),
      ),
    );
  }
}

Đầy đủ source code của ví dụ sử dụng ListView các bạn có thể xem ở đây :

Dart code: main.dart

Icons: Icons class

Pubspec: pubspec.yaml

Stack

Chúng ta sẽ sử dụng Stack trong trường hợp muốn sắp xếp các widgets mà bố cục muốn hiển thị 1 số widget lên trên các widget khác. Ví dụ như hiển thị text lên phía trên 1 tấm hình chẳng hạn.

Stack summary:

  • Sử dụng cho các widgets muốn đặt lên trên một widget con khác
  • Widget đầu trong danh sách widget con sẽ là base widget, và các widget còn lại khi hiển thị sẽ nằm trên base widget.
  • Stack thì không thể scroll được
  • Bạn có thể lựa chọn cắt bỏ widget con nào mà vượt quá khung hiển thị.

Chúng ta sẽ cùng xem qua 1 số ví dụ về sử dụng Stack

Sử dụng Stack để chồng lên một Container (hiển thị văn bản trên một nền đen mờ) nằm bên trên của một CircleAvatar. Stack sẽ sắp xếp vị trí văn bản bằng cách sử dụng thuộc tính alignmentAlignments.

class _MyHomePageState extends State<MyHomePage> {
  
  Widget build(BuildContext context) {
    var stack = Stack( // chúng ta sử dụng Stack 
      alignment: const Alignment(0.6, 0.6),
      children: [ // với layout chứa các thành phần con 
        CircleAvatar( // một avatar là first child, vì thể nó sẽ là base widget trong Stack
          backgroundImage: AssetImage('images/pic.jpg'),
          radius: 100.0,
        ),
        Container( // và Container để hiểnt thị Text 
          decoration: BoxDecoration(
            color: Colors.black45,
          ),
          child: Text(
            'Mia B',
            style: TextStyle(
              fontSize: 20.0,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
        ),
      ],
    );
    // ...
  }
}

Dart code: main.dart

Image: images

Pubspec: pubspec.yaml

Material Components

Card

Card summary:

  • Là một Material Design card
  • Được sử dụng để trình bày cho phần nội dung cố định có liên quan tới nhau.
  • Là một single child trong Flutter, nhưng child này có thể là một Row, Column, hoặc là widget con khác chứa trong danh sách children.
  • Hiển thị với rounded cornersdrop shadow
  • Không thể scroll nếu nội dung vượt quá khung hiển thị
  • Là một component trong Material Components library

Chúng ta sẽ đi xem qua ví dụ nhỏ sau để hiểu hơn về cách layout với Card

Layout này có chứa 3 thông tin khác nhau (địa chỉ, số điện thoại và email), được đặt trong kích thước cố định SizedBox. Có 1 đường kẻ (Divider) phân chia giữa thông tin địa chỉ và số điện thoại .

Phân tích code một chút nhé:

class _MyHomePageState extends State<MyHomePage> {
  
  Widget build(BuildContext context) {
    var card = SizedBox(
      height: 210.0,
      child: Card( // cả 3 thông tin sẽ hiển thị trong 1 card 
        child: Column( // hiển thị theo Column 
          children: [// và là 1 list children: 3 ListTiles và 1 Divider
            ListTile( // mỗi một thông tin sẽ là một ListTile
              title: Text('1625 Main Street',
                  style: TextStyle(fontWeight: FontWeight.w500)),
              subtitle: Text('My City, CA 99984'),
              leading: Icon(
                Icons.restaurant_menu,
                color: Colors.blue[500],
              ),
            ),
            Divider(),
            ListTile(
              title: Text('(408) 555-1212',
                  style: TextStyle(fontWeight: FontWeight.w500)),
              leading: Icon(
                Icons.contact_phone,
                color: Colors.blue[500],
              ),
            ),
            ListTile(
              title: Text('[email protected]'),
              leading: Icon(
                Icons.contact_mail,
                color: Colors.blue[500],
              ),
            ),
          ],
        ),
      ),
    );
  //...
}

Dart code: main.dart

Icons: Icons class

Pubspec: pubspec.yaml

ListTile

Từ các vị dụ trên chúng ta đã sử dụng component ListTile, đây là row widget đặc biệt trong thư viện Material Components. Để tạo một hàng có một text với tối đa 3 dòng văn bản và một optional icon. ListTile thường được sử dụng trong Card hoặc ListView, nhưng cũng có thể sử dụng ở nhiều chỗ khác nữa.

Để sử dụng 1 ListTile, đơn giản bạn chỉ cần sử dụng code sau

ListTile(
     title: Text('text'),
     leading: Icon(
             Icons.contact_mail,
             color: Colors.blue[500],
           ),
   ),

All Rights Reserved