เมื่อตรวจสอบ UI หลายภาษาของ Flutter พบว่า “300 BPM” ถูกแบ่งเป็นสองบรรทัด
บันทึกการทดสอบการแสดงผลของ SANGE Metronome โดยผสม 12 ภาษา 6 ขนาดหน้าจอ และการขยายข้อความ 3 ระดับ ปัญหา “300 BPM” ขึ้นบรรทัดใหม่และข้อความผิดพลาดที่ยาวนำไปสู่การแก้ไข UI และทบทวนรายการตรวจสอบในการทดสอบอัตโนมัติ
ได้ตรวจสอบการแสดงผลบนหน้าจอเล็กและการตั้งค่าข้อความใหญ่ของแอปที่พัฒนาด้วยตนเอง “SANGE Metronome – ฝึกจังหวะอย่างง่าย”
ปัญหาที่พบระหว่างนั้นคือตัวเลข “300 BPM” ในรายการโปรดถูกแบ่งเป็น “30” และ “0” แม้ข้อความไม่ได้ล้นออกนอกหน้าจอ แต่การแสดงผลเช่นนี้ทำให้อ่านค่าเทมโปได้ยาก
การไม่เกิดข้อยกเว้นด้านเลย์เอาต์และการอ่านข้อมูลที่จำเป็นได้ ต้องตรวจสอบแยกกัน
บทความนี้สรุปว่าพบปัญหาการแสดงผลอะไรจากการทดสอบ Widget 12 ภาษาและการตรวจหน้าจอบน Android Emulator และนำไปแก้ไขกับเพิ่มในทดสอบอย่างไร

แม้ภาษาญี่ปุ่นจะพอดี ก็ไม่ได้แปลว่าใช้เลย์เอาต์เดิมได้เสมอ
หน้าจอเมโทรนอมไม่ได้แสดงเพียงตัวเลขเทมโป แต่ยังมีจำนวนจังหวะ ชื่อจังหวะ ระดับเสียง และชื่อรายการโปรดด้วย หน้าจอบันทึกยังต้องมีช่องกรอก คำอธิบายข้อผิดพลาด และปุ่มบันทึกกับยกเลิก
เลย์เอาต์ที่พอดีเมื่อดูภาษาญี่ปุ่นอาจเปลี่ยนไปเมื่อแปลแล้วป้ายข้อความยาวต่างกัน ตัวอย่างเช่น “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 แล้วกำหนดจำนวนคอลัมน์จากความกว้างที่ต้องใช้โดยรวมระดับการขยายข้อความด้วย หากพื้นที่แคบก็ลดจำนวนคอลัมน์และเรียงในแนวตั้ง


บนหน้าแรกก็ทบทวนเลย์เอาต์ที่พยายามใส่ชื่อจังหวะยาวลงใน 2 คอลัมน์แคบ โดยเปลี่ยนจำนวนจังหวะกับจังหวะให้เรียงแนวตั้งเมื่อจำเป็น ส่วนปัญหาตัวเลข 10–12 จังหวะบนหน้าแรกถูกแยก แก้โดยให้กลุ่มแสดงจังหวะทั้งหมดตัดขึ้นหลายบรรทัดได้
แม้ “300” ถูกแยกบรรทัด ก็ไม่ได้เกิดข้อยกเว้นเสมอไป
ขณะตรวจดูภาพ พบปัญหาที่การ์ดรายการโปรดในการตั้งค่าการแสดงผล 320×568 และระดับการขยายข้อความ 200%
เนื่องจากยังจัดการ์ดเป็น 3 คอลัมน์ จึงขึ้นบรรทัดใหม่กลางตัวเลข “300” แม้ข้อความจะอยู่ภายในพื้นที่แสดงผล แต่เมื่อตัวเลขถูกแบ่งเป็น “30” กับ “0” ก็อ่านค่าเทมโปได้ยาก
การตรวจสอบข้อยกเว้นด้านเลย์เอาต์เพียงอย่างเดียวตรวจไม่พบปัญหานี้
ในการแก้ไข ได้ปรับจำนวนคอลัมน์ของการ์ดรายการโปรดตามระดับการขยายข้อความ สำหรับหน้าจอเล็กที่แสดงผล 200% ในครั้งนี้ใช้ 1 คอลัมน์ เพื่อไม่ให้ตัวเลขเทมโปถูกแบ่งกลางตัวเลข

พร้อมกับแก้ไข ได้เพิ่มการตรวจสอบว่า “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 บรรทัด แต่บนหน้าจอแคบเมื่อขยายข้อความ คำแปลที่ยาวอาจแสดงไม่ครบ จึงยกเลิกจำนวนบรรทัดสูงสุด และทำให้เลื่อนอ่านข้อความอธิบายทั้งหมดได้
เปลี่ยนปุ่มบันทึกกับยกเลิกให้เรียงแนวตั้งเมื่อหน้าจอแคบหรือข้อความใหญ่ รวมถึงปรับหัวข้อโมดัลกับปุ่มปิด และชื่อการตั้งค่ากับค่าที่เลือก ให้มีความกว้างสำหรับข้อความเพียงพอ

ได้เพิ่มการตรวจสอบขณะกรอกข้อมูลด้วย ในการทดสอบ Widget ตั้งระยะว่างด้านล่าง 280 พิกเซลเชิงตรรกะเพื่อแทนแป้นพิมพ์ และตรวจสอบว่าหลังเลื่อนแล้วปุ่มบันทึกแสดงอยู่เหนือพื้นที่นั้น อีกทั้งตรวจสอบว่าข้อความผิดพลาดไม่ถูกตัดด้วยการจำกัดจำนวนบรรทัด
บน Android Emulator ได้โฟกัสช่องกรอกจริงเพื่อตรวจสอบเลย์เอาต์ขณะแสดงแป้นพิมพ์ แต่ PNG ที่เก็บในครั้งนี้จับภาพเฉพาะหน้าจอ Flutter จึงไม่มีตัวแป้นพิมพ์ของ OS อยู่ในภาพ ได้แยกบันทึกภาพการแสดงผลปกติกับภาพขณะกรอกข้อมูลไว้ต่างหาก
ผลหลังแก้ไขและขอบเขตที่ตรวจสอบ
ผลการตรวจสอบเมื่อวันที่ 30 ตุลาคม 2026 มีดังนี้
| รายการ | ผลลัพธ์ |
|---|---|
| การทดสอบ Widget ด้านการแสดงผล | ไม่พบข้อยกเว้นด้านเลย์เอาต์ใน 1,728 เงื่อนไข และผ่านการตรวจสอบว่าตัวเลขไม่ถูกแยกบรรทัด ข้อความผิดพลาดไม่ถูกตัด และเข้าถึงปุ่มบันทึกได้ในหน้าจอที่เกี่ยวข้อง |
| การทดสอบ Flutter ทั้งหมด | ผ่าน 121 รายการ |
| การวิเคราะห์แบบสแตติก | ไม่พบปัญหา |
| เงื่อนไขหน้าจอบน Android Emulator | 280 เงื่อนไข โดยในการรันแต่ละครั้ง ค่า 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 ครั้งถัดไป สามารถตรวจสอบได้ว่าปัญหาที่พบครั้งนี้กลับมาเกิดซ้ำหรือไม่