Flutter: Giao diện người dùng: Giới thiệu các widget

Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực

Mục lục bài viết:

Các widget của Flutter được xây dựng bằng cách sử dụng một framwork hiện đại lấy cảm hứng từ React. Ý tưởng chính là bạn xây dựng giao diện người dùng của mình từ các widget. Các widget mô tả chế độ xem của chúng sẽ trông như thế nào với cấu hình và trạng thái hiện tại của chúng. Khi trạng thái của tiện ích thay đổi, tiện ích sẽ xây dựng lại mô tả của nó, framework này khác với mô tả trước đó để xác định những thay đổi tối thiểu cần thiết trong cây kết xuất bên dưới để chuyển từ trạng thái này sang trạng thái tiếp theo.

 Lưu ý: Nếu bạn muốn làm quen tốt hơn với Flutter bằng cách đi sâu vào một số mã đoạn, hãy xem codelab bố cục cơ bản , xây dựng bố cục và thêm tính tương tác vào ứng dụng Flutter của bạn.

Hello world

Ứng dụng Flutter tối thiểu chỉ cần gọi hàm runApp() bằng một widget:

import 'package:flutter/material.dart';

void main() {
  runApp(
    Center(
      child: Text(
        'Hello, world!',
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}

Hàm runApp() lấy được Widget đã cho và nạp nó vào thư mục gốc của cây widget. Trong ví dụ này, cây widget bao gồm hai widget là Center và con của nó là Text. Khung làm việc buộc tiện ích gốc phải che màn hình, có nghĩa là văn bản "Hello, world” sẽ ở giữa màn hình. Hướng văn bản cần được chỉ định trong trường hợp này; khi widget Material được sử dụng, điều này sẽ được giải quyết cho bạn, như được trình bày dưới đây. Widget SafeArea cũng được sử dụng để đệm văn bản đúng cách để nó xuất hiện bên dưới màn hình trên đầu màn hình.

Khi viết một ứng dụng, thông thường bạn sẽ tạo ra các widget mới là các lớp con của một trong hai hoặc StatelessWidget hoặc StatefulWidget tùy thuộc vào việc widget của bạn quản lý trạng thái nào. Công việc chính của widget là triển khai hàm build(), hàm này mô tả widget dưới dạng các widget cấp thấp hơn. Khuôn khổ xây dựng các widget đó lần lượt cho đến khi quá trình kết thúc trong các widget đại diện cho phần bên dưới là RenderObject tính toán và mô tả hình dạng của widget.

Các widget cơ bản

Flutter đi kèm với một bộ các widget (tiện ích) cơ bản mạnh mẽ, trong đó những tiện ích sau đây thường được sử dụng:

Text

Widget Text cho phép bạn tạo một hoạt động của văn bản theo kiểu bên trong ứng dụng của bạn.

RowColumn

Các tiện ích linh hoạt này cho phép bạn tạo bố cục linh hoạt theo cả hướng ngang (Row) và dọc (Column). Thiết kế của các đối tượng này dựa trên mô hình bố cục flexbox của web.

Stack

Thay vì được định hướng tuyến tính (theo chiều ngang hoặc chiều dọc), widget Stack cho phép bạn đặt các widget chồng lên nhau theo thứ tự vẽ. Sau đó, bạn có thể sử dụng tiện ích Positioned con của Stack để định vị chúng so với cạnh trên, phải, dưới hoặc bên trái của ngăn xếp. Các ngăn xếp dựa trên mô hình bố cục định vị tuyệt đối của web.

Container

Widget Container cho phép bạn tạo một phần tử trực quan dạng hình chữ nhật. Một vùng chứa có thể được trang trí bằng BoxDecoration, chẳng hạn như nền, đường viền hoặc bóng. Container cũng có thể có lề, phần đệm và các ràng buộc được áp dụng cho kích thước của nó. Ngoài ra, Container có thể được biến đổi trong không gian ba chiều bằng ma trận.

Dưới đây là một ví dụ đơn giản kết hợp các widget này và các widget khác:

import 'package:flutter/material.dart';

class MyAppBar extends StatelessWidget {
  MyAppBar({this.title});

  // Fields in a Widget subclass are always marked "final".

  final Widget title;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 56.0, // in logical pixels
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: BoxDecoration(color: Colors.blue[500]),
      // Row is a horizontal, linear layout.
      child: Row(
        // <Widget> is the type of items in the list.
        children: <Widget>[
          IconButton(
            icon: Icon(Icons.menu),
            tooltip: 'Navigation menu',
            onPressed: null, // null disables the button
          ),
          // Expanded expands its child to fill the available space.
          Expanded(
            child: title,
          ),
          IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
    );
  }
}

class MyScaffold extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Material is a conceptual piece of paper on which the UI appears.
    return Material(
      // Column is a vertical, linear layout.
      child: Column(
        children: <Widget>[
          MyAppBar(
            title: Text(
              'Example title',
              style: Theme.of(context).primaryTextTheme.headline6,
            ),
          ),
          Expanded(
            child: Center(
              child: Text('Hello, world!'),
            ),
          ),
        ],
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    title: 'My app', // used by the OS task switcher
    home: SafeArea(
      child: MyScaffold(),
    ),
  ));
}

Bạn hãy đảm bảo rằng có một entry uses-material-design: true trong phần flutter của file pubspec.yaml của bạn. Nó cho phép bạn sử dụng bộ biểu tượng Material đã được định nghĩa. Nói chung, bạn nên bao gồm dòng này nếu bạn đang sử dụng thư viện Materials.

name: my_app
flutter:
  uses-material-design: true

Nhiều tiện ích Material Design cần phải ở bên trong a MaterialApp để hiển thị đúng cách, nhằm kế thừa dữ liệu chủ đề. Do đó, hãy chạy ứng dụng với một MaterialApp.

Widget MyAppBar tạo ra một Container với chiều cao 56 pixel thiết bị độc lập với một đệm nội bộ (internal padding) là 8 pixel, cả hai ở bên trái và bên phải. Bên trong container, MyAppBar sử dụng một layout Row để sắp xếp các con của nó. Đối với con ở giữa là widget title thì nó được đánh dấu là Expanded, có nghĩa là nó mở rộng để lấp đầy bất kỳ không gian trống còn lại nào chưa được các con khác sử dụng. Bạn có thể có nhiều Expanded con và xác định tỷ lệ chúng sử dụng không gian có sẵn bằng cách sử dụng đối số flex cho Expanded.

Widget MyScaffold tổ chức con của nó trong một cột thẳng đứng. Ở đầu cột, nó đặt một đối tượng (thể hiện) của MyAppBar, chuyển cho thanh ứng dụng một widget Text để sử dụng làm tiêu đề của nó. Chuyển widget làm đối số cho các widget khác là một kỹ thuật mạnh mẽ cho phép bạn tạo các widget chung có thể được sử dụng lại theo nhiều cách. Cuối cùng, MyScaffold sử dụng một Expanded để lấp đầy khoảng trống còn lại với phần thân của nó, bao gồm một thông báo được căn giữa.

Để biết thêm thông tin, hãy xem Layout.

Sử dụng các thành phần Material

Flutter cung cấp một số tiện ích con giúp bạn tạo ứng dụng tuân theo Material Design. Ứng dụng Material bắt đầu với tiện ích con MaterialApp, tiện ích này xây dựng một số tiện ích hữu ích ở gốc ứng dụng của bạn, bao gồm một Navigator, quản lý một chồng các tiện ích con được xác định bằng chuỗi, còn được gọi là "route". Navigator sẽ cho phép bạn chuyển đổi mượt mà giữa các màn hình của ứng dụng. Sử dụng widget MaterialApp hoàn toàn là tùy chọn nhưng là một thực tiễn tốt.

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Flutter Tutorial',
    home: TutorialHome(),
  ));
}

class TutorialHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Scaffold is a layout for the major Material Components.
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.menu),
          tooltip: 'Navigation menu',
          onPressed: null,
        ),
        title: Text('Example title'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
      // body is the majority of the screen.
      body: Center(
        child: Text('Hello, world!'),
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Add', // used by assistive technologies
        child: Icon(Icons.add),
        onPressed: null,
      ),
    );
  }
}

Bây giờ mã đã chuyển từ MyAppBar và MyScaffold sang các widget AppBar và Scaffold, và từ material.dart thì ứng dụng đang bắt đầu xem xét nhiều Material hơn. Ví dụ: thanh ứng dụng có bóng và văn bản tiêu đề tự động kế thừa kiểu dáng chính xác. Một nút hành động nổi cũng được thêm vào.

Lưu ý rằng các widget được truyền dưới dạng đối số cho các widget khác. Widget Scaffold mang một số widget khác nhau như các đối số được đặt tên, mỗi đối số được đặt trong layout Scaffold ở vị trí thích hợp. Tương tự, widget AppBar cho phép bạn truyền các widget cho widget leading và actions của wedget title. Mẫu này sẽ lặp lại trong suốt frameword và là điều bạn có thể cân nhắc khi thiết kế các widget của riêng mình.

Để biết thêm thông tin, hãy xem các widget Material Components.

Lưu ý: Material là một trong 2 thiết kế đi kèm với Flutter. Để tạo thiết kế lấy iOS làm trung tâm, hãy xem gói thành phần Cupertino , có các phiên bản riêng của nó gồm CupertinoApp và CupertinoNavigationBar.

Xử lý các cử chỉ

Hầu hết các ứng dụng bao gồm một số hình thức tương tác của người dùng với hệ thống. Bước đầu tiên trong việc xây dựng một ứng dụng tương tác là phát hiện các cử chỉ đầu vào. Xem ví dụ dưới đây với việc tạo một nút đơn giản:

 

class MyButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        print('MyButton was tapped!');
      },
      child: Container(
        height: 36.0,
        padding: const EdgeInsets.all(8.0),
        margin: const EdgeInsets.symmetric(horizontal: 8.0),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(5.0),
          color: Colors.lightGreen[500],
        ),
        child: Center(
          child: Text('Engage'),
        ),
      ),
    );
  }
}

Widget GestureDetector không có một đại diện trực quan mà thay vào đó phát hiện những cử chỉ được thực hiện bởi người dùng. Khi người dùng chạm vào Container thì GestureDetectorse sẽ gọi callback onTap(), trong trường hợp này là in thông báo tới bảng điều khiển. Bạn có thể sử dụng GestureDetector để phát hiện nhiều cử chỉ đầu vào, bao gồm chạm, kéo và thang đo.

Nhiều widget sử dụng GestureDetector để cung cấp các lệnh callback tùy chọn cho các widget khác. Ví dụ như các widget IconButtonElevatedButton, và FloatingActionButton có các callback onPressed() được kích hoạt khi người dùng chạm vào widget.

Để biết thêm thông tin, hãy xem Cử chỉ trong Flutter.

Thay đổi các widget để phản hồi lại đầu vào

Cho đến đây thì bài viết này chỉ sử dụng các widget không trạng thái. Các widget không trạng thái nhận các đối số từ widget cha của chúng, chúng lưu trữ trong các biến thành viên final. Khi một widget được yêu cầu build(), nó sẽ sử dụng các giá trị được lưu trữ này để lấy các đối số mới cho các widget mà nó tạo ra.

Để xây dựng những trải nghiệm phức tạp hơn — ví dụ như để phản ứng theo những cách thú vị hơn với thông tin nhập của người dùng — các ứng dụng thường mang một số trạng thái. Flutter sử dụng StatefulWidgets để nắm bắt ý tưởng này. StatefulWidgets là những widget đặc biệt biết cách tạo các đối tượng State, sau đó được sử dụng để giữ trạng thái. Hãy xem xét ví dụ cơ bản sau đây, sử dụng ví dụ ElevatedButton đã đề cập ở trên:

 

class Counter extends StatefulWidget {
  // This class is the configuration for the state. It holds the
  // values (in this case nothing) provided by the parent and used
  // by the build  method of the State. Fields in a Widget
  // subclass are always marked "final".

  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {
      // This call to setState tells the Flutter framework that
      // something has changed in this State, which causes it to rerun
      // the build method below so that the display can reflect the
      // updated values. If you change _counter without calling
      // setState(), then the build method won't be called again,
      // and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called,
    // for instance, as done by the _increment method above.
    // The Flutter framework has been optimized to make rerunning
    // build methods fast, so that you can just rebuild anything that
    // needs updating rather than having to individually change
    // instances of widgets.
    return Row(
      children: <Widget>[
        ElevatedButton(
          onPressed: _increment,
          child: Text('Increment'),
        ),
        Text('Count: $_counter'),
      ],
    );
  }
}

Bạn có thể thắc mắc tại sao StatefulWidget và State là những đối tượng riêng biệt. Trong Flutter, hai loại đối tượng này có vòng đời khác nhau. Widgets là các đối tượng tạm thời, được sử dụng để xây dựng bản trình bày của ứng dụng ở trạng thái hiện tại của nó. Còn các đối tượng State thì liên tục giữa các lần gọi đến build() để cho phép chúng ghi nhớ thông tin.

Ví dụ trên chấp nhận đầu vào của người dùng và trực tiếp sử dụng kết quả trong phương thức build() của nó. Trong các ứng dụng phức tạp hơn, các phần khác nhau của hệ thống phân cấp tiện ích con có thể chịu trách nhiệm về các mối quan tâm khác nhau; ví dụ: một tiện ích con có thể trình bày giao diện người dùng phức tạp với mục tiêu thu thập thông tin cụ thể, chẳng hạn như ngày tháng hoặc vị trí, trong khi tiện ích con khác có thể sử dụng thông tin đó để thay đổi bản trình bày tổng thể.

Trong Flutter, thay đổi luồng thông báo "lên" hệ thống phân cấp widget theo cách gọi lại, trong khi trạng thái hiện tại chuyển "xuống" các widget không trạng thái thực hiện trình bày. Nguồn gốc chung chuyển hướng luồng này là State. Ví dụ phức tạp hơn một chút sau đây cho thấy cách này hoạt động trong thực tế:

 

class CounterDisplay extends StatelessWidget {
  CounterDisplay({this.count});

  final int count;

  @override
  Widget build(BuildContext context) {
    return Text('Count: $count');
  }
}

class CounterIncrementor extends StatelessWidget {
  CounterIncrementor({this.onPressed});

  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: Text('Increment'),
    );
  }
}

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {
      ++_counter;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Row(children: <Widget>[
      CounterIncrementor(onPressed: _increment),
      CounterDisplay(count: _counter),
    ]);
  }
}

Lưu ý việc tạo hai widget không trạng thái mới, tách biệt rõ ràng các mối quan tâm của việc hiển thị bộ đếm (CounterDisplay) và thay đổi bộ đếm (CounterIncrementor). Mặc dù kết quả giống như ví dụ trước, nhưng việc tách biệt trách nhiệm cho phép gói gọn sự phức tạp hơn trong các widget riêng lẻ, trong khi vẫn duy trì sự đơn giản trong phần cha.

Để biết thêm thông tin, hãy xem:

Ghép tất cả lại với nhau

Sau đây là một ví dụ đầy đủ hơn tập hợp các khái niệm này lại với nhau: Một ứng dụng mua sắm giả định hiển thị các sản phẩm khác nhau được chào bán và duy trì một giỏ hàng cho các giao dịch mua dự định. Bắt đầu bằng cách định nghĩa lớp trình bày là ShoppingListItem:

 

class Product {
  const Product({this.name});
  final String name;
}

typedef void CartChangedCallback(Product product, bool inCart);

class ShoppingListItem extends StatelessWidget {
  ShoppingListItem({this.product, this.inCart, this.onCartChanged})
      : super(key: ObjectKey(product));

  final Product product;
  final bool inCart;
  final CartChangedCallback onCartChanged;

  Color _getColor(BuildContext context) {
    // The theme depends on the BuildContext because different parts
    // of the tree can have different themes.
    // The BuildContext indicates where the build is
    // taking place and therefore which theme to use.

    return inCart ? Colors.black54 : Theme.of(context).primaryColor;
  }

  TextStyle _getTextStyle(BuildContext context) {
    if (!inCart) return null;

    return TextStyle(
      color: Colors.black54,
      decoration: TextDecoration.lineThrough,
    );
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
      onTap: () {
        onCartChanged(product, inCart);
      },
      leading: CircleAvatar(
        backgroundColor: _getColor(context),
        child: Text(product.name[0]),
      ),
      title: Text(product.name, style: _getTextStyle(context)),
    );
  }
}

Widget ShoppingListItem theo sau một mô hình chung cho các widget không có trạng thái. Nó lưu trữ các giá trị mà nó nhận được trong hàm khởi tạo của nó trong các biến thành viên final, sau đó nó sẽ sử dụng trong hàm build() của nó. Ví dụ: inCartboolean chuyển đổi giữa hai giao diện trực quan: một giao diện sử dụng màu chính từ chủ đề hiện tại và một giao diện khác sử dụng màu xám.

Khi người dùng nhấn vào mục danh sách thì widget sẽ không thay đổi trực tiếp giá trị inCart của nó. Thay vào đó, widget gọi hàm onCartChanged mà nó nhận được từ widget cha của nó. Mẫu này cho phép bạn lưu trữ trạng thái cao hơn trong phân cấp tiện ích con, khiến trạng thái tồn tại trong thời gian dài hơn. Trong trường hợp cực đoan, trạng thái được lưu trữ trên widget được truyền tới runApp() sẽ tồn tại trong suốt thời gian của ứng dụng.

Widget cha nhận được callback onCartChanged, nó sẽ cập nhật trạng thái bên trong của nó, điều này kích hoạt widget cha xây dựng lại và tạo một thể hiện mới của ShoppingListItem với gái trị inCart mới. Mặc dù widget cha tạo một đối tượng mới của ShoppingListItem khi nó xây dựng lại, nhưng hoạt động đó khá rẻ vì khung công tác so sánh các tiện ích con mới được xây dựng với các tiện ích con được xây dựng trước đó và chỉ áp dụng những khác biệt cho phần bên dưới widget RenderObject.

Đây là một tiện ích con mẫu lưu trữ trạng thái có thể thay đổi:

 

class ShoppingList extends StatefulWidget {
  ShoppingList({Key key, this.products}) : super(key: key);

  final List<Product> products;

  // The framework calls createState the first time a widget
  // appears at a given location in the tree.
  // If the parent rebuilds and uses the same type of
  // widget (with the same key), the framework re-uses the State object
  // instead of creating a new State object.

  @override
  _ShoppingListState createState() => _ShoppingListState();
}

class _ShoppingListState extends State<ShoppingList> {
  Set<Product> _shoppingCart = Set<Product>();

  void _handleCartChanged(Product product, bool inCart) {
    setState(() {
      // When a user changes what's in the cart, you need to change
      // _shoppingCart inside a setState call to trigger a rebuild.
      // The framework then calls build, below,
      // which updates the visual appearance of the app.

      if (!inCart)
        _shoppingCart.add(product);
      else
        _shoppingCart.remove(product);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Shopping List'),
      ),
      body: ListView(
        padding: EdgeInsets.symmetric(vertical: 8.0),
        children: widget.products.map((Product product) {
          return ShoppingListItem(
            product: product,
            inCart: _shoppingCart.contains(product),
            onCartChanged: _handleCartChanged,
          );
        }).toList(),
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    title: 'Shopping App',
    home: ShoppingList(
      products: <Product>[
        Product(name: 'Eggs'),
        Product(name: 'Flour'),
        Product(name: 'Chocolate chips'),
      ],
    ),
  ));
}

Lớp ShoppingList thừa kế lớp StatefulWidget, có nghĩa là các widget lưu trữ các trạng thái có thể thay đổi. Khi widget ShoppingList lần đầu tiên được chèn vào cây, thì khung công tác sẽ gọi hàm createState() để tạo một phiên bản mới của _ShoppingListState để liên kết với vị trí đó trong cây (lưu ý rằng các lớp con của State thường được đặt tên với dấu gạch dưới ở đầu để chỉ ra rằng chúng là chi tiết triển khai riêng tư). Khi widget cha của widget con này xây dựng lại, lớp cha sẽ tạo một bản sao mới là ShoppingList, nhưng khung công tác sẽ sử dụng lại bản sao _ShoppingListState đã có trong cây thay vì gọi createState lần nữa.

Để truy cập thuộc tính của ShoppingList hiện thời thì _ShoppingListState có thể sử dụng thuộc tính widget của nó. Nếu widget cha xây dựng lại và tạo mới ShoppingList, thì các cấu trúc _ShoppingListState sẽ xây dựng lại với giá trị widget con mới. Nếu bạn muốn được thông báo khi thuộc tính widget thay đổi, hãy ghi đè phương thức didUpdateWidget() để truyền một oldWidget nhằm cho phép bạn so sánh tiện ích cũ với tiện ích hiện tại.

Khi xử lý callback onCartChanged thì cấu trúc _ShoppingListState sẽ thay đổi trạng thái bên trong của nó bằng cách thêm hoặc xóa một sản phẩm khỏi _shoppingCart. Để báo hiệu cho framework rằng nó đã thay đổi trạng thái bên trong, nó sẽ kết thúc các lệnh gọi đó trong lời gọi đến setState(). Việc gọi setState sẽ đánh dấu widget này là bẩn và lên lịch xây dựng lại vào lần tiếp theo ứng dụng của bạn cần cập nhật màn hình. Nếu bạn quên gọi setState khi sửa đổi trạng thái bên trong của widget thì khung công tác sẽ không biết tiện ích của bạn bị bẩn và có thể không gọi phương thức build() của tiện ích, điều đó có nghĩa là giao diện người dùng có thể không cập nhật để phản ánh trạng thái đã thay đổi. Bằng cách quản lý trạng thái theo cách này, bạn không cần phải viết mã riêng để tạo và cập nhật các tiện ích con. Thay vào đó, bạn chỉ cần thực thi phương thức build để xử lý cả hai tình huống.

Phản hồi các sự kiện trong vòng đời của widget

Sau khi gọi createState() trên StatefulWidget, framework chèn đối tượng trạng thái mới vào cây và sau đó gọi initState() trên đối tượng trạng thái. Một lớp con của State có thể ghi đè initState để thực hiện công việc chỉ cần thực hiện một lần. Ví dụ: ghi đè initState để định cấu hình hoạt ảnh hoặc đăng ký các dịch vụ nền tảng. Việc triển khai của initState được yêu cầu bắt đầu bằng cách gọi super.initState.

Khi một đối tượng trạng thái không còn cần thiết nữa, framework sẽ gọi dispose() trên đối tượng trạng thái. Ghi đè phương thức dispose để thực hiện công việc dọn dẹp. Ví dụ: ghi đè dispose để hủy hẹn giờ hoặc hủy đăng ký các dịch vụ nền tảng. Việc triển khai dispose thường kết thúc bằng cách gọi super.dispose.

Để biết thêm thông tin, hãy xem State.

Các key

Việc sử dụng các key để kiểm soát tiện ích con nào mà framework khớp với các tiện ích con khác khi một tiện ích con xây dựng lại, thì theo mặc định, framework sẽ khớp với các tiện ích con trong bản dựng hiện tại và trước đó theo thứ tự runtimeType và thứ tự xuất hiện của chúng. Với các key, framework sẽ yêu cầu hai widget phải có key giống nhau cũng như runtimeType giống nhau.

Các key hữu ích nhất là các key trong các widget con tạo nhiều bản sao của cùng một loại tiện ích con. Ví dụ: widget ShoppingList tạo chỉ đủ các các đối tượng ShoppingListItem để lấp đầy vùng hiển thị của nó:

  • Không có key, mục nhập đầu tiên trong bản dựng hiện tại sẽ luôn đồng bộ với mục nhập đầu tiên trong bản dựng trước đó, ngay cả khi về mặt ngữ nghĩa, mục nhập đầu tiên trong danh sách chỉ cuộn ra ngoài màn hình và không còn hiển thị trong chế độ xem.

  • Bằng cách gán cho mỗi mục nhập trong danh sách một khóa "ngữ nghĩa", danh sách vô hạn có thể hiệu quả hơn bởi vì khung công tác đồng bộ hóa các mục nhập với các khóa ngữ nghĩa phù hợp và do đó xuất hiện hình ảnh tương tự (hoặc giống hệt nhau). Hơn nữa, đồng bộ hóa các mục nhập về mặt ngữ nghĩa có nghĩa là trạng thái được giữ lại trong các widget có trạng thái vẫn được gắn với cùng một mục nhập ngữ nghĩa hơn là mục nhập ở cùng một vị trí số trong khung nhìn.

Để biết thêm thông tin, hãy xem API Key.

Các key global

Các key global dùng để định nghĩa duy nhất các widget con. Key global phải là duy nhất trên toàn bộ hệ thống phân cấp tiện ích con, không giống như khóa cục bộ chỉ cần duy nhất giữa các anh chị em. Vì chúng là duy nhất nên một key global có thể được sử dụng để truy xuất trạng thái được liên kết với tiện ích con.

Để biết thêm thông tin, hãy xem API GlobalKey.

Nguồn: flutter.dev
» Tiếp: Layout (Bố cục) trong Flutter
« Trước: Codelab
Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực
Copied !!!