เบื้องหลังการสร้าง UI สำหรับคูปองเพื่อใช้งานในแอป LINE MAN ที่เป็นมิตรต่อเพื่อนร่วมทีม — ตอนที่ 3

Akexorcist
Life@LINE MAN Wongnai
4 min readJan 29, 2021

--

หลังจากที่ได้เห็นเบื้องหลังในการคำนวณเส้น Path ที่ใช้สร้าง UI Component เป็นรูปคูปองกันไปแล้ว ต่อไปก็ถึงเวลาของการทำให้โค้ดเหล่านั้นสามารถนำไปใช้งานกับโค้ดบนแอนดรอยด์ได้อย่างเรียบเนียนไร้รอยต่อกัน

บทความทั้งหมดของชุดนี้

สร้าง View สำหรับคูปอง

การสร้าง UI Component ใด ๆ บนแอนดรอยด์ด้วยสิ่งที่เรียกว่า View ซึ่งเป็นพื้นฐานของ UI ทั้งหมดทั้งมวลบนแอนดรอยด์ ซึ่งจะมีโค้ดพื้นฐานดังนี้

จะเห็นว่าผมได้เตรียม setup(...) ไว้เพื่อใช้กำหนดค่า View Attribute ต่าง ๆ ในทีหลัง และจะต้องกำหนด setWillNotDraw(...) เป็น false เพื่อให้ View ทำการ Render UI ขึ้นมาทันทีที่สร้างขึ้นมา ซึ่งควรใช้คำสั่งเมื่อมีการ Override คำสั่งที่ชื่อว่า onDraw(canvas: Canvas?) เสมอ

นอกจากนี้ setup(...) ยังมี Parameter ที่ส่งเข้ามา 2 ตัวคือ AttributeSet กับ Int เพื่อเรียกค่า View Attribute ต่าง ๆ ที่กำหนดไว้ในตอนที่สร้าง UI

กำหนดรูปแบบของ View Attribute สำหรับใช้กับ Canvas API

การสร้าง UI บนแอนดรอยด์ส่วนใหญ่จะถูกสร้างไว้ใน Layout Resource ที่เขียนด้วย XML ดังนั้นการเตรียม View Attribute ให้กับ View จะช่วยเพิ่มความสะดวกในการนำไปใช้งาน เพราะว่าคนที่นำไปใช้จะได้กำหนด Attribute ต่าง ๆ ผ่าน XML ได้เลย ลดโค้ดที่เกี่ยวกับ UI ในฝั่ง Java หรือ Kotlin ที่ไม่จำเป็นให้น้อยลง เช่น

  • สีพื้นหลังหรือสีเส้นขอบรอบคูปอง
  • รัศมีของมุมโค้งหรือรอยแหว่งครึ่งวงกลมที่ด้านข้างคูปอง

โดย View Attribute จะสัมพันธ์กับ Scope ที่กำหนดไว้ในตอนแรกนั่นเอง

รวมไปถึงการกำหนดว่าจะให้มีรอยแหว่งและมุมโค้งที่ฝั่งไหนบ้าง จะได้นำไปใช้ในรูปแบบอื่น ๆ ได้หลากหลายมากขึ้น

เพื่อให้ง่ายต่อการกำหนดมุมหรือด้านที่ต้องการ ผมจึงออกแบบให้กำหนดค่าเหล่านี้ในรูปแบบของ Bitwise

ครึ่งวงกลมสำหรับขอบแต่ละด้าน

  • Left เป็น 0x01
  • Top เป็น 0x02
  • Right เป็น 0x04
  • Bottom เป็น 0x08

โดยตัวเลขที่แทนค่าให้ในแต่ละด้านจะมองเป็น Flag ที่กำหนดอยู่ในรูปของเลขฐาน 2 จำนวน 4 บิต เมื่อค่าเป็น 1 คือเลือกและ 0 คือไม่เลือก

ด้วยวิธีนี้จะช่วยให้เราสามารถกำหนดค่าเพื่อเลือกด้านที่ต้องการด้วยรูปแบบไหนก็ได้ ด้วยการนำค่าของแต่ละด้านมาทำ Bitwise OR เพื่อรวมค่าเข้าด้วยกัน ไม่ว่าจะเป็น

  • None มีค่าเป็น 0x00
  • Left & Right (Horizontal) มีค่าเป็น 0x01 OR 0x04 หรือ 0x05
  • Top & Bottom (Vertical) มีค่าเป็น 0x02 OR 0x08 หรือ 0x0A
  • All (ทุกด้าน) เป็น 0x01 OR 0x02 OR 0x04 OR 0x08 หรือ 0x0F

มุมโค้ง

  • Top Left เป็น 0x01
  • Top Right เป็น 0x02
  • Bottom Left เป็น 0x04
  • Bottom Right เป็น 0x08
  • None เป็น 0x00
  • Top Left & Top Right (Top) เป็น 0x01 OR 0x02 หรือ 0x03
  • Bottom Left & Bottom Right (Bottom) เป็น 0x04 OR 0x08 หรือ 0x0C
  • Top Left & Bottom Left (Left) เป็น 0x01 OR 0x04 หรือ 0x05
  • Top Right & Bottom Right (Right) เป็น 0x02 OR 0x08 หรือ 0x0A
  • All (ทุกมุม) เป็น 0x01 OR 0x02 OR 0x04 OR 0x08 หรือ 0x0F

เตรียม View Attribute ที่จำเป็นต่อการใช้ Canvas API

ในการสร้าง View Attribute ขึ้นมาเพื่อใช้งานกับ View ของตัวเองโดยเฉพาะ จะต้องสร้างไฟล์ attrs.xml ไว้ใน res/values แบบนี้

จะเห็นว่า coupon_backgroundColor กับ coupon_borderColor สามารถกำหนดเป็น Color หรือ Reference ก็ได้ เพื่อรองรับการกำหนดค่าสีโดยตรงหรือจะกำหนดเป็น Color State List (แยกสีตาม View State) ก็ได้

เวลานักพัฒนาคนอื่นในทีมนำ View ตัวนี้ไปใช้งาน ก็สามารถกำหนดใน Layout Resource ด้วย XML ได้เลย

และการนำค่าที่กำหนดไว้ใน Attribute เหล่านี้ไปใช้คำนวณใน Canvas API ก็จะทำผ่านคลาส AttributeSet ที่กำหนดไว้ในโค้ด Kotlin เมื่อตอนแรกนั่นเอง

ถึงแม้ว่าใน Layout Resource จะกำหนด Dimension ด้วยหน่วย dp ก็ตาม แต่เมื่อใช้คำสั่ง getDimension(...) ก็จะได้ค่าเป็นหน่วย px โดยอัตโนมัติ

เพื่อให้การระบุทิศทางของมุมโค้งหรือครึ่งวงกลมสามารถเรียกใช้งานได้ง่ายขึ้น ผมจึงสร้าง Constant Value ขึ้นมาเตรียมไว้เพื่อให้เรียกใช้งานสะดวกมากขึ้น

และสำหรับตัวแปร

  • backgroundPathColor กับ backgroundPathColorStateList
  • borderPathColor กับ borderPathColorStateList

ที่ต้องสร้างแยกกันแบบนี้ ก็เพื่อรองรับค่าแบบ Color หรือ Color State List ตามที่กำหนดไว้ใน attrs.xml นั่นเอง

เตรียมคลาส Paint เพื่อใช้ใน Canvas API

Paint เป็นคลาสที่ใช้กำหนดรูปแบบของสิ่งที่จะวาดด้วยคำสั่งใน Canvas API ว่าอยากจะให้มีพื้นหลังเป้นสีอะไร มีเส้นขอบหรือไม่ เส้นขอบมีขนาดเท่าไรและเป็นสีอะไร

โดยจะต้องสร้างคลาส Paint เตรียมไว้ 2 ตัวด้วยกัน เพราะใน 1 ตัวจะไม่สามารถกำหนดสีพื้นหลังกับสีเส้นขอบแยกกันได้ จึงต้องสร้าง Paint ตัวแรกสำหรับกำหนดสีพื้นหลัง และอีกตัวสำหรับกำหนดขนาดและสีของเส้นขอบ

ในการตัดสินใจว่าจะเลือกใช้สีแบบ Color หรือ Color State List จะดูว่าตัวแปรที่เก็บค่า Color State List เป็นหลัก ถ้ามีค่าเก็บไว้ก็จะใช้สีที่กำหนดไว้ในนั้น แต่ถ้าไม่มีก็จะใช้สีที่กำหนดไว้ในค่า Color

สำหรับคำสั่ง setupPaint() จะเก็บไว้เรียกใช้งานที่ onDraw(canvas: Canvas?) ในภายหลัง

กำหนด Path เตรียมไว้ให้เรียบร้อย

จากที่ได้อธิบายการคำนวณเพื่อสร้าง Path ให้เป็นรูปคูปองในบทความตอนที่ 2 จึงทำให้คำสั่งของ Path นั้นพร้อมใช้งานแล้ว

เพื่อให้โค้ดไม่ยาวจนเกินไป ผมขอตัดโค้ดที่คำนวณการลากเส้น Path ออกไป เพราะถือว่าได้อธิบายไปเรียบร้อยแล้ว

แต่อย่าลืมว่าจะต้องทำให้มุมโค้งกับครึ่งวงกลมในแต่ละด้านสามารถเลือกได้ว่าจะให้แสดงหรือไม่ จึงต้องเพิ่มเงื่อนไขในการกำหนดค่าเข้าไปดังนี้

  • Corner Radius : ถ้าด้านไหนไม่แสดงมุมโค้งให้กำหนดค่าเป็น 0 แทน cornerRadius
  • Semi Circle Radius : ถ้าด้านไหนไม่แสดงครึ่งวงกลมให้ข้ามคำสั่งนั้นไป

สำหรับค่าที่กำหนดว่าจะให้แสดงด้านไหนบ้างจะถูกเก็บไว้ในตัวแปรที่ชื่อ cornerDirection กับ semiCircleDirection โดยจะใช้ Bitwise เข้ามาช่วยในจุดนี้เช่นกัน

ยกตัวอย่างเช่น ให้ semiCircleDirection มีค่าเป็น 0x0A และอยากรู้ว่าทิศทางที่กำหนดไว้ในนั้นมี SEMI_CIRCLE_BOTTOM อยู่ด้วยหรือไม่

คำสั่งดังกล่าวจะเป็นการใช้ Bitwise AND เพื่อทำให้เหลือแค่ตำแหน่งของบิตที่ต้องการ แล้วเทียบว่าในตำแหน่งของบิตนั้นมีค่าตรงกับที่ต้องการเปรียบเทียบหรือไม่

จึงสามารถนำมาสร้างเป็นคำสั่งแบบนี้ได้

เตรียมทุกอย่างสำหรับ Canvas API ครบทั้งหมดแล้ว

สิ่งที่ต้องทำใน onDraw(canvas: Canvas?) จะเป็นการใช้คำสั่ง drawPath(path: Path, paint: Paint) เพื่อวาดพื้นหลังและเส้นขอบให้เป็นรูปคูปอง

อย่าลืมทำให้ View รองรับ State Changes ด้วย

เพื่อให้เป็นไปตาม Best Practice ของแอนดรอยด์ ดังนั้น View จะต้องจัดการกับ UI State ในตัวเองได้ เพื่อให้รองรับกับ State Changes ที่เกิดขึ้นจากระบบแอนดรอยด์

สำหรับขั้นตอนและคำสั่งที่จะต้องใช้เพื่อทำให้ View รองรับ State Changes ได้นั้น ผมขอแนะนำให้อ่านบทความ Handle Android State Changes in Inherited Custom View — Google Developer Experts แทน เนื่องจากผู้เขียนบทความนี้ได้เขียนอธิบายไว้อย่างครอบคลุมแล้ว

เสร็จเรียบร้อย พร้อมใช้งานแล้ว

สมมติว่าผมต้องการสร้าง UI แบบภาพข้างล่างนี้

ก็สามารถสร้างเป็น 2 ส่วนแล้วนำมาต่อกันแบบนี้ได้เลย

ด้วยคำสั่งทั้งหมดนี้จะทำให้การใช้ Canvas API เพื่อสร้างภาพพื้นหลังเป็นรูปคูปองสามารถนำมาใช้งานกับ UI บนแอนดรอยด์ได้อย่างมีประสิทธิภาพมากขึ้นหรือ Android Developer-Friendly

ทั้งนี้ก็เพราะว่าไม่ได้มีแค่ผมที่จะต้องใช้ View ตัวนี้ แต่เพื่อนร่วมทีมก็ต้องนำไปใช้ด้วยเช่นกัน

ถึงแม้ว่าในบางครั้งการเลือกใช้ Solution สำเร็จรูปอาจจะรวดเร็วหรือคุ้มค่ากว่า แต่ในบางครั้งเมื่อเราคำนึงถึงโอกาสที่จะต้องทำสิ่งนี้ซ้ำ ๆ การลงแรงเพื่อสร้างบางอย่างขึ้นมาเพื่อให้ทีมสามารถใช้งานได้สะดวกมากกว่า ก็อาจจะคุ้มค่ากว่า ทั้งนี้ก็ขึ้นอยู่กับรูปแบบในการทำงานของคนในทีมด้วย

อาจจะไม่ใช่ฟีเจอร์ที่หวือหวาหรือโดดเด่น แต่ถ้าเราสามารถช่วยคนในทีมลดภาระให้น้อยลงได้ คนอื่น ๆ ก็จะมีเวลาเหลือมากพอที่จะไปทำฟีเจอร์อื่น ๆ เพื่อเพิ่ม Value ให้กับ Product ให้มากขึ้นได้

เราอาจจะไม่ต้องเขียน Library สุดเจ๋งที่นักพัฒนาทั่วโลกต้องนำมาใช้​ แต่อย่างน้อยเราก็ควรเขียนโค้ดบางอย่างที่ช่วยลดงานของเพื่อนร่วมทีมให้น้อยลง เพราะนั่นคือจุดเริ่มต้นที่ดีของการเขียนโค้ดที่มีคุณค่านั่นเอง

ถ้าคุณชอบบทความนี้ก็สนับสนุนด้วยการกด Clap หรือ Share ให้บทความของผม แต่ถ้าอยากมาเป็นส่วนหนึ่งของทีมพัฒนาแอป LINE MAN ผู้ช่วยส่วนตัวในชีวิตประจำวันของคนไทย ก็สามารถเข้ามาดูรายละเอียดของงานในแต่ละตำแหน่งกันได้ที่ careers.lmwn.com นะครับ 😉

--

--

Lovely android developer who enjoys learning in android technology, habitual article writer about Android development for Android community in Thailand.