Home Coding LISTVIEW TRONG FLUTTER

LISTVIEW TRONG FLUTTER

by Khanh Tran

Nếu bạn đã từng xây dựng bất kì một ứng dụng mobile với Android và IOS nào trước đây. Bạn sẽ cảm thấy việc tạo ListView trong Flutter đơn giản và dễ dàng thế nào. Trong bài viết này, tôi sẽ làm các ví dụ đơn giản để các bạn có thể hiểu một số cách sử dụng phổ biến của ListView.
Trước tiên, chúng ta sẽ xem xét các loại ListViews được xây dựng sẵn. Sau đó sẽ cùng tìm hiểu cách style cho các Items. Cuối cùng là hướng dẫn các thao tác thêm, xoá các item trong list.

Setup

Start một new project Flutter. Đặt tên project là flutter_listview.

Mở file main.dart và thay bằng đoạn code sau:

import 'package:flutter/material.dart';

    void main() => runApp(MyApp());

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'ListViews',
          theme: ThemeData(
            primarySwatch: Colors.teal,
          ),
          home: Scaffold(
            appBar: AppBar(title: Text('ListViews')),
            body: BodyLayout(),
          ),
        );
      }
    }

    class BodyLayout extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return _myListView(context);
      }
    }

    // replace this function with the code in the examples
    Widget _myListView(BuildContext context) {
      return ListView();
    }
Hình 1: Project chưa add ListView

Note_myListView() ở dòng cuối cùng là nơi chúng ta sẽ thêm các ví dụ cho bài hôm nay. Chỉ cần thay đổi code bên trong thẻ ListView.

Các loại ListView cơ bản

Static ListView

Nếu bạn có một danh sách ngắn các items không thay đổi, bạn có thể sử dụng ListView constructor mặc định để tạo một ListView cơ bản. Điều này rất hữu ích khi chúng ta muốn tạo một settings menu page….

Thêm đoạn code sau vào trong _myListView():

Widget _myListView(BuildContext context) {
      return ListView(
        children: <Widget>[
          ListTile(
            title: Text('Dog'),
          ),
          ListTile(
            title: Text('Cat'),
          ),
          ListTile(
            title: Text('Rabbit'),
          ),
        ],
      );
    }

Sau khi save lại, chúng ta sẽ thấy hình ảnh sau:

Hình2: ListTile

Đây là một ListView cơ bản nhất. Children của ListView là các ListTiles. ListTile là một widget được build sẵn để thể hiện Một title trong ListView cơ bản. Tuy nhiên một ListTile cũng có thể bao gồm subtitles, icons và images.

Nếu bạn muốn có thêm một dòng phân cách giữa các items, bạn có thể dùng ListTile.divideTiles:

Widget _myListView(BuildContext context) {
      return ListView(
        children: ListTile.divideTiles(
          context: context,
          tiles: [
            ListTile(
              title: Text('Dog'),
            ),
            ListTile(
              title: Text('Cat'),
            ),
            ListTile(
              title: Text('Rabbit'),
            ),
          ],
        ).toList(),
      );
    }

Lúc này, ListView của chúng ta sẽ trông như thế này:

Hình 3: ListTile

Dynamic ListView

Khi sử dụng Static ListView. Tất cả các elements sẽ được tạo cùng một lúc. Điều này tốt khi sử dụng với một short list nhưng khi ta có một long list thì điều đó lại không phù hợp. Khi đó chúng ta nên tạo một ListView động bằng cách sử dụng ListView.builder() constructor. Khi đó ListView sẽ chỉ render các phần tử xuất hiện trên màn hình.

Thay đổi đoạn code trong _myListView():

Widget _myListView(BuildContext context) {

      // backing data
      final europeanCountries = ['Albania', 'Andorra', 'Armenia', 'Austria', 
        'Azerbaijan', 'Belarus', 'Belgium', 'Bosnia and Herzegovina', 'Bulgaria',
        'Croatia', 'Cyprus', 'Czech Republic', 'Denmark', 'Estonia', 'Finland',
        'France', 'Georgia', 'Germany', 'Greece', 'Hungary', 'Iceland', 'Ireland',
        'Italy', 'Kazakhstan', 'Kosovo', 'Latvia', 'Liechtenstein', 'Lithuania',
        'Luxembourg', 'Macedonia', 'Malta', 'Moldova', 'Monaco', 'Montenegro',
        'Netherlands', 'Norway', 'Poland', 'Portugal', 'Romania', 'Russia',
        'San Marino', 'Serbia', 'Slovakia', 'Slovenia', 'Spain', 'Sweden', 
        'Switzerland', 'Turkey', 'Ukraine', 'United Kingdom', 'Vatican City'];

      return ListView.builder(
        itemCount: europeanCountries.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(europeanCountries[index]),
          );
        },
      );
    }

Khi đó, trên màn hình mobile sẽ xuất hiện:

Hình 4: ListView trong Flutter

itemCount sẽ thông báo cho ListView số lượng phần tử trong list Items. itemBuilder function là function tạo danh sách động các phần tử khi chúng xuất hiện trên màn hình. BuildContext cung cấp context parameter và item position là index parameter. Index giúp chúng ta thuận tiện trong việc truy cập các phần tử trong list.

Infinite list

Flutter làm cho việc tạp một danh sách vô hạn trở nên đơn giản. Chỉ cần xóa itemCount parameter khỏi hàm ListView.builder Constructor.

Widget _myListView(BuildContext context) {
      return ListView.builder(
        itemBuilder: (context, index) {
          return ListTile(
            title: Text('row $index'),
          );
        },
      );
    }
Hình 5: Infinite list

Bạn có thể cuộn cả ngày và bạn sẽ không bao giờ đi đến phần tử cuối. Đó là cách quá dễ dàng.

Nếu bạn muốn thêm dấu phân cách giữa các ô trong ListView động, bạn có thể sử dụng ListView.separated.

Widget _myListView(BuildContext context) {
      return ListView.separated(
        itemCount: 1000,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text('row $index'),
          );
        },
        separatorBuilder: (context, index) {
          return Divider();
        },
      );
    }
Hình 6: Infinite list

Horizontal ListView

Để tạo một ListView theo chiều ngang, tất cả những gì bạn cần làm là đặt thuộc tính scrollDirection là horizontal:

Widget _myListView(BuildContext context) {
      return ListView.builder(
        scrollDirection: Axis.horizontal,
        itemBuilder: (context, index) {
          return Container(
            margin: const EdgeInsets.symmetric(horizontal: 1.0),
            width: 50,
            color: Colors.tealAccent,
            child: Text('$index'),
          );
        },
      );
    }
Hình 7: Horizontal ListView

ListTile customization

Phần lớn các ListView không chỉ hiển thị đơn giản các dòng Text mà còn kèm theo icons hay images. Để làm điều đó, chúng ta cùng xét một ví dụ đơn giản:

Widget _myListView(BuildContext context) {
      return ListView(
        children: <Widget>[
          ListTile(
            leading: Icon(Icons.wb_sunny),
            title: Text('Sun'),
          ),
          ListTile(
            leading: Icon(Icons.brightness_3),
            title: Text('Moon'),
          ),
          ListTile(
            leading: Icon(Icons.star),
            title: Text('Star'),
          ),
        ],
      );
    }
Hình 8: ListTile customization

Bạn cũng có thể add icon vào phần cuối của item nếu bạn sử dụng thuộc tính trailing.

ListTile(
      leading: Icon(Icons.wb_sunny),
      title: Text('Sun'),
      trailing: Icon(Icons.keyboard_arrow_right),
    ),
Hình 9: ListTile customization

Chúng ta cũng có thể sử dụng image thay thế cho icon với CircleAvatar widget:

Widget _myListView(BuildContext context) {
      return ListView(
        children: <Widget>[
          ListTile(
            leading: CircleAvatar(
              backgroundImage: AssetImage('assets/sun.jpg'),
            ),
            title: Text('Sun'),
          ),
          ListTile(
            leading: CircleAvatar(
              backgroundImage: AssetImage('assets/moon.jpg'),
            ),
            title: Text('Moon'),
          ),
          ListTile(
            leading: CircleAvatar(
              backgroundImage: AssetImage('assets/stars.jpg'),
            ),
            title: Text('Star'),
          ),
        ],
      );
    }

Để code có thể chạy, chúng ta cần thêm image vào project. Bạn có thể sử dụng NetworkImage(imageUrl) thay vì AssetImage(path). Tạo một thư mục assets trong thư mục gốc của project. Sau đó thêm các ảnh sau:

Đăng kí thư mục assets trong file pubspec.yaml:

flutter:
      assets:
        - assets/

Run lại ứng dụng và chúng ta sẽ được:

HÌnh 10: Images

Để thêm phụ đề cho thẻ ListTile chúng ta thêm subtitle argument:

ListTile(
      leading: CircleAvatar(
        backgroundImage: AssetImage('assets/sun.jpg'),
      ),
      title: Text('Sun'),
      subtitle: Text('93 million miles away'), //           <-- subtitle
    ),
Hình 11: ListTile customization image

Cards

Cards là một cách tuyệt vời để làm cho ListView của bạn trông sang trọng. Tất cả những gì bạn phải làm là bọc ListTile của mình bằng một Cards widget.

Widget _myListView(BuildContext context) {

      final titles = ['bike', 'boat', 'bus', 'car',
      'railway', 'run', 'subway', 'transit', 'walk'];

      final icons = [Icons.directions_bike, Icons.directions_boat,
      Icons.directions_bus, Icons.directions_car, Icons.directions_railway,
      Icons.directions_run, Icons.directions_subway, Icons.directions_transit,
      Icons.directions_walk];

      return ListView.builder(
        itemCount: titles.length,
        itemBuilder: (context, index) {
          return Card( //                           <-- Card widget
            child: ListTile(
              leading: Icon(icons[index]),
              title: Text(titles[index]),
            ),
          );
        },
      );
    }
Hình 12: ListView Cards

Bạn có thể thay đổi shadow của Cards bằng thuộc tính elevation. Bạn cũng nên thử các thuộc tính shape và margin.

Custom list item

Nếu ListTile không đủ cho nhu cầu của bạn, bạn có thể tự design tile cho chính mình. Xem ví dụ sau:

Widget _myListView(BuildContext context) {

      // the Expanded widget lets the columns share the space
      Widget column = Expanded(
        child: Column(
          // align the text to the left instead of centered
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text('Title', style: TextStyle(fontSize: 16),),
            Text('subtitle'),
          ],
        ),
      );

      return ListView.builder(
        itemBuilder: (context, index) {
          return Card(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Row(
                children: <Widget>[
                  column,
                  column,
                ],
              ),
            ),
          );
        },
      );

    }
Hình 13: Custom list item

Touch detection

Nếu bạn đang sử dụng ListTile, tất cả những gì bạn phải làm để phản hồi lại thao tác chạm của người dùng là thêm một onTaphoặc onLongTouch callback.

Widget _myListView(BuildContext context) {
      return ListView(
        children: <Widget>[
          ListTile(
            title: Text('Sun'),
            trailing: Icon(Icons.keyboard_arrow_right),
            onTap: () {
              print('Sun');
            },
          ),
          ListTile(
            title: Text('Moon'),
            trailing: Icon(Icons.keyboard_arrow_right),
            onTap: () {
              print('Moon');
            },
          ),
          ListTile(
            title: Text('Star'),
            trailing: Icon(Icons.keyboard_arrow_right),
            onTap: () {
              print('Star');
            },
          ),
        ],
      );
    }
Hình 14: Touch detection

Nếu bạn đang làm việc với custom list item thay vì ListTile, bạn có thể bọc tiện ích con của mình trong InkWell. 

return ListView.builder(
        itemBuilder: (context, index) {
          return Card(
            child: InkWell(
              onTap: () {
                print('tapped');
              },
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Row(
                  children: <Widget>[
                    column,
                    column,
                  ],
                ),
              ),
            ),
          );
        },
      );
Hinhf 16: Touch detection

Updating list data ListView trong Flutter

Adding and deleting rows in a ListView

Việc cập nhật các mục trong ListView thường là khá dễ dàng. Tất cả những gì bạn phải làm là sử dụng Stateful widget và gọi setState()bất cứ khi nào dữ liệu thay đổi.

Vì chúng ta cần một Stateful widget, hãy thay thế cả hai BodyLayoutvà _myListView()bằng code sau:

class BodyLayout extends StatefulWidget {
      @override
      BodyLayoutState createState() {
        return new BodyLayoutState();
      }
    }

    class BodyLayoutState extends State<BodyLayout> {

      List<String> titles = ['Sun', 'Moon', 'Star'];

      @override
      Widget build(BuildContext context) {
        return _myListView();
      }

      Widget _myListView() {
        return ListView.builder(
          itemCount: titles.length,
          itemBuilder: (context, index) {
            final item = titles[index];
            return Card(
              child: ListTile(
                title: Text(item),

                onTap: () { //                                  <-- onTap
                  setState(() {
                    titles.insert(index, 'Planet');
                  });
                },

                onLongPress: () { //                            <-- onLongPress
                  setState(() {
                    titles.removeAt(index);
                  });
                },

              ),
            );
          },
        );
      }
    }

Khi bạn nhấn vào một mục, nó sẽ thêm một mục “Planet” tại Index đó. Khi bạn nhấn và giữ một mục, nó sẽ xóa mục đó.

Hình 17: Updating list data

Kết luận

Chúng ta vừa tìm hiểu khá đầy đủ về ListView trong Flutter. Bây giờ bạn sẽ có một hiểu biết vững chắc về cách triển khai phần lớn các trường hợp sử dụng mà bạn sẽ gặp phải. Hãy like và cmt về những thắc mắc xuống phía dưới để nhận được giải đáp nhé.

You may also like

Leave a Comment