ขอพูดถึงเรื่องดีไซน์กันหน่อยละกันนะสำหรับบทความนี้ โดยคราวนี้จะมาพูดถึงวิธีการใช้งานไฟล์ภาพใน drawable บ้าง ผู้ที่หลงเข้ามาอ่านหลายคนก็รู้จักกันดีอยู่แล้วกับโฟลเดอร์นี้
ปัญหานี้เป็นปัญหาที่คิดไม่ตกสำหรับมือใหม่หลายๆคน เนื่องจากมีอุปกรณ์แอนดรอยด์เพียงไม่กี่เครื่องให้ทดสอบ ดังนั้นภาพที่ใส่ลงไปอาจจะใหญ่เกินหรือเล็กเกินจนไม่สวยได้
เริ่มต้นจากเรื่องอัตราส่วนของหน้าจอแต่ละ Density กันก่อน ผู้ที่หลงเข้ามาอ่านเคยเห็นผ่านตากันมาบ้างมั้ย 3 : 4 : 6 : 8 ถ้าเคยค้นหาศึกษาตามเว็ปต่างๆในเรื่องดีไซน์ก็อาจจะเคยเห็นอยู่
เจ้าของบล็อกจะมาเริ่มต้นกับเรื่องอัตราส่วนที่ว่ากันก่อนเลย ให้นึกภาพตามไปด้วยนะ สมมติว่าเจ้าของบล็อกมีมือถือสองเครื่อง เครื่องนึงหน้าจอขนาด 4.5 นิ้ว ความละเอียด 800 x 480 px กับอีกเครื่องหน้าจอขนาด 4.5 นิ้ว ความละเอียด 1280 x 720 px เครื่องไหนจะมี Density ของ px มากกว่ากันล่ะ? (Density คือความหนาแน่นของเม็ดพิกเซลต่อพื้นที่หน้าจอ) ถ้าลองคำนวณดูเครื่องแรกจะมี Density เป็น 207.32 DPI ส่วนอีกเครื่องจะมี Density เป็น 326.36 DPI ก็จะสรุปได้ว่าเครื่องหลังมี Density ของ px มากกว่า
เวลาใช้งานโดยให้แสดงภาพเต็มหน้าจอทั้งสองเครื่อง เครื่องที่มี Density มากกว่า ภาพก็จะคมชัดกว่านั่นเอง
แล้วมันเกี่ยวอะไรกับบทความนี้ล่ะ?
ยังไม่ลืมกันใช่มั้ยว่าบนอุปกรณ์แอนดรอยด์เวลาเขียนแอปแล้วกำหนดขนาดบน Layout จะไม่นิยมใช้หน่วย px กัน แต่จะเป็นหน่วย dp แทน ซึ่งเป็นหน่วยที่จะช่วยลดความแตกต่างกันของหน้าจอ
[Android Design] แท้จริงแล้วหน่วย dp คืออะไร?
ดังนั้นเวลาเจ้าของบล็อกกำหนด Image View ว่าให้มีความกว้างและความสูงเท่ากับ 100 dp ดังนั้นถ้าเจ้าของบล็อกแสดงบนหน้าจอ mdpi กับ hdpi ขนาดของภาพในหน่วย px ก็ย่อมไม่เท่ากันอยู่แล้ว เพราะ hdpi มีความหนาแน่นของ px มากกว่าทำให้คมชัดกว่า นั่นก็หมายความว่าต้องเตรียมภาพที่ขนาดใหญ่กว่าสำหรับ hdpi เพราะถ้าใช้ภาพขนาดเดียวกับ mdpi เมื่อแสดงบน hdpi ภาพจะแตก
นักพัฒนาหลายๆคนใช้วิธีทำภาพให้สำหรับหน้าจอแต่ละ Density เพื่อให้หน้าจอขนาดๆนั้นๆสามารถแสดงภาพได้เหมาะสมที่สุด ยกตัวอย่างง่ายๆเลยก็ให้ดูที่ภาพไอคอนตอนสร้างโปรเจคขึ้นมาใหม่ๆ จะเห็นว่ามีภาพไอคอนแยกย่อยตามโฟลเดอร์ drawable แต่ละ density โดยแต่ละภาพจะมีขนาดต่างกัน เพื่อความเหมาะสมใน Density นั้นๆ
โดยจะใช้หลักเกณฑ์ในการกำหนดขนาด 3 : 4 : 6 : 8 เรียง ldpi : mdpi : hdpi : xhdpi ตามลำดับ ผู้ที่หลงเข้ามาอ่านจะทำภาพอิงหน้าจอ Density ไหนก็ได้ เวลาทำภาพสำหรับ Density อื่นๆก็ให้คำนวณจากอัตราส่วนนี้ แต่ทีนี้ อัตราส่วนดังกล่าวถูกกำหนดขึ้นเมื่อนานมาแล้ว แต่ในปัจจุบันนี้ Density ของหน้าจอได้เพิ่มขึ้นมาเล็กน้อย เจ้าของบล็อกจึงเพิ่มอัตราส่วนของ Density ทั้งหมดเป็นดังนี้
** tvdpi ไม่ค่อยนิยมทำกัน ส่วนมากเหมารวมกับ hdpi แต่ถ้าจะทำเพิ่มก็ได้เช่นกัน เพราะเป็น Density สำหรับ Tablet 7 นิ้ว **
mdpi จะเป็นอัตราส่วนหลักที่เจ้าของบล็อกใช้ ต้องการทำภาพใน Density ใดๆ จะเป็นอัตราส่วนเป้าหมาย และภาพที่ทำใน mdpi จะนำขนาดความกว้างและสูงมาคำนวณ
ขนาดของภาพหลัก x อัตราส่วนเป้าหมาย / อัตราส่วนหลัก
สมมติเจ้าของบล็อกทำภาพ 300 x 500 px บน mdpi แล้วอยากทำภาพสำหรับ xxhdpi จะต้องมีค่าตัวแปรดังนี้ mdpi เป็นอัตราส่วนหลักมีค่าเป็น 4 xxhdpi เป็นอัตราส่วนเป้าหมายมีค่าเป็น 12 ภาพหลักจะมีความกว้าง 300 สูง 500 px
ความกว้างของภาพบน xxhdpi จะได้เป็น
300 x 12 / 4 = 900 px
ความสูงของภาพบน xxhdpi จะได้เป็น
500 x 12 / 4 = 1500 px
ดังนั้นภาพสำหรับ xxhdpi จะต้องทำขนาด 900 x 1500 px
สมมติว่าเจ้าของบล็อกมีเครื่องเป็น mdpi ถ้าต้องการทำสำหรับ Density อื่นๆก็จะคำนวณดังนี้
300 x 12 / 4 = 900 px
500 x 12 / 4 = 1500 px
ดังนั้นภาพสำหรับ xxhdpi จะต้องทำขนาด 900 x 1500 px
ถ้าอัตราส่วนหลักเป็น ldpi ก็ดูตัวอย่างดังนี้
ภาพที่ใช้
Bitmap bm = BitmapFactory.decodeResource(getResources()
, R.drawable.android_platform);
Drawable dw = getResources().getDrawable( R.drawable.android_platform );
TextView textView1 = (TextView)findViewById(R.id.textView1);
textView1.setText("Bitmap : " + bm.getWidth() + " x "
+ bm.getHeight() + " px\n"
+ "Drawable : " + dw.getIntrinsicWidth()
+ " x " + dw.getIntrinsicHeight() + " px");
ทีนี้มาดูภาพขนาด hdpi กันบ้าง
ลองดูขนาดอื่นๆบ้าง อย่าง ldpi
ขนาด tvdpi
ขนาด xhdpi
จะเห็นได้ว่า การเอาไปใส่ใน drawable สุดท้ายก็จะถูกขยายตาม Density
ซึ่งลองนึกภาพดูว่าถ้าเจ้าของบล็อกเอาภาพขนาด 900 x 900 ใส่เข้าไปล่ะ? ระบบจะมองว่าภาพขนาด 900 x 900 ที่ใส่เข้ามานั้นเป็นแบบ mdpi และถ้าไปเปิดบน xhdpi ก็จะกลายเป็น 1800 x 1800 ไปในทันที แต่นั่นก็ยังไม่ใหญ่เท่า xxhdpi ที่จะกลายเป็น 2700 x 2700
ภาพถูกขยายเป็น 2700 x 2700 px แล้วจะเกิดอะไรขึ้น?
คำตอบคือ "บัฟเฟอร์ล้นครับ" ล้นด้วยภาพที่ถูกขยายจนเกินจำกัด ดังนั้นการนำไปเปิดบนเครื่อง mdpi หรือ hdpi อาจจะทำงานได้ปกติ แต่พอเอาไปเปิดบนเครื่องพวก Flagship ที่มีหน้าจอเป็น Full HD บัฟเฟอร์ก็จะล้น และแอปพลิเคชันก็จะเด้งในทันทีนั่นเอง
ดังนั้นการนำภาพไปไว้ในโฟลเดอร์ drawable ใดๆจะต้องคำนึงถึงความพอดีของภาพด้วย
สมมติว่าเจ้าของบล็อกทำภาพปุ่มบนเครื่อง xhdpi โดยภาพที่พอดีกับหน้าจอนี้อยู่ประมาณ 500 x 300 px ดังนั้นควรเก็บภาพนี้ไว้ใน drawable-xhdpi ถ้าขี้เกียจทำหลายๆภาพก็เก็บไว้ในโฟลเดอร์นี้เพียงภาพเดียวก้ได้
เพราะว่าเวลาที่รันบน Density อื่นๆเช่น mdpi ถ้าไม่มีภาพใน drawable หรือ drawable-mdpi ระบบก็จะมองหาว่าอยู่ในโฟลเดอร์แยกย่อยอื่นหรือป่าว ก็จะไปดึงภาพจากใน drawable-xhdpi แล้วย่อตามอัตราส่วน ดังนั้นเมื่อเปิดบน mdpi ภาพก็จะย่อเหลือขนาด
500 x 4 / 8 = 250 px
300 x 4 / 8 = 150 px
ก็จะเหลือขนาด 250 x 150 px นั่นเอง
สมมติว่าเปิดบนเครื่อง xxhdpi ระบบก็จะทำแบบนี้เช่นกัน ถ้าไม่มีไฟล์ภาพดังกล่าวใน drawable หรือ drawable-xxhdpi ระบบก็จะไปดึงจากโฟลเดอร์ที่อยู่ใกล้เคียงแล้วขยายขนาดเอา
แต่
.
.
.
อย่าลืมว่าภาพถูกขยาย ภาพก็จะแตกนะ ไม่ได้คมชัดสิ ดังนั้นเวลาทำภาพแบบลวกๆด้วยวิธีเจ้าของบล็อกคือเจ้าของบล็อกก็ทำภาพตามเครื่องเจ้าของบล็อกก่อน แล้วค่อยคำนวณว่าถ้าเปิดบน xxhdpi จะมีขนาดเท่าไรก็จะทำภาพขนาด xxhdpi แล้วเก็บใน drawable-xxhdpi แทนเลย
ทำภาพรองรับหน้าจอขนาดใหญ่ที่สุดเป็นหลัก ส่วนภาพขนาดเครื่องรองลงมาจะทำหรือไม่ก็แล้วแต่
วิธีนี้เป็นวิธีที่ดีที่สุดแล้วจริงๆหรือ?
สำหรับการย่อภาพใน Drawable ไม่ได้เหมือนการทำภาพใน Photoshop นะ เพราะการย่อภาพจะเสมือนตอนที่ย่อภาพ แต่ยังไม่ได้เรนเดอร์ภาพใหม่ (นึกภาพใช้ Transform ย่อขนาด โดยยังไม่กด Enter ภาพจะย่อแบบหยาบๆ)
จริงๆก็ไม่ได้ย่อจนหยาบแบบภาพนี้หรอก แต่ถ้าสังเกตดีๆก็เห็นได้ว่าภาพที่ย่อด้วย Drawable จะหยาบๆ
และกรณีที่ทำภาพ xxhdpi แล้วเปิดบน ldpi หรือ mdpi ก็จะกลายเป็นว่าเครื่อง ldpi กับ mdpi ต้องรับภาระหนัก เปลืองทรัพยากรให้กับระบบในการนำภาพใหญ่มาแสดง จึงอาจจะลดขนาดภาพเหลือแค่ xhdpi ก็พอ เพื่อลดภาระลง ส่วน xxhdpi ขยายภาพเอานิดหน่อย ภาพไม่แตกมากนักหรอก
ดังนั้นการทำภาพหลายๆขนาดไปเลยจะทำให้งานออกมาดี แต่ก็จะอาจจะทำให้เปลืองพื้นที่แอป เมื่อมีจำนวนภาพเยอะๆ สุดท้ายแล้ว ก็ขึ้นอยู่กับผู้ที่หลงเข้ามาอ่านนั่นแหละว่าจะเลือกแบบใด เพราะวิธีพวกนี้ไม่มีตายตัว ขึ้นอยู่กับความเหมาะสมของงาน
หรือจะใช้ Shape Drawable เข้ามาช่วยก็ได้ เพราะไม่กินพื้นที่ แต่ก็ใส่ลูกเล่นของภาพไม่ได้เพราะเป็นแค่ Shape ง่ายๆเท่านั้น [Android Design] สร้างภาพง่ายๆจาก XML ด้วย Shape [Drawable Resource]
หรือจะใช้วิธีนำภาพแบบ Vector อย่าง SVG เข้ามาใช้เลยก็ได้ แต่ข้อเสียก็คือต้องใช้โค๊ดในการนำภาพดังกล่าวมาแสดง [Android Code & Design] ภาพ Vector ขยายยังไงภาพก็ไม่แตกกกกกก
ไม่มีวิธีไหนที่ดีที่สุดนั่นแหละ แต่ขึ้นอยู่กับสถานการณ์เท่านั้นเอง
สำหรับผู้ที่หลงเข้ามาอ่านที่ต้องการไฟล์ตัวอย่างตอนใช้คำสั่งเช็คขนาดภาพดาวน์โหลดได้ที่ Resource Density Ratio [Google Drive]
ลองไปฝึกแต่ละวิธีดูกันเองนะ
.
.
.
อย่าลืมว่าภาพถูกขยาย ภาพก็จะแตกนะ ไม่ได้คมชัดสิ ดังนั้นเวลาทำภาพแบบลวกๆด้วยวิธีเจ้าของบล็อกคือเจ้าของบล็อกก็ทำภาพตามเครื่องเจ้าของบล็อกก่อน แล้วค่อยคำนวณว่าถ้าเปิดบน xxhdpi จะมีขนาดเท่าไรก็จะทำภาพขนาด xxhdpi แล้วเก็บใน drawable-xxhdpi แทนเลย
จริงๆก็ไม่ได้ย่อจนหยาบแบบภาพนี้หรอก แต่ถ้าสังเกตดีๆก็เห็นได้ว่าภาพที่ย่อด้วย Drawable จะหยาบๆ
และกรณีที่ทำภาพ xxhdpi แล้วเปิดบน ldpi หรือ mdpi ก็จะกลายเป็นว่าเครื่อง ldpi กับ mdpi ต้องรับภาระหนัก เปลืองทรัพยากรให้กับระบบในการนำภาพใหญ่มาแสดง จึงอาจจะลดขนาดภาพเหลือแค่ xhdpi ก็พอ เพื่อลดภาระลง ส่วน xxhdpi ขยายภาพเอานิดหน่อย ภาพไม่แตกมากนักหรอก
ดังนั้นการทำภาพหลายๆขนาดไปเลยจะทำให้งานออกมาดี แต่ก็จะอาจจะทำให้เปลืองพื้นที่แอป เมื่อมีจำนวนภาพเยอะๆ สุดท้ายแล้ว ก็ขึ้นอยู่กับผู้ที่หลงเข้ามาอ่านนั่นแหละว่าจะเลือกแบบใด เพราะวิธีพวกนี้ไม่มีตายตัว ขึ้นอยู่กับความเหมาะสมของงาน
หรือจะใช้ Shape Drawable เข้ามาช่วยก็ได้ เพราะไม่กินพื้นที่ แต่ก็ใส่ลูกเล่นของภาพไม่ได้เพราะเป็นแค่ Shape ง่ายๆเท่านั้น [Android Design] สร้างภาพง่ายๆจาก XML ด้วย Shape [Drawable Resource]
หรือจะใช้วิธีนำภาพแบบ Vector อย่าง SVG เข้ามาใช้เลยก็ได้ แต่ข้อเสียก็คือต้องใช้โค๊ดในการนำภาพดังกล่าวมาแสดง [Android Code & Design] ภาพ Vector ขยายยังไงภาพก็ไม่แตกกกกกก
ไม่มีวิธีไหนที่ดีที่สุดนั่นแหละ แต่ขึ้นอยู่กับสถานการณ์เท่านั้นเอง
สำหรับผู้ที่หลงเข้ามาอ่านที่ต้องการไฟล์ตัวอย่างตอนใช้คำสั่งเช็คขนาดภาพดาวน์โหลดได้ที่ Resource Density Ratio [Google Drive]
ลองไปฝึกแต่ละวิธีดูกันเองนะ