web analytics

ทำ Custom Segmented Control ใน Flutter ตอนที่ 1

สวัสดีครับ บล็อกนี้ขอกลับมาเขียนเรื่องพื้นฐานง่ายๆ สนุกๆกันครับ นั่นก็คือ Segmented Control หรือก็คือ ตัวเลือกเมนู คล้ายๆกับแท็บนั่นเอง สามารถนำไปปรับใช้ได้หลายๆแบบ เช่น ทำเป็น Bottom navigation menu ก็ได้เช่นกัน

Cupertino Segmented

Segmented Control ใน Flutter Widget สำเร็จรูปให้ใช้งานอยู่ 1 ตัว นั่นคือ CupertinoSegmentedControl หน้าตาเหมือนกับใน iOS ที่เราอาจจะเห็นบ่อยๆ โดยเจ้าตัวนี้จะอยู่ใน package cupertino

วิธีการใช้งานง่ายมาก คือกำหนด groupValue (ค่าปัจจุบัน) ให้มัน และ chrilden คือ Map ของค่าแต่ละ segment กับ Widget

Custom Segmented

แต่ทว่า ตัว CupertinoSegmentedControl นั้นปรับแต่งได้น้อย ได้แค่กำหนดสีนิดหน่อย ดังนั้นผมจึงแนะนำว่า ให้เราสร้าง Segmented Control ของเราเองเลยดีกว่า ซึ่งวิธีการทำนั้นไม่ได้ยากเลย

โดยเราจะสร้าง Segmented Control แบบ 3 segment วิธีการคือ สร้าง Container ตัวนอกเป็นกรอบใหญ่ จากนั้นกำหนด Row ข้างใน Row กำหนด Container 3 ตัวให้ขนาดความกว้างเท่าๆกัน นั่นก็คือใช้ Expanded ครอบ Container แต่ละตัว

แล้วก็กำหนด border radius ตามต้องการ จะได้ประมาณนี้ พร้อมกับผมสมมุติว่า ช่องแรกถูกเลือกไว้ เลยกำหนดสีให้มันแตกต่าง

ได้ segment control หน้าตาประมาณนี้

ต่อไปเราจะกำหนด ให้ Segment แต่ละตัวสามารถถูกเลือกได้ นั่นก็คือจะมี State มาเกี่ยวข้อง ผมประกาศให้ indexSegmentSelected = 1 ก็คือให้ ช่องที่ถูกเลือกคือช่องที่ 2 (ให้ index เริ่มที่ 0 ละกัน)

จากนั้น refactor code นิดหน่อย เพราะว่าส่วนของ Container สำหรับ segment แต่ละอัน มันคล้ายกัน ต่างกันแค่สถานะและข้อความเท่านั้น

ตอนนี้ segment ของเราจะขยับตามค่า _indexSegmentSelected แล้ว

จากนั้นเพิ่ม GestureDetector เพื่อเพิ่ม onTap ให้ segment เมื่อกดแล้วก็จะ setState กำหนดค่า _indexSegmentSelected

ผลคือสามารถกดที่ segment และขยับไปตามที่ต้องการได้แล้ว

Segmented Control + PageView

มาลองประยุกต์ใช้งานเพิ่มเติมกันครับ เช่นใช้งานร่วมกับ PageView + PageController ก็คือการกดที่ segment แล้วให้ page view เปลี่ยนหน้าไปตาม segment

สร้างตัวแปร PageController

จากนั้นเพิ่ม Page View และกำหนด Page Controller ให้มัน โดยผมจะให้ Page View อยู่ด้านล่างของ Segmented Control และขยายเต็มพื้นที่โดยใช้ Expanded

ลองรัน ก็จะได้ PageView และสามารถเลื่อนหน้าได้ตามที่เรากำหนด children ของ PageView ไว้

แต่ว่าตอนนี้ยังไม่สามารถกดเปลี่ยน page จาก segment ได้ ดังนั้นต้องเพิ่มให้ onTap ของ segment เมื่อกดแล้วให้ PageController animateTo ไปที่หน้านั้น

จากนั้น ปัญหาต่อมาที่เจอคือ เมื่อเราเลื่อนที่ PageView ซ้าน-ขวา ตัว Segment ไม่ได้อัพเดทตาม นั่นก็เพราะเรายังไม่ได้เพิ่มคำสั่งในฝั่งของ PageView นั่นเอง

ซึ่ง PageView จะมีคำสั่ง onPageChanged อยู่ โดยคือคำสั่งนี้จะถูกเรียกเมื่อ page ถูกเปลี่ยน ดังนั้นเราก็เพิ่ม setState ให้อัพเดท ค่า _indexSegmentSelected ให้ถูกต้องตาม page

ปัญหา Animate page

มาเก็บตกปัญหากันครับ จาก code ที่เราเขียน เราได้เชื่อม Segment กับ PageView ทั้ง 2 ฝั่ง คือ onTap ของ segment และ onPageChanged ของ PageView

ปัญหาตอนนี้ที่เจอก็คือเมื่อกด segment ข้ามจาก 1 ไป 3 จะพบว่ามันแสดง Animate ซ้ำซ้อน นั่นก็เพราะ เมื่อเรากดเปลี่ยน segment ก็จะทำการ animateTo ไปยัง page ปลายทาง และนั่นก็ทำให้มีการเรียก onPageChanged ของ PageView ด้วย ทำให้ มันมีการพยายามเปลี่ยนหน้าซ้ำกันถึง 2 ครั้ง

ดังนั้น วิธีแก้แบบง่ายๆ ก็คือ เราจะสร้าง method กลางอันนึง ให้ onTap ของ Segment และ onPageChnaged เรียก method นี้ และ กรณีของ onPageChanged เราจะไม่ทำ animate เราจะทำ animateTo เฉพาะ กรณีที่กดที่ segment เท่านั้น

ที่ onTap ของ Segment ก็เรียก changePage(index) กำหนดให้ แสดง animate default เป็น true

ส่วนที่ onPageChanged เรียก changePage(index, animate: false) คือเปลี่ยนหน้าเฉพาะไม่ต้อง animate

ลองดูผลงาน Animate ไม่ซ้ำแล้ว

Viewport Fraction

ลองเพิ่มลูกเล่นของ Page Controller โดยการกำหนด Viewport Fraction ซึ่งเราจะเห็นในการแสดงพวก banner เช่น กำหนด viewportFraction = 0.8 จะทำให้ page หลักที่แสดงของเรามีขนาด 80% ทำให้ที่ว่างที่เหลือจะแสดง page อื่นๆเพิ่มเข้ามาได้

Animated

ลองเพิ่ม Animated ให้กับ Page ของเราอีกสักหน่อย โดยใช้ Animated Widget ในตัวอย่างนี้ใช้ AnimatedPadding กับ AnimatedOpacity หรือก็คือ page จะค่อยๆขยาย และค่อยๆ fade นั่นเอง

หากสนใจเรื่อง Animated Widget สามารถอ่านเรื่อง Animation ใน Flutter ของผมได้ครับ ผมเขียนเอาไว้ 2 ตอน

สรุป

ในบทความนี้เราได้ลองเล่น Segmented Control ที่ลอง custom เอง และการนำมาใช้กับ PageView + PageController สามารถนำไปประยุกต์ใช้ได้หลายแบบ สำหรับตอนที่ 2 นั้น เราจะลองนำ Custom Paniter มาใช้งานร่วมกับ Segmented Control กันครับ จะช่วยให้ Segmented Control ของเราดูใหลลื่นมากขึ้น

หวังว่าบทความนี้จะมีประโยชน์กับผู้เริ่มต้นครับ ขอบพระคุณครับ สวัสดีครับ (:

สำหรับ source code สามารถลองเล่นได้ที่ DartPad ตามลิงค์ด้านล่างนี้เลยครับ
https://dartpad.dev/7a9b1708f5aac03640ecb996c6b96bca

อ่านต่อ ตอนที่ 2