บันทึกการพัฒนาเครื่องเมตรอนอม

เมื่อตรวจสอบ UI หลายภาษาของ Flutter พบว่า “300 BPM” ถูกแบ่งเป็นสองบรรทัด

บันทึกการทดสอบการแสดงผลของ SANGE Metronome โดยผสม 12 ภาษา 6 ขนาดหน้าจอ และการขยายข้อความ 3 ระดับ ปัญหา “300 BPM” ขึ้นบรรทัดใหม่และข้อความผิดพลาดที่ยาวนำไปสู่การแก้ไข UI และทบทวนรายการตรวจสอบในการทดสอบอัตโนมัติ

สาธารณะ อัปเดต
  • Flutter
  • การพัฒนาส่วนบุคคล
  • การรองรับหลายภาษา
  • การทดสอบ UI

ได้ตรวจสอบการแสดงผลบนหน้าจอเล็กและการตั้งค่าข้อความใหญ่ของแอปที่พัฒนาด้วยตนเอง “SANGE Metronome – ฝึกจังหวะอย่างง่าย”

ปัญหาที่พบระหว่างนั้นคือตัวเลข “300 BPM” ในรายการโปรดถูกแบ่งเป็น “30” และ “0” แม้ข้อความไม่ได้ล้นออกนอกหน้าจอ แต่การแสดงผลเช่นนี้ทำให้อ่านค่าเทมโปได้ยาก

การไม่เกิดข้อยกเว้นด้านเลย์เอาต์และการอ่านข้อมูลที่จำเป็นได้ ต้องตรวจสอบแยกกัน

บทความนี้สรุปว่าพบปัญหาการแสดงผลอะไรจากการทดสอบ Widget 12 ภาษาและการตรวจหน้าจอบน Android Emulator และนำไปแก้ไขกับเพิ่มในทดสอบอย่างไร

ภาพรวมส่วนล่างของหน้าแรกในภาษาฝรั่งเศส โปรตุเกสแบบบราซิล อินโดนีเซีย ไทย เวียดนาม และญี่ปุ่น ที่ระดับการขยายข้อความ 130%
ภาพรวมหน้าจอหลังแก้ไข 6 ภาษาที่นำมาเรียงกัน แถวบนจากซ้ายคือภาษาฝรั่งเศส โปรตุเกสแบบบราซิล และอินโดนีเซีย แถวล่างจากซ้ายคือภาษาไทย เวียดนาม และญี่ปุ่น ชื่อภาษาเยอรมันในรายการโปรดเป็นข้อมูลทดสอบเดียวกันทุกภาษา เพื่อทดสอบชื่อที่ยาว

แม้ภาษาญี่ปุ่นจะพอดี ก็ไม่ได้แปลว่าใช้เลย์เอาต์เดิมได้เสมอ

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

เลย์เอาต์ที่พอดีเมื่อดูภาษาญี่ปุ่นอาจเปลี่ยนไปเมื่อแปลแล้วป้ายข้อความยาวต่างกัน ตัวอย่างเช่น “12拍” ในภาษาญี่ปุ่นกับ “12 Schläge” ในภาษาเยอรมันต้องใช้ความกว้างของปุ่มต่างกัน

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

ตรวจสอบอัตโนมัติด้วย 12 ภาษา 6 ขนาด และการขยายข้อความ 3 ระดับ

ในการทดสอบ Widget ได้ผสมเงื่อนไขดังต่อไปนี้ การทดสอบ Widget เป็นวิธีเรนเดอร์องค์ประกอบหน้าจอ Flutter ในสภาพแวดล้อมทดสอบเพื่อตรวจสอบ

เกณฑ์ตรวจสอบเงื่อนไข
ภาษาญี่ปุ่น อังกฤษ สเปน โปรตุเกสแบบบราซิล เยอรมัน ฝรั่งเศส เกาหลี อินโดนีเซีย เวียดนาม ไทย จีนตัวเต็ม และอิตาลี
ขนาดเชิงตรรกะ・แนวตั้ง320×568、360×640、360×800、393×873、412×915、432×960
ระดับการขยายข้อความ100%・130%・200%
หน้าจอหน้าแรก จำนวนจังหวะ การแบ่งย่อยจังหวะ การตั้งค่า การเลือกภาษา การบันทึก การจัดการรายการโปรด และรายการโปรดทั้งหมด
12 ภาษา × 6 ขนาด × 3 ระดับการขยายข้อความ × 8 หน้าจอ = 1,728 เงื่อนไข

ตั้งข้อมูลทดสอบเป็น 300 BPM, 12 จังหวะ, โน้ตเขบ็ตสองชั้น, ระดับเสียง 100% และรายการโปรดชื่อยาว 3 รายการ ในหน้าจอบันทึกยังให้แสดงข้อผิดพลาดเมื่อชื่อซ้ำด้วย

กำหนดระดับการขยายข้อความด้วย TextScaler.linear ค่า 100%・130%・200% ในครั้งนี้เป็นเงื่อนไขทดสอบที่ขยายแบบเชิงเส้น ไม่ได้จำลองการขยายข้อความตามการตั้งค่า OS บนอุปกรณ์จริงได้เหมือนกันทั้งหมด

ได้เก็บภาพบน Android Emulator ด้วย โดยครอบคลุม 7 ภาษา ได้แก่ เยอรมัน ฝรั่งเศส โปรตุเกสแบบบราซิล อินโดนีเซีย ไทย เวียดนาม และญี่ปุ่น ไม่ได้ทดสอบทุกชุดผสมของขนาด ระดับการขยาย และภาษา แต่ใช้ภาษาเยอรมันตรวจสอบขนาดหน้าจอกับระดับการขยายข้อความ และตรวจสอบอีก 6 ภาษาที่การตั้งค่า 360×640 และ 130%

บนอีมูเลเตอร์ พื้นที่ที่แอปใช้ได้จะเล็กลงตามพื้นที่ของแถบระบบ นอกจากขนาดที่ตั้งค่าแล้ว ยังบันทึกขนาดเชิงตรรกะขณะเรนเดอร์และขนาดภายใน SafeArea ด้วย

ปรับจำนวนคอลัมน์ของปุ่มจังหวะให้ข้อความพอดี

ปัญหาแรกที่พบคือคำอธิบายเทมโปและหน่วยจำนวนจังหวะล้นออกด้านขวา Android Emulator ยังตรวจพบ RenderFlex overflow ด้วย จึงจำเป็นต้องปรับเลย์เอาต์

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

จึงวัดป้ายข้อความหลังแปลด้วย TextPainter แล้วกำหนดจำนวนคอลัมน์จากความกว้างที่ต้องใช้โดยรวมระดับการขยายข้อความด้วย หากพื้นที่แคบก็ลดจำนวนคอลัมน์และเรียงในแนวตั้ง

หน้าจอหลังแก้ไขที่ตั้งค่าการแสดงผล 320×568 ภาษาเยอรมัน 100% โดยปุ่มเลือกจังหวะ 1–12 เรียงเป็น 2 คอลัมน์
การแสดงผล 100% หลังแก้ไข ในเงื่อนไขนี้จัดปุ่ม 12 ปุ่มเป็น 2 คอลัมน์ได้
หน้าจอเลือกจำนวนจังหวะที่เลื่อนลงด้านล่าง ในการตั้งค่าการแสดงผล 320×568 ภาษาเยอรมัน 200% โดยปุ่ม 7–12 จังหวะเรียงเป็น 1 คอลัมน์
ที่ขนาดการแสดงผลเดียวกัน เมื่อขยายเป็น 200% เปลี่ยนเป็น 1 คอลัมน์ และเลื่อนตรวจสอบจนถึงปุ่ม 12 จังหวะกับข้อความอธิบายด้านล่าง ภาพนี้ต่างจากภาพบนทั้งระดับการขยายข้อความและตำแหน่งการเลื่อน โดยทั้งสองภาพเป็นหลังแก้ไข

บนหน้าแรกก็ทบทวนเลย์เอาต์ที่พยายามใส่ชื่อจังหวะยาวลงใน 2 คอลัมน์แคบ โดยเปลี่ยนจำนวนจังหวะกับจังหวะให้เรียงแนวตั้งเมื่อจำเป็น ส่วนปัญหาตัวเลข 10–12 จังหวะบนหน้าแรกถูกแยก แก้โดยให้กลุ่มแสดงจังหวะทั้งหมดตัดขึ้นหลายบรรทัดได้

แม้ “300” ถูกแยกบรรทัด ก็ไม่ได้เกิดข้อยกเว้นเสมอไป

ขณะตรวจดูภาพ พบปัญหาที่การ์ดรายการโปรดในการตั้งค่าการแสดงผล 320×568 และระดับการขยายข้อความ 200%

เนื่องจากยังจัดการ์ดเป็น 3 คอลัมน์ จึงขึ้นบรรทัดใหม่กลางตัวเลข “300” แม้ข้อความจะอยู่ภายในพื้นที่แสดงผล แต่เมื่อตัวเลขถูกแบ่งเป็น “30” กับ “0” ก็อ่านค่าเทมโปได้ยาก

การตรวจสอบข้อยกเว้นด้านเลย์เอาต์เพียงอย่างเดียวตรวจไม่พบปัญหานี้

ในการแก้ไข ได้ปรับจำนวนคอลัมน์ของการ์ดรายการโปรดตามระดับการขยายข้อความ สำหรับหน้าจอเล็กที่แสดงผล 200% ในครั้งนี้ใช้ 1 คอลัมน์ เพื่อไม่ให้ตัวเลขเทมโปถูกแบ่งกลางตัวเลข

หน้าจอหลังแก้ไขที่ตั้งค่าการแสดงผล 320×568 ภาษาเยอรมัน 200% โดยรายการโปรดด้านล่างหน้าแรกเรียงเป็น 1 คอลัมน์ และตัวเลข 300 BPM ไม่ถูกแยกบรรทัด
รายการโปรดหลังแก้ไข ตัดท้ายชื่อที่ยาว แต่แสดงตัวเลขเทมโปโดยไม่แยกบรรทัด เนื่องจากไม่ได้เก็บภาพก่อนแก้ไขภายใต้เงื่อนไขนี้ จึงไม่ใช่การเปรียบเทียบภาพก่อนและหลังแก้ไขภายใต้เงื่อนไขเดียวกันแบบเคียงข้างกัน

พร้อมกับแก้ไข ได้เพิ่มการตรวจสอบว่า “BPM สามหลักต้องอยู่ในบรรทัดเดียว” ในการทดสอบอัตโนมัติ ต่อไปนี้เป็นส่วนหนึ่งของโค้ดตรวจสอบนั้น

for (var card = 0; card < 3; card++) {
  final paragraph = tester.renderObject<RenderParagraph>(
    find.text('300  BPM').at(card),
  );
  expect(
    paragraph.getBoxesForSelection(
      const TextSelection(baseOffset: 0, extentOffset: 3),
    ),
    hasLength(1),
    reason:
        '$locale/$size/$scale favorite BPM must stay on one line',
  );
}

ตรวจสอบว่ากล่องการวาดของช่วงอักขระ “300” มีเพียงหนึ่งกล่อง ในการทดสอบจริงได้ตรวจสอบ BPM ของรายการโปรดทั้ง 3 รายการ และตัวเลขจังหวะ 1–12 บนหน้าแรกด้วย

วิธีนี้ใช้กับตัวเลขที่วาดด้วยแบบอักษรเดียวกันในการทดสอบครั้งนี้ ไม่ใช่วิธีทั่วไปสำหรับตัดสินการขึ้นบรรทัดใหม่ในข้อความที่มีหลายภาษาหรือหลายแบบอักษรปะปนกัน

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

เข้าถึงคำอธิบายข้อผิดพลาดและปุ่มบันทึกได้หรือไม่

ในหน้าจอบันทึกได้ทบทวนการจำกัดจำนวนบรรทัดของข้อความผิดพลาดด้วย

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

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

หน้าจอบันทึกที่เลื่อนลงด้านล่าง ในการตั้งค่าการแสดงผล 320×568 ภาษาเยอรมัน 200% แสดงข้อความผิดพลาดชื่อซ้ำครบถ้วน และปุ่มยกเลิกกับบันทึกที่เรียงแนวตั้ง
คำอธิบายข้อผิดพลาดชื่อซ้ำ พร้อมปุ่มยกเลิกและบันทึก เป็นหน้าจอหลังเลื่อน

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

บน Android Emulator ได้โฟกัสช่องกรอกจริงเพื่อตรวจสอบเลย์เอาต์ขณะแสดงแป้นพิมพ์ แต่ PNG ที่เก็บในครั้งนี้จับภาพเฉพาะหน้าจอ Flutter จึงไม่มีตัวแป้นพิมพ์ของ OS อยู่ในภาพ ได้แยกบันทึกภาพการแสดงผลปกติกับภาพขณะกรอกข้อมูลไว้ต่างหาก

ผลหลังแก้ไขและขอบเขตที่ตรวจสอบ

ผลการตรวจสอบเมื่อวันที่ 30 ตุลาคม 2026 มีดังนี้

รายการผลลัพธ์
การทดสอบ Widget ด้านการแสดงผลไม่พบข้อยกเว้นด้านเลย์เอาต์ใน 1,728 เงื่อนไข และผ่านการตรวจสอบว่าตัวเลขไม่ถูกแยกบรรทัด ข้อความผิดพลาดไม่ถูกตัด และเข้าถึงปุ่มบันทึกได้ในหน้าจอที่เกี่ยวข้อง
การทดสอบ Flutter ทั้งหมดผ่าน 121 รายการ
การวิเคราะห์แบบสแตติกไม่พบปัญหา
เงื่อนไขหน้าจอบน Android Emulator280 เงื่อนไข โดยในการรันแต่ละครั้ง ค่า layoutErrors เท่ากับ 0 รายการ
PNG ที่เก็บ478 ภาพ รวมภาพหลังเลื่อนและขณะกรอกข้อมูล

1,728 คือจำนวนชุดผสมของเงื่อนไขการแสดงผล ส่วน 121 คือจำนวนการทดสอบ Flutter ทั้งหมดที่รัน เนื่องจากในการทดสอบหนึ่งรายการมีการวนตรวจสอบหลายเงื่อนไขการแสดงผล จึงแยกตัวเลขสองค่านี้ออกจากกัน

478 คือจำนวน PNG ที่เก็บ ไม่ได้ตัดสินผ่านหรือไม่ผ่านด้วยสายตาให้ภาพทุกภาพแยกกัน แต่เลือกหน้าจอหลักเพื่อตรวจสอบข้อความขาดหายหรือซ้อนทับ ตัวเลขถูกแยก และการแสดงผลหลังเลื่อน ในหน้าจอหลักที่ตรวจสอบของภาษาไทยและเวียดนามก็ไม่พบกล่องแทนอักขระที่หายไปหรือข้อความซ้อนทับ

บนอีมูเลเตอร์ นอกจากขนาดสังเคราะห์ 6 แบบแล้ว ยังใช้การตั้งค่าการแสดงผล 6 แบบของ Galaxy A16 5G・A06 4G・S24+・S23・Redmi 14C 4G・Pixel 9 ได้เตรียมการตั้งค่าตามรุ่นไว้ 30 แบบ แต่ครั้งนี้รันเพียง 6 แบบจากทั้งหมด

เป็นการใช้ความละเอียดและค่าความหนาแน่นโดยประมาณสำหรับการตรวจสอบกับ Android 14 Emulator ตัวเดียวกัน ไม่ได้ตรวจสอบโดยจำลอง OS เฉพาะผู้ผลิต แบบอักษร หรือรูเจาะกล้อง และไม่ได้ทดสอบบนอุปกรณ์จริง 30 รุ่น การทดสอบ Flutter รันบน Windows และครั้งนี้ไม่ได้รันการเปรียบเทียบภาพ golden แบบตรงกันทุกพิกเซลสำหรับ Linux

บันทึกครั้งนี้เป็นการตรวจสอบการแสดงผล ผลการตรวจสอบการเรนเดอร์หน้าจอ การโฟกัสช่องกรอก และการเลื่อน ไม่ได้ยืนยันว่าการเล่นเสียงหรือกระบวนการบันทึกสำเร็จด้วย

สิ่งที่ต้องตรวจสอบในการเปลี่ยน UI ครั้งถัดไป

การตรวจสอบครั้งนี้ช่วยให้กำหนดสิ่งที่ต้องเก็บไว้ในการทดสอบการแสดงผลได้ชัดเจนขึ้น

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

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

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

← ไปที่รายการบทความบล็อกการพัฒนา