Xin chào các bạn, tiếp nối bài viết trước giới thiệu về
Flutter, hôm nay mình xin hướng dẫn các bạn xây dựng một ứng dụng di động trên cả 2 hệ điều hành Android và iOS bằng Flutter.
Trong bài viết này, chúng ta sẽ cùng nhau làm 1 Todos list application rất đơn giản nhằm hiểu rõ về các thành phần cơ bản của Flutter cũng như giúp các bạn đang có ý định tiềm hiểu về Flutter sẽ có một ví dụ để tiếp cận tốt hơn với platform mới mẻ này.
Nội dung của blog hôm nay như sau:
- Giới thiệu chức năng app, Sketch wireframe
- Phân tích các thành phần
- Khởi tạo project
- Render todo lists
- Đánh dấu 1 task đã hoàn thành
- Thêm task mới
Ok, chúng ta bắt đầu!
1. Giới thiệu chức năng app, Vẽ Wireframe
1.1. Giới thiệu
Lần này chúng ta sẽ cùng nhau làm một ví dụ về ứng dụng ghi chú task để quản lý công việc đơn giản. Ứng dụng cho phép hiển thị danh sách các task đang hiện có, thêm task mới và đánh dấu một task đã làm xong
2.2. Wireframe
Sau khi đã hình dung ra chức năng của ứng dụng chúng ta sẽ tiến hành vẽ wireframe để từ đó tạo giao diện cho ứng dụng. Ở bước này, mình dùng phần mềm Balsamiq Mockup để vẽ layout. Nếu muốn tiện các bạn có thể dùng giấy bút và tự vẽ cho tiện cũng OK.
Về cơ bản app sẽ gồm 3 layout sau:
Đầu tiên khi mới lần đầu sử dụng app, thì sẽ hiển thị một dòng “Empty task” để người dùng biết rắng chưa có task nào trong ứng dụng
|
Empty task |
Để tạo task, chúng ta click vào nút có dấu (+) sau đó nhập nội dung task và chọn “Add task"
|
Add new task |
Sau khi tạo xong thì task sẽ được thêm vào danh sách
|
Display list of tasks |
Và khi hoàn thành task nào, chúng check vào task đó và sẽ được highlight để phân biệt với các task chưa hoàn thành
|
Mark a task as done |
2. Phân tích các thành phần
Dựa vào wireframe, ta thấy được, ứng dụng gồm các thành phần sau:
- Empty text: Text nằm giữa screen, chỉ hiển thị khi chưa có task nào được tạo
- List of task: Dánh sách chứa các task item
- Task item: Chưa nội dung của task và trạng thái đã hoàn thành hay chưa. Khi task đã hoàn thành thì sẽ có màu xanh
- Add Task button: Có dạng Floating action button. Khi click vào sẽ hiển thị dialog để tạo task mới
- Dialog: Chứa text input để nhập nội dung task
Sau khi phân tích xong, tiếp theo chúng ta sẽ tiến hành khỏi tạo project và hiện thực ứng dụng.
3. Khởi tạo project với Android Studio
Trước hết bạn cần đảm bảo rằng môi trường để phát triển ứng dụng Flutter đã được setup trên máy tính. Nếu chưa có, bạn có thể tham khảo bài viết cũ của mình tại
đây hoặc xem hướng dẫn tại trang chủ của Flutter. Trong bài viết này mình sẽ sử dụng Android Studio để khởi tạo project
- Mở Android Studio, chọn Start new Flutter project
|
Start new project |
- Chọn Flutter Application và click Next
|
Select Flutter Application |
- Đặt tên cho Project, ở đây mình đặt là to_dos_list, sau đó click Next rồi Finish.
- Chờ Android load xong thì chúng ta đã khởi tạo xong project Todo List bằng Flutter.
4. Render TodoList
- Trong main.dart, chỉnh sửa như sau:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Todo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: new TodoList(),
);
}
}
class Todo {
String text;
bool checked;
Todo({this.text, this.checked: false});
String toString() => "$text - $checked";
}
class TodoList extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new TodoListState();
}
}
class TodoListState extends State<TodoList> {
List<Todo> todos = [];
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('Todos'),
),
body: new Center(
child: todos.length == 0 ? buildEmpty() : null,
);
}
Widget buildEmpty() {
return new Text('Empty Task');
}
}
Chúng app của chúng ta sẽ có một title là
Flutter Todo, màu chủ đạo là màu xanh. Phần này được khai báo trong class MyApp. Giao diện ứng dụng sẽ được render thông qua class TodoList
TodoList sẽ mở rộng StatefulWidget và override hàm createState() trả về một object TodoListState. Object này có chức năng render ra các thành phần của ứng dụng như list các todo, dialog tạo thêm task… đồng thời quản lý state của TodoList của chúng ta.
Trước tiên chúng ta sẽ định nghĩa 1 todo gồm những trường thông tin gì. Tạo class Todo với 2 field là
text (nội dung của task) và
checked (trạng thái task đã hoàn thành hay chưa). Sau đó định nghĩa constructor cho Todo. Constructor này sẽ trả về một object Todo từ 1 tham số là text.
Todo list của chúng ta sẽ được lưu trữ dạng mảng ở biến todos, ban đầu được khởi là là mảng rỗng, trong hàm build(), nếu danh sách rỗng.
Trong hàm build(), ta kiểm tra xem nếu todos không có phần tử nào thì sẽ render ra dòng
Empty Task, phần này sẽ được hàm buildEmpty thực hiện. Tiếp theo chúng ta tiến hành render từng item của todo list.
|
Empty task |
Tạo thêm file TodoItem.dart, như sau:
import 'package:flutter/material.dart';
class TodoItem extends StatefulWidget {
final String text;
final bool checked;
TodoItem({this.text, this.checked: false});
@override
State<StatefulWidget> createState() {
return new _TodoItem(text, checked);
}
}
class _TodoItem extends State<TodoItem> {
bool checked = false;
String text;
_TodoItem(this.text, this.checked);
void changeChecked(bool value) => setState(() => checked = value);
TextStyle getTextStyle () => new TextStyle(
color: Colors.black,
fontFamily: "Rubik",
fontSize: 18.0,
decoration: TextDecoration.none,
decorationColor: Colors.grey[500],
);
@override
Widget build(BuildContext context) {
return new Container (
padding: const EdgeInsets.only(left: 15, right: 5),
margin: const EdgeInsets.only(top:0, bottom:0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Text(
text,
style: getTextStyle(),
),
Container(
margin: const EdgeInsets.only(left: 40),
child: Row(
children: <Widget>[
Checkbox(
value: checked,
onChanged: changeChecked,
)
],
)
),
],
),
);
}
}
Sau đó import TodoItem vào main.dart như sau
import 'TodoItem.dart';
Tạo hàm buildTodoList trong TodoListState như sau:
List<Widget> buildTodoList() {
return todos.map((item) => new TodoItem(text: item.text, checked: item.checked)).toList();
}
Sau đó update hàm build như sau:
...
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('Todos'),
),
body: new Center(
child: todos.length == 0 ? buildEmpty() : new Column(
children: buildTodoList(),
),
),
);
}
...
Tạo thử 1 TodoItem như sau và thêm vào mảng todos để thử
List<Todo> todos = [new Todo(text: “Test todo item")];
- Sau đó run và xem kết quả
|
Render todo list |
5. Đánh dấu 1 task là đã hoàn thành
Theo như wireframe, khi một task được đánh dấu đã hoàn thành bằng cách tick vào check box, thì nội dung text sẽ được gạch ngang đồng thời background sẽ chuyển thành màu xanh lá cây. Ta sửa file TodoItem.dart như sau:
import 'package:flutter/material.dart';
class TodoItem extends StatefulWidget {
final String text;
final bool checked;
TodoItem({this.text, this.checked: false});
@override
State<StatefulWidget> createState() {
return new _TodoItem(text, checked);
}
}
class _TodoItem extends State<TodoItem> {
bool checked = false;
String text;
_TodoItem(this.text, this.checked);
void changeChecked(bool value) => setState(() => checked = value);
TextStyle getTextStyle () => new TextStyle(
color: checked ? Colors.red[500] : Colors.black,
fontFamily: "Rubik",
fontSize: 18.0,
decoration: checked ? TextDecoration.lineThrough : TextDecoration.none,
decorationColor: Colors.grey[500],
);
@override
Widget build(BuildContext context) {
return new Container (
padding: const EdgeInsets.only(left: 15, right: 5),
margin: const EdgeInsets.only(top:0, bottom:0),
decoration: new BoxDecoration(
border: new Border(top: BorderSide.none, left: BorderSide.none, right: BorderSide.none, bottom: BorderSide(color: Colors.grey[500] ,width:1, style: BorderStyle.solid)),
color: checked ? Colors.lightGreenAccent : Colors.transparent,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Text(
text,
style: getTextStyle(),
),
Container(
margin: const EdgeInsets.only(left: 40),
child: Row(
children: <Widget>[
Checkbox(
value: checked,
onChanged: changeChecked,
)
],
)
),
],
),
);
}
}
Lưu lại và đợi hot reload, ta được kết qủa như sau:
|
Task done |
6. Thêm task mới
Chúng ta sẽ tạo một Float action button để mở một dialog để nhập task mới. Ta sửa hàm build của TodoListState trong main.dart như sau:
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('Todos'),
),
body: new Center(
child: todos.length == 0 ? buildEmpty() : new Column(
children: buildTodoList(),
),
),
floatingActionButton: FloatingActionButton(
tooltip: 'Increment',
child: Icon(Icons.add),
onPressed: () => print(“Add new task"),
),
);
}
Ta được UI như sau, khi click vào nút có (+) sẽ in ra dòng
Add new task trên console
|
Float action button |
Tiếp theo chúng ta cần hiển thị dialog với input text để có thể nhập liệu. Trong TodoListState, ta thêm field inputText kiểu String và hàm xử lý thay đổi của input text
onChangeText . Sau đó để hiển thị dialog, ta sẽ thêm hàm
showAddTodoDialog: để xử lý việc hiển thị dialog
showAddTodoDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
content: new Container(
child: new Row(
children: <Widget>[
new Expanded(
child: new TextField(
decoration: InputDecoration(
labelText: 'Add Task',
hintText: 'Enter task here'
),
onChanged: onChangeText,
),
)
]
),
),
actions: <Widget>[
FlatButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop(true);
},
),
FlatButton(
child: const Text('Add task'),
onPressed: () {
print(inputText);
var newTodoObj = new Todo(text: inputText);
todos.add(newTodoObj);
var updatedTodos = todos;
setState(() {
todos = updatedTodos;
});
Navigator.of(context).pop(true);
},
),
],
);
}
);
}
Sau đó update để khi click button sẽ hiển thị dialog,
...
floatingActionButton: FloatingActionButton(
tooltip: 'Increment',
child: Icon(Icons.add),
onPressed: () => showAddTodoDialog(context),
),
...
Lưu lại và chạy hot reload, ta được kết quả như sau:
|
Show dialog |
|
Handle input |
|
Finish |
Kết
Vậy là chúng ta vừa hoàn thành xong một ứng dụng đơn giản bằng Flutter, hi vọng bài viết này sẽ giúp các bạn có thể sử dụng Flutter một cách tốt hơn và tạo ra được những app dành riêng cho bản thân.
Mã nguồn của app mình sẽ để trên github tại
đây cho các bạn tham khảo.
Nếu bài viết hay và có ích, bạn có thể share cho nhiều người khác cùng biết, nếu có bất kì ý kiến đóng góp xây dựng, đừng ngần ngại để lại bình luận dưới bài viết này nhé. Thanks
Reference: