web analytics

บันทึกการเรียน Flutter ของผม ตอนที่ 4

cover

สวัสดีครับ บทความนี้เป็นบันทึกการเรียน Flutter + ภาษา Dart ของผม ตอนนี้พอจะเริ่มเข้าใจหลักการแล้วละ บทความนี้ก็เลยมาบันทึกการเรียน และการทำความเข้าใจเกี่ยวกับ Flutter ของผมครับ ซึ่งบทความนี้จะลองทำความเข้าใจกับ Codelabs ของ Flutter ที่หัวข้อ Building Beautiful UIs with Flutter ครับ โดยตัว Codelabs จะพาทำแอปแชท แล้วก็สอนการตกแต่ง UI และการ Custom UI ตาม Platform ครับ อ้อแล้วก็รอบนี้ผมจะลองรันใน iOS ด้วยเลย

สามารถดู Codelabs ของ Flutter ได้ที่ลิงค์นี้เลย

Building Beautiful UIs with Flutter
https://codelabs.developers.google.com/codelabs/flutter/#0

แอปที่เราจะทำ

แอปนี้เป็นแอทหน้าจอแชทแบบง่ายๆ เพื่อฝึกการทำ UI ครับ สังเกตว่า UI ของ Android จะแตกต่างกับ iOS ทั้งสีธีม แล้วก็ไอคอนครับ
เดี๋ยวเราลองมาทำ Codelab กัน

ภาพจาก Flutter

1

 

เริ่มต้น

ที่ main.dart สร้าง Widget สำหรับ app ของเราอันนึง คือ FriendlychatApp โดยภายในจะใช้งาน MaterialApp
ซึ่งใน MaterialApp ตรงพารามิเตอร์ home ก็ค่อยเรียกใช้ Widget ChatScreen ที่ทำแบบนี้ก็เพราะว่าจะได้เปลี่ยนธีมของแอปง่ายๆ คือใช้ความสามารถของ MaterialApp นั่นเอง
ส่วนที่ ChatScreen ก็ใส่แค่ AppBar ไว้ก่อน

 

ลองรัน จะได้หน้าเปล่าๆกับ AppBar

2

 

 

ทำช่องพิมพ์ข้อความ

ต่อมาก็มาทำช่องพิมพ์ข้อความ หรือเรียกว่า TextField (ใน Android จะเรียก EditText) โดย UI ตรงนี้จะกำหนดให้มีไอคอนรูปส่งจดหมาย ด้านขวาด้วย

3a

พอมาถึงตรงจุดนี้ เราก็จะคิดขึ้นได้ว่า หน้า Chat มันก็ต้องมีการเปลี่ยนแปลง เช่นพิมพ์ข้อความไปหน้าจอก็ต้องอัพเดท ดังนั้นก็ต้องใช้ StatefulWidget แทน StatelessWidget
พอใช้ StatefulWidget ก็ต้องมี State และมี createState()

 

ดังนั้นต้องทำคลาส ChatScreenState แล้วย้ายส่วนของ build ใน ChatScreen มาใน ChatScreenState  แทน
จากนั้นก็เพิ่มส่วนของช่องกรอกข้อความ ใน Flutter เรียกว่า TextField โดยมันจะมาคู่กับ TextEditingController
ก่อนอื่นประกาศตัวแปร TextEditingController ก่อน แล้ว เขียน method สำหรับแสดง TextField ใน Flutter เรียกกระบวนการนี้ว่า TextComposer
โดยใน method ก็มีการสร้าง container แล้ว ข้างในก็ค่อยใส่ TextField เพื่อจะได้กำหนด margin ได้ จะได้สวยขึ้น

 

ลองรัน ก็จะได้ช่องพิมพ์ข้อความแล้ว และก็มี margin ด้านซ้ายขวาเล็กน้อย

4

 

ต่อมาก็เพิ่มไอคอน รูปส่ง ไปด้านขวาของช่องกรอกข้อความ วิธีก็คือการใช้ Row
ซึ่งมันก็เหมือนการเอา View มาต่อกัน ส่วนช่องข้อความก็ใช้คลาส Flexible เพื่อให้มันแสดงยาวเต็มจอ

อ้อแล้วก็กำหนด controller , submit ให้กับ TextField ด้วย

 

ลองรัน จะมีไอคอนเพิ่มมาแล้ว และอยู่ด้านขวาสุด

5

 

เปลี่ยนสีไอคอน

ทำการปรับแต่งสีของไอคอน ตรงนี้จะใช้ IconThemeData
โดยเอา container ทั้งหมดของ view ยัดมาเป็น child ซึ่งมันจะเหมือนกันเปลี่ยนไอคอนทั้งหมดใน child เป็นสีนั้น
แบบนี้ก็สะดวกดี

 

ลองรัน ไอคอนเปลี่ยนสีแล้ว

6

ทำส่วนแถวแสดงข้อความ

ออกแบบแถวสำหรับแสดงข้อความแชทประมาณนี้

7

 

ทำ class สำหรับแสดงข้อความชื่อ ChatMessage
ภายใน build ก็ใส่ Widget ต่างๆ แล้วเราสามารถวาดวงกลมโดยใช้ CircleAvatar ได้เลย สะดวกดี
ถามว่า การเขียนโค้ดตรงนี้พวก Widget ให้มันเป็นไปตามที่ ออกแบบยากไหม ผมว่ายาก 5555
คงต้องจำ attr แล้วก็ Widget สักหน่อย อันนี้อยู่ที่การฝึกฝนละนะ

 

เพิ่มตัวแปร list สำหรับเก็บ ChatMessage ใน ChatScreenState

 

ปรับแต่ง method การ submit ของ TextField ให้มีการเพิ่มข้อความลงไปใน list
แล้วก็ทำใน setState เพราะว่าจะได้อัพเดทหน้าจอ

 

ทำ ListView แสดงข้อความ

ทำการเพิ่ม ListView สำหรับแสดงข้อความแชท
สังเกตว่ามีการใช้ Flexible ครอบ LsitView เพราะจะได้แผ่ให้ ListView บีบ TextField ไปอยู่ด้านล่างจอนั่นเอง
แล้วก็ใช้ ListView reverse = true เพราะ ให้แสดงข้อความจากด้านล่าง

 

ลองรัน แล้วพิมพ์ข้อความ

8

 

ทำ Animation

ต่อมา ลองทำ animation ให้กับ ข้อความแชทกันครับ
เริ่มจาก นำ TickerProviderStateMixin โดยใช้ with

สำหรับ keyword “with” นี้ ผมก็สงสัยว่าคืออะไร
https://stackoverflow.com/questions/21682714/with-keyword-in-dart

A mixin refers to the ability to add the capabilities of another class or classes to your own class, without inheriting from those classes. The methods of those classes can now be called on your class, and the code within those classes will execute. Dart does not have multiple inheritances, but the use of mixins allows you to fold in other classes to achieve code reuse while avoiding the issues that multiple inheritances would cause.

With คือการเรียกใช้คลาสอื่นเพิ่มเข้ามาเป็นเพิ่มความสามารถของคลาสอื่นมาที่คลาสของเรา โดยไม่มีการสืบทอด ทำให้ method ของคลาสนั้นถูกเรียกได้ในคลาสของเรา ซึ่งใน Dart ไม่มีการสืบทอดแบบหลายคลาสนะ (มีใน C++) แต่ว่าการใช้ mixin จะอนุญาตเราทำสิ่งที่คล้ายๆ การสืบทอดแบบหลายทำได้ เรียกคลาสที่ถูกเพิ่มความสามารถว่า  Mixin

โอเค พอเราเพิ่ม TickerProviderStateMixin เข้ามาให้กับ ChatScreenState ผ่าน with แล้ว
ทำให้ ChatScreenState สามารถเรียกใช้ method และมี context ของ TickerProviderStateMixin ด้วย

จากนั้นประกาศตัวแปร AnimationController ให้กับ ChatMessage และเพิ่มให้ constuctor ด้วย
AnimationController จะดูแลพวกเวลา animate , ทิศทางของ animation

 

ที่คำสั่งของปุ่ม submit ก็แก้ไขให้ ChatMessage รองรับ AnimationController ให้กำหนดพวกเวลาของ animation
และตรงนี้เองที่พารามิเตอร์ vsync ต้องการ  TickerProviderStateMixin เราก็ใช้ this ได้เลย

 

ถ้าไม่ใส่ with TickerProviderStateMixin ก็จะมาแดงตรง vsync

9

 

ต่อมาก็มากำหนด animation ว่าต้องการแสดงกับ Widget ไหน ซึ่งในที่นี้คือ ทั้ง container ใน ChatMessage
จะใช้ SizeTransition ครอบ container ทั้งหมด แล้วก็กำหนด sizeFactor ว่าต้องการ curve แบบไหน

นอกจาก SizeTransition ที่มันจะทำการขยาย หด แล้วอีกตัวที่ใช้ได้คือ FadeTransition มันจะเล่นกับความจาง เข้ม เล่นกับพวก opacity แทน

 

มีให้เลือก curve หลายแบบ

10

 

จากนั้นเพื่อสุขลักษณะที่ดี ทาง Flutter บอกว่าใช้เสร็จแล้วให้ คืน memmory ด้วย
ก็ใช้ override dispose() แล้วก็วนลูป animationController ทำการ dispose ให้หมด

 

 

ลองรัน ก็จะได้ animation แล้ว

a1

 

ป้องกันการส่งข้อความเปล่า

เพิ่มตัวแปร boolean เพื่อบอกว่าตอนนี้มีคำในช่องกรอกข้อความอยู่มัย

 

เพิ่ม onChange ของ TextField ถ้ามีข้อความอยู่ก็ให้ _isCompsing เป็น true
แล้วแก้ไข onPressed ของ Icon ส่ง ถ้า _isCompsing = true ค่อยเรียกใช้ submit

ดังนั้นต้องจะเห็นว่า ใน onChange ตัว _isComposing ต้องทำใน setState เพราะว่า ต้องการอัพเดท onPressed ใหม่ให้เป้นไปตมค่า _isCompsing นั่นเอง

 

หลังจาก submit ก็คืนค่า _isComposing เป็น false อีกครั้ง

 

หลายคนอาจสงสัยเหมือนผมตอนแรกว่า ทำไม ไม่ทำแค่เช็ค if เช็ค _isComposing ใน submit ถ้าเป็น true ก็ค่อยไปสร้าง ChatMessage ถ้า false ก็ไม่ต้องทำอะไร แต่จริงๆที่แล้วที่ต้องใช้ setState และการกำหนด null ให้ onPressed เมื่อไม่มีข้อความ
เป็นเพราะว่าต้องการให้ ปุ่มไอคอนส่งเป็นสีเทาเมื่อใช้งานไม่ได้ และเมื่อมีข้อความค่อยมีสีฟ้า

a2

 

 

ปัญหาข้อความไม่ขึ้นบรรทัดใหม่

ตอนนี้เมื่อเราพิมพ์ข้อความยาวๆ มันจะแสดงได้แค่บรรทัดเดียว

11

 

ให้เพิ่ม Expanded ครอบ Column

13

 

ลองรัน พิมพ์ข้อความยาวๆอีกครั้ง

12

 

การปรับแต่งตาม Platform

ทีนี้จะ แยกว่า Android – iOS ให้คนละ ธีมกัน เพื่อแสดงให้เหมาะกับ Platform นั้น

ให้ import foundation.dart เข้ามา

 

ประการตัวแปร themeData กำหนดสี
แล้วเอาธีมไปใส่ที่ พารามิเตอร์ theme ของ MaterialApp นี่แหละคือสิ่งที่ Widget MaterialApp ช่วยให้เป็นเรื่องง่าย
การแยก Platform ก็ใช้ผ่านตัวแปร defaultTargetPlatform ได้เลย ซึ่งตัวนี้จะมากับ foundation.dart นั่นเอง

หรือพิมพ์แค่ defaultTargetPlatform แล้วจะให้มัน import ให้ก็ได้

14

 

ลองรันบน Android

15

 

เดี๋ยวของ iOS ต้องไปลองใน Macbook แล้วจะมาเขียนเพิ่ม

 

โค้ดของโปรเจคนี้

https://gist.github.com/benznest/7373b7376c1c8df6a6e0c5cc23c51499

 

สรุป

บทความนี้ทำให้ผมได้เรียนรู้เยอะมาก อย่างแรกเลยคือการทำ UI , การใช้งาน TextField , การ custom ListView , การทำ Animation และการแยก UI ตาม Platform ครับ

ตอนนี้จากทั้งหมด 4 ตอนจนถึงตอนนี้ ผมเห็นภาพการพัฒนาแอปด้วย Flutter มากขึ้นมากๆ บทความหน้าจะเรียนเรื่องอะไรอีก จะมาบันทึกต่อครับ