เมื่อเฟรมเวิร์กพัฒนาแอปมือถือ Flutter ได้รับความนิยมมากขึ้น ธุรกิจหลายแห่งจึงเลือกใช้สำหรับโปรเจกต์ของพวกเขา
นักพัฒนาหลายคนชื่นชมความสามารถในการพัฒนาแอปด้วย UI ที่สวยงามแบบ Pixel-perfect ด้วยโครงสร้าง Widget ที่เรียบง่าย ผมเชื่อว่า Flutter เป็นอนาคตของการพัฒนาแอปมือถือ เพราะความเรียบง่ายในการพัฒนา UI และความสามารถในการจัดการ Logic ด้วยภาษา Dart
บทเรียนนี้เน้นสอนพื้นฐาน Flutter ผ่านการสร้างแอปคำนวณค่าทิปง่ายๆ จะครอบคลุมรูปแบบเขียนโค้ดมาตรฐาน รวมถึง Stateful และ Stateless Widget รวมถึง Widget ที่ใช้บ่อยที่สุดในการพัฒนาแอป Flutter
แนวคิดคือเริ่มจากการตั้งค่าโปรเจกต์ Flutter เริ่มต้น จากนั้นนำไปสู่การสร้าง UI และฟังก์ชันพื้นฐาน
มาเริ่มกันเลย!
วิธีตั้งค่าโปรเจกต์ Flutter
เพื่อสร้างโปรเจกต์ Flutter ใหม่ คุณต้องติดตั้ง Flutter SDK บนระบบ สำหรับการติดตั้งที่ง่ายและรวดเร็ว สามารถทำตามเอกสารอย่างเป็นทางการของ Flutter
จำไว้ว่ายังต้องการ Android Studio และ Android SDK หากพัฒนาแอปสำหรับแพลตฟอร์ม Android
หลังตั้งค่าเสร็จแล้ว ตามเอกสาร ให้รันคำสั่ง Flutter ต่อไปนี้ในเทอร์มินัล:
flutter create tipCalculatorคำสั่งนี้จะดาวน์โหลดและตั้งค่าโปรเจกต์ Flutter เริ่มต้นให้อัตโนมัติ ตอนนี้คุณสามารถเปิดโปรเจกต์ใน IDE Visual Studio Code ได้
หากมีอุปกรณ์จำลองหรือสมาร์ทโฟนจริงเชื่อมต่ออยู่ สามารถรันคำสั่งต่อไปนี้เพื่อเปิดแอป:
flutter runอีกวิธีหนือคือกด F5 บนคีย์บอร์ด จะแสดงเมนูใน VSCode ให้เลือกอุปกรณ์ที่ต้องการเปิดแอป
หมายเหตุ: ต้องอยู่ในไฟล์ที่มีนามสกุล .dart จึงจะรันคำสั่งนี้ได้สำเร็จ
Build และรันด้วยคำสั่งข้างต้นหรือ F5 จะได้เทมเพลตเริ่มต้นใน Emulator/อุปกรณ์จริง:

ตอนนี้คุณควรมีแอป Flutter ทำงานได้แล้ว
มาดูรายละเอียดไฟล์โปรเจกต์หลัก main.dart
ในไฟล์ main.dart มีออบเจ็กต์คลาส 2 ตัว หนึ่ง extends Stateful Widget อีกตัว extends Stateless Widget หมายความว่าอะไร?
- Stateful Widget: คลาสที่เก็บ State ของแอป State สามารถเปลี่ยนแปลงและกระตุ้นการ Render Widget ในคลาสนี้ มีส่วนทำให้เกิดการเปลี่ยนแปลง State แบบไดนามิก
- Stateless Widget: คลาสที่ไม่เก็บ State ใดๆ แสดง Widget View ที่ไม่เปลี่ยนแปลง ไม่มีส่วนทำให้เกิดการเปลี่ยนแปลง State แบบไดนามิก
ไฟล์ main.dart ยังมีฟังก์ชัน main() ที่เรียกคลาส MyApp ภายในเมธอด runApp เพื่อเริ่มต้นแอป Flutter บนอุปกรณ์
วิธีสร้าง UI ของแอปคำนวณค่าทิป
เพื่อเริ่มสร้าง UI เราต้องลบทุกอย่างภายในคลาส MyHomePageState ที่มาด้วยค่าเริ่มต้น
หลังลบเสร็จ เราจะคืนค่า Widget Scaffold เรียบง่ายจากฟังก์ชัน build
Widget Scaffold ให้ Property เพื่อเพิ่ม appBar และ body ตอนนี้เพิ่ม App Bar เรียบง่าย ดูโค้ดด้านล่าง:
class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title:Text('Tip Calculator', style: TextStyle(color: Colors.black87),), ), body: Container() ); } }Build และรันหลังเพิ่ม appBar ด้วย Widget AppBar และ Property title จะได้ผลลัพธ์ใน Emulator:

หมายเหตุ: Flutter มี Hot Reload เมื่อบันทึกไฟล์ Dart การเปลี่ยนแปลงจะสะท้อนใน Emulator ทันที
ขั้นตอนที่ 1: ออกแบบ App Bar
เราจะปรับแต่ง Widget AppBar ด้วย Property ต่างๆ ดูโค้ดที่ปรับแล้ว:
appBar: AppBar( title: Text('Tip Calculator', style: TextStyle(color: Colors.black87),), centerTitle: true, elevation: 0.0, backgroundColor: Colors.white70, ),Build และรัน จะได้ผลลัพธ์ใน Emulator:

เราใช้ Property พื้นฐานของ Widget AppBar ได้แก่
elevationควบคุมเอฟเฟกต์เงาใน App Bar คล้าย z-indexcenterTitleจัดตำแหน่ง Title ให้อยู่กึ่งกลาง- เปลี่ยนสีพื้นหลังเป็นสีขาว
ขั้นตอนที่ 2: ออกแบบ Scaffold Body
จนถึงตอนนี้มีแค่ Widget Container ว่างใน Property body ตอนนี้จะเพิ่ม Property และ Child Widget ให้ Container ตามโค้ดด้านล่าง:
body: Container( color: Colors.white70, padding: const EdgeInsets.all(16.0), child: Center( child: Form( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ ], ), ), ),Build และรัน จะได้ผลลัพธ์ใน Emulator:

เห็นได้ว่าพื้นหลัง Body เปลี่ยนเป็นสีขาว เพิ่ม padding พร้อม Widget Center เป็น Child เพื่อจัด UI ให้อยู่กึ่งกลาง
Widget Center มี Child เป็น Widget Form สำหรับสร้างฟอร์มพร้อม Text Field
สำคัญคือ Widget Column เป็น Child ของ Form ให้ Property children เป็น Array ของ Widget ที่จะแสดงแนวตั้งบนหน้าจอ
ขั้นตอนที่ 3: กำหนดค่าคงที่และตัวแปร
ก่อนสร้าง Element ของฟอร์ม (รวม Text Field) ต้องกำหนดค่าคงที่เพื่อจัดการ Input จากช่องกรอกข้อมูล
ดูค่าคงที่และตัวแปรที่ต้องการในโค้ดด้านล่าง:
// จำนวนเงินบิลเริ่มต้น static const defaultBillAmount = 0.0; // เปอร์เซ็นต์ทิปเริ่มต้น static const defaultTipPercentage = 15; // TextEditingController ติดตามการเปลี่ยนแปลงจำนวนเงินบิล final _billAmountController = TextEditingController(text: defaultBillAmount.toString()); // TextEditingController ติดตามการเปลี่ยนแปลงเปอร์เซ็นต์ทิป final _tipPercentageController = TextEditingController(text: defaultTipPercentage.toString()); // เก็บค่าล่าสุดของจำนวนเงินบิลที่คำนวณได้ double _billAmount = defaultBillAmount; // เก็บค่าล่าสุดของเปอร์เซ็นต์ทิปที่คำนวณได้ int _tipPercentage = defaultTipPercentage;ในโค้ดข้างต้นใช้เมธอด TextEditingController ซึ่งช่วยจัดการ Text Input ใน Widget TextFormField ที่จะสร้างต่อไป ซึ่ง Initialize ด้วยค่าเริ่มต้น
ขั้นตอนที่ 4: เพิ่มช่องกรอกข้อมูลฟอร์ม
ตอนนี้จะเพิ่มช่องกรอกข้อมูล 2 ช่องด้วย Widget TextFormField
ขณะใช้ Widget นี้ ต้องกำหนด Property controller ให้ตรงกับตัวแปร Controller ที่กำหนดไว้ ดูโค้ดด้านล่าง:
body: Container( color: Colors.white70, padding: const EdgeInsets.all(16.0), child: Center( child: Form( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ TextFormField( key: Key("billAmount"), controller: _billAmountController, keyboardType: TextInputType.numberWithOptions(decimal: true), decoration: InputDecoration( hintText: 'Enter the Bill Amount', labelText: 'Bill Amount', labelStyle: TextStyle( fontSize: 25, letterSpacing: 1, fontWeight: FontWeight.bold ), fillColor: Colors.white, border: new OutlineInputBorder( borderRadius: new BorderRadius.circular(20.0), ), ), ), SizedBox(height: 25,), TextFormField( key: Key("tipPercentage"), controller: _tipPercentageController, keyboardType: TextInputType.number, decoration: InputDecoration( hintText: 'Enter the Tip Percentage', labelText: 'Tip Percentage', labelStyle: TextStyle( fontSize: 25, letterSpacing: 1, fontWeight: FontWeight.bold ), fillColor: Colors.white, border: new OutlineInputBorder( borderRadius: new BorderRadius.circular(20.0), ), ), ), ], ), ), ), ),เรากำหนด Property keyboardType เพื่อแสดงคีย์บอร์ดประเภทที่ต้องการเมื่อผู้ใช้กดที่ช่องกรอก
ยังมี Property decoration สำหรับจัดสไตล์ช่องกรอกด้วย Widget InputDecoration
ใน Widget InputDecoration มี Property หลายอย่างช่วยแสดง Placeholder Text และ Label เหนือช่องกรอก ยังใช้ Property border แสดงขอบโค้งมน
Build และรัน จะได้ผลลัพธ์ใน Emulator:

ขั้นตอนที่ 5: เพิ่ม Event Listener และ Function
เนื่องจากจะคำนวณค่าทิปทันทีที่ผู้ใช้กรอกจำนวนเงินบิลหรือเปอร์เซ็นต์ จึงต้องฟังการเปลี่ยนแปลงในช่องกรอกข้อความ
เพิ่ม Event Listener ให้ Controller ด้วยเมธอด addListener
เมื่อมีการเปลี่ยนแปลงในช่องกรอก ต้องการ Trigger Function เพื่ออัปเดตจำนวนเงินบิลและเปอร์เซ็นต์ทิป
ใช้ Function ที่จำเป็นร่วมกับเมธอด setState ช่วย Render UI ใหม่ทันทีที่มีการเปลี่ยนแปลง
หมายเหตุ: เมธอดsetStateกระตุ้นการรันเมธอดbuildใหม่
ดูโค้ดด้านล่าง:
@override void initState() { super.initState(); _billAmountController.addListener(_onBillAmountChanged); _tipPercentageController.addListener(_onTipAmountChanged); } _onBillAmountChanged() { setState(() { _billAmount = double.tryParse(_billAmountController.text) ?? 0.0; }); } _onTipAmountChanged() { setState(() { _tipPercentage = int.tryParse(_tipPercentageController.text) ?? 0; }); }ที่นี่มีเมธอด initState ซึ่งรันทันทีที่เข้าสู่หน้าจอนี้ของแอป จึงเพิ่ม Event Listener ทันทีที่เข้าหน้าจอ
ขั้นตอนที่ 6: เพิ่มส่วนแสดงจำนวนเงิน
กลับไปที่ UI Widget จะเพิ่มส่วน Amounts ใต้ช่องกรอกข้อมูล ภายใน Widget Column
ใช้ Widget SizedBox สร้างระยะห่างระหว่าง Widget ดูโค้ดด้านล่าง:
), SizedBox(height: 20,), Container( margin: EdgeInsets.all(15), padding: EdgeInsets.all(15), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.all( Radius.circular(15), ), border: Border.all(color: Colors.white), boxShadow: [ BoxShadow( color: Colors.black12, offset: Offset(2, 2), spreadRadius: 2, blurRadius: 1, ), ], ), child: Column( children: [ Text("Tip Amount"), Text("Total Amount") ], ), ),ที่นี่มี Container พร้อมการตกแต่ง Style Child Property มี Widget Column อีกตัวหนึ่ง จัด Widget Text 2 ตัวแนวตั้ง
Build และรัน จะได้ผลลัพธ์ใน Emulator:

ขั้นตอนที่ 7: สร้าง Stateless Widget แยกต่างหากเพื่อแสดงจำนวนเงิน
เนื่องจากต้องการแสดง Tip Amount และ Total Amount พร้อมสไตล์เล็กน้อย จะไม่มี State แต่ขึ้นอยู่กับค่าที่ส่งมาจาก Stateful Widget
ดูโค้ดคลาส Stateless Widget AmountText ด้านล่าง:
class AmountText extends StatelessWidget { final String text; const AmountText( this.text, { Key key, }) : super(key: key); @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(8), child: Text(text.toUpperCase(), style: TextStyle(fontWeight: FontWeight.bold, color: Colors.blueAccent, fontSize: 20)), ); } }ใช้ Constructor Class รับค่า Text ที่จะแสดง เมธอด build คืนค่า Widget Container พร้อม padding และ Widget Text เป็น Child
พร้อม Widget AmountText แล้ว ตอนเรียกใช้ใน Stateful Widget
เพิ่ม Widget ภายใน Widget Column ที่กำหนดไว้ก่อนหน้านี้แทน Widget Text ธรรมดา ส่งค่า Text ที่ต้องการ
ดูโค้ดด้านล่าง:
Container( margin: EdgeInsets.all(15), padding: EdgeInsets.all(15), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.all( Radius.circular(15), ), border: Border.all(color: Colors.white), boxShadow: [ BoxShadow( color: Colors.black12, offset: Offset(2, 2), spreadRadius: 2, blurRadius: 1, ), ], ), child: Column( children: [ AmountText( 'Tip Amount: ${_getTipAmount()}', key: Key('tipAmount'), ), AmountText( 'Total Amount: ${_getTotalAmount()}', key: Key('totalAmount'), ), ], ), ),ส่ง Function ไปใน Widget AmountText Function คืนค่า Tip Amount และ Total Amount ตามโค้ดด้านล่าง:
_getTipAmount() => _billAmount * _tipPercentage / 100; _getTotalAmount() => _billAmount + _getTipAmount();สุดท้าย ต้องยกเลิก Controller เมื่อออกจาก View ใช้ฟังก์ชัน dispose ในตัว ซึ่งรันขณะออกจากหน้าจอปัจจุบัน
ในเมธอดนี้ เรียก Controller ด้วยเมธอด dispose เพื่อยกเลิก Text Input Controller จะหยุดฟังการเปลี่ยนแปลงในช่องกรอก
ดูฟังก์ชัน dispose ด้านล่าง:
@override void dispose() { // ทำให้แน่ใจว่าไม่รั่วไหล ยกเลิก TextEditingController ที่ใช้ // เมื่อ Widget ถูกลบออกจากหน่วยความจำ _billAmountController.dispose(); _tipPercentageController.dispose(); super.dispose(); }Build และรัน เพื่อดูผลลัพธ์สุดท้ายของแอปคำนวณค่าทิปใน Demo ด้านล่าง:

สังเกตว่าค่าทั้งสองจำนวนเปลี่ยนไปตาม Input ในช่องกรอก
จบบทเรียนนี้แล้ว คุณสร้างแอปคำนวณค่าทิปง่ายๆ ด้วยเฟรมเวิร์ก Flutter และ Dart เรียบร้อยแล้ว
ขั้นตอนต่อไป
จุดประสงค์หลักคือสอนรูปแบบการเขียนโปรแกรมพื้นฐานในเฟรมเวิร์ก Flutter ผ่านการสร้างแอปคำนวณค่าทิปง่ายๆ
ยังมี Widget และฟีเจอร์น่าสนใจอีกมากมายให้สำรวจ สามารถเปลี่ยน UI ของแอปได้ พร้อมรักษา Component ฟังก์ชันเดิมไว้
โดยรวม Flutter ทำให้การออกแบบ UI ที่ซับซ้อนกลายง่ายด้วยรูปแบบ Widget สร้าง UI ที่ยอดเยี่ยมได้เพียงแค่ซ้อน Widget ผ่าน Property Child
ขั้นตอนต่อไปอาจใช้ Mechanism การนำทางของ Flutter ไปหน้าจออื่น Flutter ยังทำให้การเพิ่ม Custom Drawer Menu และ Bottom Tabs ง่ายขึ้น
นี่คือจุดเริ่มต้นของการพัฒนา Flutter ยังมีอะไรอีกมาก เราต้องสำรวจและเขียนโค้ดต่อไป
Demo ของโปรเจกต์ทั้งหมดมีบน Codepen
สามารถหาแรงบันดาลใจสำหรับแอป Flutter ของคุณจากแอปที่มีอยู่แล้ว
Happy Coding!