
บทเรียนซีรีย์ Rapid เป็นไว ทำได้ เข้าใจ กับ Flutter กับตัวอย่างหลังจากดึงข้อมูล JSON มาแสดงใน ListView แล้วมาตกแต่ง Row ด้วย Card กันหน่อย
ตัวอย่างที่จำเป็นต้องศึกษาก่อนหน้า
- เริ่มต้นการพัฒนาแอพฯ Cross Platform ด้วย Flutter ของ Google การติดตั้ง
- Rapid Series: Flutter สร้างแอปพลิเคชันรายการ List Widget ด้วย Web Services JSON
จากบทเรียนที่แล้วเราจะได้หน้าจอแอปพลิเคชันของเราเป็นดังนี้:

บทเรียนนี้เราจะมา Decoration กันสักหน่อยในเรื่องของความสวยงามซึ่งเราต้องจัด Padding และ Margin กันหน่อยมาดูกันว่าเราจะเริ่มต้นยังไง
ให้เราสร้าง File ใหม่ขึ้นมาใน folder “lib” ตั้งชื่อว่า items.dart
ใน items.dart เราจะสร้างคลาสที่ชื่อว่า Items() ขึ้นมา โดยรับค่าตัวแปรจาก JSON คือ title และ thumbnail ให้ทำการประกาศคลาส String
class Items {
final String title;
final String thumbnail;
Items(this.title, this.thumbnail) {
if (title == null) {
throw ArgumentError("Title can't not be null"
"Received: '$title'");
}
if (thumbnail == null) {
throw ArgumentError("Thumbnail of Books can't be null. "
"Received: '$thumbnail'");
}
}
}
เป็น คลาสในการทำ Custom Type ง่ายๆ เวลารับค่าก็แค่เรียก Items(title,thumbnail) ก็จบและเช็คค่าว่ามันไม่ใช่ค่าว่างก็เท่านั้น
กลับไปที่ main.dart ให้เพิ่ม Header เข้าไปดังนี้:
import 'items.dart';
เพื่อเรียกใช้งานคลาสของ Items ที่อยู่ในไฟล์ items.dart ได้
ไปที่ ContextState() ใน main.dart ให้เราแก้ไขตัวแปรจากเดิมคือ:
var _books = [];
แก้ไขเป็น:
var _items = <Items>[];
เพิ่ม const ของ style ตัวอักษรให้เป็นสีฟ้าเล็กน้อยดังนี้:
final _blueFont = const TextStyle(color: Colors.blueAccent);
แก้ไขส่วนของ Widget ListView ใหม่จากเดิมคือ:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Strings.appName),
),
body: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: _books.length,
itemBuilder: (BuildContext context, int position) {
return _buildRow(position);
}),
);
}
แก้ไขใหม่เป็น:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Strings.appName),
),
body: ListView.builder(
itemCount: _items.length * 2,
itemBuilder: (BuildContext context, int position) {
if (position.isOdd) return Divider();
final index = position ~/ 2;
return _buildRow(index);
}),
);
}
นั่นคือการทำ Divider ของแถวให้มีช่องว่างแก๊บระยะของแต่ละ Item ถูกคั่นด้วย Row เปล่าๆ ที่ไม่สูงมากระหว่างรายการมาคั่นไว้พร้อมเส้นของแถว
แก้ไข setState() ใหม่เป็นดังนี้:
setState(() {
final itemsJSON = json.decode(response.body);
for (var data in itemsJSON) {
final member = Items(data["title"], data["thumbnail"]);
_items.add(member);
}
});
นั่นคือเราจะเข้าระหัส URL web Services เก็บที่ itemsJSON หลังจากนั้นไป foreach เอาค่า value ตาม key มาเก็บใน data[“key”] ต่างๆ ส่งไปที่ฟังก์ชันในคลาส Items(title,thumbnail) เก็บลงตัวแปร member สมาชิกข้อมูล แล้วยัด array ค่าลงไปในตัวแปร _items ที่เราสร้างไว้
แก้ไขฟังก์ชันเมธอดของ _buildRow() ใหม่ทั้งหมดเลย โดยเราจะเริ่มที่การสร้าง Row มาคั่นตามด้วย Column ของ Row นั่นคือใน 1 แถวจะถูกแบ่งคอลัมน์เป็น 2 คอลัมน์ฝั่งซ้ายคือหน้าปกหนังสือที่ดึงจาก thumbnail ฝั่งขวาจาก title ซึ่งการจัด Layout อาจจะวุ่นวายและยาวเล็กน้อย
return Container(
margin: EdgeInsets.symmetric(horizontal: 0.0, vertical: 0.1),
padding: EdgeInsets.symmetric(horizontal: 5.0, vertical: 0.15),
height: MediaQuery.of(context).size.height * 0.25,
child: Card(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
child: Row(
children: <Widget>[
Expanded(
child: FittedBox(
fit: BoxFit.contain, // otherwise the logo will be tiny
child: Image.network(_items[i].thumbnail)),
),
Expanded(
child: Column(children: <Widget>[
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Text("${_items[i].title}", style: _biggerFont),
)),
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Text(
"see more detail and click to detail about: ${_items[i].title}",
style: _blueFont),
))
]))
],
)),
);
ลำดับของ Hierarchy เป็นตามรูปนี้:

ตรวจและแกะกันเอาเองนะครับ ผมแค่ใช้ Container คลุม แล้วสร้าง Card ขอบมนๆ มาระยะ padding ดีๆ ภายใน Card มี Row ที่แบ่ง Expanded (คอลัมน์ซ้าย) ด้วย Image และ ขวาด้วย Columns (แบ่งแถวของฝั่งขวา) เป็น 2 แถวด้วย Text สองตัว ตาม Code
ซึ่งถ้าสังเกตผมจะเรียก _items[i].title แทนการเรียกแบบเก่า ส่วนรูปภาพใช้ Image.Network มารับค่าแทน NetworkImage:
ดังนั้นภาพรวม Source code ของ main.dart เป็นดังนี้:
import 'package:flutter/material.dart';
import 'strings.dart';
import 'items.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
void main() => runApp(MainApp());
class MainApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: Strings.appName,
home: MainContextApp(),
);
}
}
class ContextState extends State<MainContextApp> {
var _items = <Items>[];
final _biggerFont = const TextStyle(fontSize: 18.0);
final _blueFont = const TextStyle(color: Colors.blueAccent);
@override
void initState() {
super.initState();
_loadData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Strings.appName),
),
body: ListView.builder(
itemCount: _items.length * 2,
itemBuilder: (BuildContext context, int position) {
if (position.isOdd) return Divider();
final index = position ~/ 2;
return _buildRow(index);
}),
);
}
Widget _buildRow(int i) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 0.0, vertical: 0.1),
padding: EdgeInsets.symmetric(horizontal: 5.0, vertical: 0.15),
height: MediaQuery.of(context).size.height * 0.25,
child: Card(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
child: Row(
children: <Widget>[
Expanded(
child: FittedBox(
fit: BoxFit.contain, // otherwise the logo will be tiny
child: Image.network(_items[i].thumbnail)),
),
Expanded(
child: Column(children: <Widget>[
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Text("${_items[i].title}", style: _biggerFont),
)),
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Text(
"see more detail and click to detail about: ${_items[i].title}",
style: _blueFont),
))
]))
],
)),
);
}
_loadData() async {
String dataURL = "https://enet5-7f9f6.firebaseio.com/Books.json";
http.Response response = await http.get(dataURL);
setState(() {
final itemsJSON = json.decode(response.body);
for (var data in itemsJSON) {
final member = Items(data["title"], data["thumbnail"]);
_items.add(member);
}
});
}
}
class MainContextApp extends StatefulWidget {
@override
createState() => ContextState();
}
รันคำสั่ง
$ flutter run
เราจะได้ผลลัพธ์ตามภาพ

ลองไปแกะคำสั่งข้างบนดูอีกทีนะครับ หรือจะ Clean Code ก็ได้ส่วนของ ContextState() ไม่น่าจะยากละ

