Flutter

Flutter, Bài 8: Giới thiệu một số Widgets thường dùng

Trong bài viết này, tôi sẽ giới thiệu một số loại Widget hiển thị dữ liệu mà bạn sẽ thường dùng, bao gồm Text để hiển thị đoạn văn bản, Image để hiển thị hình ảnh, RaisedButtonFloatingActionButton để nhận event click, AppBar tương ứng với Toolbar và ActionBar của Android, đi kèm với nó là Icon, Scaffold để chứa giao diện ứng dụng. Các widgets khác sẽ được giới thiệu trong bài sau.

Flutter

1. Scaffold và các Widgets liên quan:

1.1. Scaffold:

Như trong bài phân tích ví dụ cho sẵn của Flutter, tôi có giới thiệu sơ lược về Scaffold, cũng như một số tham số prop trong constructor của nó. Nhìn chung, mỗi tham số prop như appBar, body, floatingActionButton, v.v… tương ứng với một child trong Center hay Container. Về bản chất, Scaffold đóng vai trò như phần nền để bố trí các thành phần khác theo phong các Material Design, hay mới đây là Material Theming. Nếu bạn đã quen với việc viết ứng dụng Android theo phong cách mặc định là Google cung cấp thông qua theme Theme.AppCompat hay android:Theme.Material thì bạn không còn lạ gì với Scaffold này. Nó (tương đương) là sự tổng hợp của DrawerLayout và CoordinatorLayout vậy.

Scaffold cung cấp cho chúng ta constructor như sau:

Trong đó:

  • appBar: sẽ chứa một AppBar dù phần này chỉ đòi hỏi một PreferredSizeWidget, class này tương ứng với ActionBar và Toolbar trên Android.
  • body: là phần giao diện chính của “màn hình”.
  • floatingActionButton: một lần nữa, bạn cần một FloatingActionButton chứ không phải là một Widget bất kì như kiểu class được chọn, tất nhiên vẫn có thể là một Widget vuông nhưng FloatingActionButton phải đẹp hơn.
  • floatingActionButtonAnimator & floatingActionButtonLocation: hiệu ứng floatingActionButtonAnimator được thực thi khi floatingActionButton di chuyển theo định nghĩa vị trí của floatingActionButtonLocation, phần này bạn sẽ cần thực hành nhiều để hiểu rõ hơn.
  • drawer: sẽ chứa một Drawer theo tiêu chuẩn Material Design, nhưng nếu bạn không dùng Drawer thì vẫn có thể dùng một Widget bố cục theo ý bạn, nằm về bên trái màn hình.
  • endDrawer: giống drawer, nhưng sẽ nằm về bên phải màn hình.
  • persistentFooterButtons: tương tự phần được đóng khung đỏ trong hình này.
  • bottomNavigationBar: tương tự như BottomNavigationBar trong Android Support Lib, hoặc một BottomAppBar tương tự như BottomNavigationBar nhưng sẽ bị lõm một vị trí ứng với vị trí của FAB.
  • backgroundColor: màu nền.

Về chi tiết, bạn có thể đọc đầy đủ hướng dẫn chính chủ về tên này ở đây: https://docs.flutter.io/flutter/material/Scaffold-class.html. Ngoài ra, Scaffold còn có một method gián tiếp để gọi Snackbar (tương ứng với Snackbar trên Android) là Scaffold.of(BuildContext)#showSnackbar(Snackbar), và hiện một BottomSheet là Scaffold.of(BuildContext)#showBottomSheet(BottomSheet), trong đó .of sẽ return một ScaffoldState.

1.2. AppBar:

Nó tương tự như Toolbar hay ActionBar trong Android Sdk và Support Lib. Tuy nhiên, nó không đơn giản như Toolbar hay ActionBar, vì thực sự bạn phải chỉ định Widget con của nó bằng tay, thay vì dùng hàng loạt methods để thực hiện các thao tác đã định sẵn với các Widgets mặc định trong Android như setText hay setNavigationIcon.

Constructor của nó là:

Ở đây tôi chỉ trình bày một số prop mà bạn hay sử dụng, các props khác bạn hầu như ít khi sử dụng, trừ khi muốn tùy chỉnh giao diện mạnh tay để tạo phong cách riêng. Chúng gồm:

  • leading: tương tự setNavigationIcon. Mặc định, nó sẽ được ẩn đi trong “màn hình” đầu tiên, và sẽ hiển thị icon mũi tên trỏ về bên trái như trên Android khi vào “màn hình” thứ hai trở đi, với nhiệm vụ quay lại trước, tính theo page stack. Lưu ý: Nó là Widget hẳn hoi chứ không phải chỉ là một Drawable, nếu bạn muốn thay đổi nó, nếu chỉ là phần icon thì dùng class Icon, còn nếu muốn thay đổi cả hành vi nhận click thì lại phải dùng class IconButton. 2 class này tôi sẽ nói sơ qua sau.
  • title: là một Widget và cho dù bạn chỉ muốn định nghĩa phần text thôi, nhưng vẫn cần chỉ định một Text widget, chẳng hạn Text(“Hello World”) thay vì chỉ cần gọi setText(“Hello World) như Android. Vậy tại sao không cho nó là một String? Câu trả lời là để bạn tự do thay đổi phong cách của title này, chẳng hạn màu chữ, cỡ chữ, kiểu chữ… dễ dàng hơn.
  • centerTitle: là một bool, hỏi bạn xem là có dời tên title kia vào giữa cho giống trên iOS không, hay để bên góc trái (hoặc phải với ngôn ngữ RTL) như trên Android.
  • backgroundColor: màu nền.

Nếu muốn tùy chỉnh nhiều hơn để AppBar của bạn có phong cách đặc trưng, đập mắt thì bạn có thể tham khảo thêm về class này tại https://docs.flutter.io/flutter/material/AppBar-class.html.

1.3. FloatingActionButton:

Đây là một Widget, có thể nói, là đặc trưng của phong cách Material Design, mà các ngôn ngữ khác có widget tương tự cũng chỉ là hàng nhái. Constructor của nó là dư lầy:

Ở đây ngoài các prop về Color mà tôi tin bạn không có thắc mắc gì, bạn chỉ cần chú ý tới mấy tên sau khi mới làm quen:

  • mini: là một bool định nghĩa FAB có phải sử dụng kích cỡ mini hay normal (bình thường).
  • child: một lần nữa, tương tự như leading của AppBar, bạn cần tự định nghĩa một Widget để chỉ định icon cho nó. Có thể bạn sẽ cảm thấy khó chịu vì tại sao phải dùng Icon thay vì truyền trực tiếp một biểu tượng nào đó. Thực tế, có những lúc bạn phải sử dụng Text thay vì Icon để gán cho FAB, và trên Android thì việc vẽ một dòng text dưới dạng drawable là không dễ đối với các bạn non kinh nghiệm, còn với Flutter thì cứ dùng class Text mà thôi. Vì vậy, dù phiền phức một chút nhưng việc chỉ định Widget bằng tay là điều rất hợp lí.
  • onPressed: là một VoidCallback, định nghĩa hành động khi bạn click vào nó, tốt nhất bạn nên “câu” method này ra ngoài cho thuận tiện thao tác.
  • onLongTap: tương ứng hành động nhấn giữ, cách định nghĩa như onPressed.

Ngoài ra, nó còn một constructor có tên nữa là FloatingActionButton.extended. Để tìm hiểu về FAB.extended cũng như các props khác khi cần thiết, bạn hãy click vào đường link sau: https://docs.flutter.io/flutter/material/FloatingActionButton-class.html

1.4. Drawer:

Nếu trên Android, bạn hay dùng NavigationDrawer để dựng một drawer thì với Flutter, tên này được sinh ra với sứ mệnh tương tự. Constructor của nó như sau, và bạn chỉ cần chú ý tới prop child là đủ, nó chính là nội dung của Drawer. Còn elevation là độ cao của Drawer so với phần body của Scaffold và giá trị mặc định đã là tốt, bạn chỉ nên thay khi thực sự cần.

1.5. BottomNavigationBar và BottomAppBar:

BottomNavigationBar chính là, hoặc tương tự với, thứ nằm ở cạnh dưới màn hình trong ứng dụng YouTube trên iOS và Android. Và ngoài lề một chút, theo quan sát của tôi thì có khả năng cao Google Tasks được viết bằng Flutter chứ không phải native platform APIs.

BottomNavigationBar có constructor như sau:

Trong đó,

  • items: là một List<BottomNavigationBarItem>, chỉ mỗi phần tử của BottomNavigationBar. BottomNavigationBarItem cũng chẳng có gì to tát, chỉ gồm một icon để chỉ biểu tượng và title để chỉ tên hành động mà thôi. Theo tiêu chuẩn Material Design thì không có quá 5 items.
  • currentIndex: vị trí đầu tiên sẽ được chọn theo mặc định, thường là vị trí đầu tiên, tức khi bật ứng dụng lên lần đầu thì vị trí đầu tiên sẽ được chọn và nội dung tương ứng với nó sẽ được hiện ra.
  • onTap: được gọi khi một BottomNavigationBarItem được nhấn, hàm callback này nhận một param index ứng với vị trí của BottomNavigationBarItem vừa nhận sự kiện, ví dụ:

Còn BottomActionBar là một kiểu Layout bao ngoài BottomNavigationBar có tác dụng sẽ “lỡm” vào đúng vị trí của FAB để tạo hiệu ứng đẹp mắt như Google Tasks cho Android và iOS. Constructor của nó như sau, và bạn có thể hiểu prop child của nó sẽ chứa gì:

Trong đó, hasNotch có nghĩa là có lỡm vào không.

Về chi tiết của BottomNavigationBar và BottomNavigationBarItem, bạn có thể tham khảo thêm trên trang chính của Flutter tại https://docs.flutter.io/flutter/material/BottomAppBar-class.html

1.6. SnackBar và BottomSheet:

SnackBar có constructor như sau:

Trong khi content và phần text mô tả, và nó là Widget (dĩ nhiên trong đại đa số các trường hợp là Text), thì action chính là định nghĩa của phần nút hành động trên Snackbar. SnackBarAction có constructor đơn giản gồm phần label (và ơn trời nó là String chứ không phải Widget) và onPress là VoidCallback, tức chỉ là () { … }. Bạn không sử dụng trực tiếp class này trong Scaffold mà phải gọi thông qua showSnackbar.

Để có thêm thông tin phục vụ cho nhu cầu tùy chỉnh của bạn về sau, hãy bookmark trang này: https://docs.flutter.io/flutter/material/SnackBar-class.html, còn đối với BottomSheet là trang này: https://docs.flutter.io/flutter/material/BottomSheet-class.html.

Tổng hợp phần 1: Như vậy tôi đã trình bày sơ lược về Scaffold và các Widgets liên quan để dựng giao diện theo phong cách Material Design, bên dưới là những Widgets không thể thiếu trong quá trình dựng ứng dụng.

Flutter

2. Text

Đây có thể nói là Widget quan trọng nhất nhì trong Flutter, bởi bạn không thể tìm thấy một ứng dụng nào mà không có chữ nghĩa. Constructor phổ biến nhất của Flutter Text khá đơn giản, và nếu bạn giỏi tiếng Anh thì có thể hiểu ngay và luôn trong một nốt nhạc duy nhất:

Trong đó,

  • data là nội dung hiển thị, tức là phần chữ nghĩa. Lưu ý, prop này buộc phải nằm ở đầu constructor mà không nằm ở nơi nao khác, và nó chỉ là một chiều, truyền vào Text và không tài nào lấy ra được, nên không có thao tác lấy text thông qua nó đâu nhé!
  • style là TextStyle để định nghĩa về kiểu chữ, cỡ chữ, màu chữ, v.v… và bạn bắt buộc phải thuê mướn tên TextStyle này vào, tương tự như styleSheet trong React Native, nhưng có vẻ nhức nhối hơn, đặc biệt là với prop bên dưới.
  • align để chỉ định việc căn văn bản về phía lề nào. Rõ ràng, đây là sự phức tạp không đáng có, vì lẽ ra nên gom nó vào TextStyle luôn cho tiện. Nhưng chúng ta đang nói tới The Big G bạn à, thích gây khó khăn cho vui nhà vui cửa. Mặc định là TextAlign.start, tức là theo chiều của ngôn ngữ LTR hoặc RTL.
  • textDirection cũng tương tự, nếu bạn không truyền vào thì nó sẽ là chiều của ngôn ngữ, và tốt nhất bạn đừng thay đổi nó.

Để tìm hiểu rõ ràng hơn về Text và TextStyle, bạn có thể “bay” tới https://docs.flutter.io/flutter/widgets/Text-class.html.

3. Image

Widget quan trọng bậc nhì này có khá nhiều constructors tương ứng với các kiểu nguồn dữ liệu ảnh. Ở đây, tôi sẽ giới thiệu 3 constructors phổ biến nhất. Các constructors khác cũng như các methods liên quan có thể được tham khảo tại https://docs.flutter.io/flutter/widgets/Image-class.html.

3.1. Để hiển thị một ảnh từ assets, bạn sẽ dùng constructor này:

Vậy, vấn đề là bạn đặt các image assets ở đâu? Bạn sẽ đặt nó trong bất kì thư mục nào ngang hàng với tập tin pubspec.yaml, và cấu hình nó trong tập tin yaml đó. Thông thường, bạn nên đặt chúng trong thư mục có tên là assets để dễ bề quản lí. Chẳng hạn, cây thư mục của tôi là như sau:

Thì tôi cấu hình tập tin pubspec.yaml như sau:

Và để hiển thị với Image, bạn dùng constructor này với cú pháp:

Ngoài ra, bạn còn có thể đặt assets vào trong package trong thư mục lib. Khi dùng constructor này với assets dạng đó, bạn cần định nghĩa String package. Để tìm hiểu thêm về assets trong Flutter, bạn có thể đọc thêm tại https://flutter.io/assets-and-images/.

3.2. Để load ảnh từ một tập tin trong bộ nhớ thiết bị, bạn sử dụng constructor như sau:

Trong đó, param thứ nhất là một instance của class File. Class này tương tự như File trong Java, với constructor khá quen thuộc với các bạn đã quen thuộc với Android Sdk là File(String path). Do đó, để load một tập tin ảnh từ bộ nhớ, bạn sẽ cần path trỏ tới nó. Để tìm hiểu về File, bạn có thể đọc chi tiết tại đây: https://docs.flutter.io/flutter/dart-io/File-class.html.

Lưu ý: Với Android, để load ảnh từ bộ nhớ chính của thiết bị, tức Environment.getExternalStorageDirectory(), bạn cần quyền android.permission.READ_EXTERNAL_STORAGE trong Manifest, và runtime đối với Android 6.0 API23 về sau.

3.3. Để load ảnh từ internet, bạn dùng constructor bên dưới:

Trong đó, String src sẽ là URL trỏ tới ảnh. Chẳng hạn:

Và cũng tương tự như trên, bạn cần quyền android.permission.INTERNET trong AndroidManifest khi build cho Android.

4. TextField:

Widget này có “cái tên nói lên tất cả”. Nó là Widget nhận dữ liệu được nhập từ bàn phím. Nó chỉ có duy nhất 1 constructor:

Trong đó, bạn chỉ cần, ít nhất là cho tới thời điểm này, các params sau:

  • TextEditingController controller: Có nhiệm vụ quan sát sự thay đổi giá trị nhập liệu của TextField, tức phần text đang được nhập vào.
  • int maxLines: Số dòng tối đa.
  • int maxLength: Số kí tự tối đa.
  • TextInputType keyboardType: Kiểu dữ liệu sẽ được nhập vào, được chỉ định qua các constants tại https://docs.flutter.io/flutter/services/TextInputType-class.html. Mặc định là dạng text tức TextInputType.text.
  • ValueChanged<String> onChanged: Được gọi khi phần text có thay đổi. Nó có dạng một function chứa tham số duy nhất là text chỉ phần text hiện tại của TextField.
  • ValueChanged<String> onSubmit: Được gọi khi phần text được submit. Nó có dạng một function chứa tham số duy nhất là text chỉ phần text hiện tại của TextField.

Chẳng hạn:

Vậy, làm sao để lấy text ra? Có hai cách như sau:

Cách thứ nhất: Dùng TextEditingController#text, chẳng hạn, để lấy text của myTextField ra, bạn chỉ cần gọi myController.text, bởi bạn đã gán myController vào myTextField rồi, nên cứ dùng thoải mái.

Cách thứ hai: Tạo một String bên ngoài và sử dụng onChanged, vì biến text trong hàm onChanged callback chính là phần text hiện tại. Tuy nhiên, cách dễ dàng này không phổ biến cho lắm, cũng như TextEditingController cung cấp một số khả năng khác. Để tìm hiểu ví dụ về TextField, bạn có thể code theo sample của Flutter team tại https://flutter.io/cookbook/forms/text-field-changes/.

5. Checkbox:

Checkbox trong Flutter hoàn toàn có hành vi tương tự như Checkbox trong Android. Constructor của nó chỉ đơn giản như sau:

Trong đó, bạn chỉ cần nhớ các params sau:

  • bool value: Trạng thái mặc định ban đầu của Checkbox, tức là có được checked hay chưa, bạn phải tự chỉ định ở bước này, nó không cung cấp giá trị mặc định sẵn.
  • bool tristate: Trạng thái thứ ba, thường được sử dụng khi bạn có một checkbox bao một tập hợp các checbox con để chỉ tổng trạng thái của các checkbox con đó, bao gồm chọn tất cả, chỉ chọn một số, và không chọn cái nào. Nếu tristate là true thì value bên trên có thể null.
  • ValueChanged<bool> onChanged: Được gọi khi trạng thái checked có thay đổi. Tương tự như TextField, onChanged có dạng (checked) { }, trong đó checked là true, false hay null khi tristate là true.
  • Color activeColor: Màu nền khi được checked.

6. Switch:

Switch tương tự như native Switch trên iOS hay Switch(Compat) trên Android.  Constructor của nó tương tự như của Checkbox:

So với Checkbox, bạn có thể cần phải nhớ thêm các params sau:

  • Color activeColor: Màu khi trạng thái của Switch là on (checked == true).
  • Color inactiveTrackColor: Màu khi trạng thái của Switch là off (checked == false).

7. Radio:

Radio tương tự như Radio trong Android. Constructor của nó cũng không quá phức tạp:

Trong đó, bạn cần nhớ những props sau:

  • T value: Value của từng mục trong nhóm.
  • T groupValue: Tương ứng Value của item được chọn.
  • ValueChanged<T> onChanged: Được gọi khi bạn thay đổi lựa chọn.

Bạn sẽ dễ hiểu hơn qua ví dụ sau:

8. Slider:

Flutter Slider tương đối giống Slider trong iOS hay Seekbar trong Android. Constructor của nó gồm các params như sau:

Trong đó, bạn chỉ cần nhớ “gần hết” các props sau:

  • double value: Giá trị ban đầu, chỉ vị trí ban đầu của con trỏ.
  • ValueChanged<double> onChanged: Được gọi khi con trỏ được nắm kéo di chuyển bởi người dùng.
  • double min: Giá trị thấp nhất, tức đầu mút cực tiểu, không phải chewing gum.
  • double max: Giá trị cao nhất, tức đầu mút cực đại.
  • Color activeColor: Màu của phần từ đầu mút cực tiểu tới con trỏ.
  • Color inactiveColor: Màu của phần từ con trỏ tới đầu mút cực đại.

######

Như vậy, tôi đã giới thiệu với các bạn các Widgets thông dụng hay dùng khi dựng giao diện ứng dụng. Riêng ListView, tôi sẽ có hẳn một bài cho nó. Đối với các Widgets khác, tôi sẽ trình bày trong các bài hướng dẫn sau này ngoài loạt bài chính về Flutter hiện tại. Hẹn gặp lại các bạn trong bài ListView.

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.