29 October 2013

เคล็ดไม่ลับกับภาพ Drawable Resource สำหรับรองรับหน้าจอหลายขนาด

Updated on

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



        ถ้าอัตราส่วนหลักเป็น mdpi ก็ดูตัวอย่างดังนี้



        ถ้าอัตราส่วนหลักเป็น tvdpi ก็ดูตัวอย่างดังนี้



        ถ้าอัตราส่วนหลักเป็น hdpi ก็ดูตัวอย่างดังนี้


        ถ้าอัตราส่วนหลักเป็น xhdpi ก็ดูตัวอย่างดังนี้


        และถ้าอัตราส่วนหลักเป็น xxhdpi ก็ดูตัวอย่างดังนี้


        เท่านี้ก็จะรู้กันแล้วว่าต้องทำภาพขนาดเท่าไรบ้างในแต่ละ Density ก็ให้ผู้ที่หลงเข้ามาอ่านเอาวิธีไปลองคำนวณกันเอาเองนะ

        ทีนี้ก็คงเดาต่อกันได้นะครับ ว่าจะเอาไปใช้งานกันยังไง


        ทีนี้ขอต่อด้วยเคล็ดที่ไม่ลับสำหรับกรณีที่ขี้เกียจทำภาพทุกขนาด ก่อนอื่นต้องขอเกริ่นเรื่องการนำภาพไปใส่ในโฟลเดอร์ drawable ก่อน โดยปกติแล้วโฟลเดอร์ drawable ที่เอาไว้ใส่ไฟล์ภาพมักจะมีดังนี้ drawable, drawable-ldpi, drawable-mdpi, drawable-tvdpi, drawable-hdpi, drawable-xhdpi และ drawable-xxhdpi

        ก่อนอื่นอยากจะให้เข้าใจก่อน กับโฟลเดอร์ drawable


        ไฟล์ภาพที่ใส่ใน drawable จะเหมือนกับใส่ไว้ใน drawable-mdpi นั่นเอง ผู้ที่หลงเข้ามาอ่านหลายๆคนอาจจะเข้าใจว่า ถ้าอยากทำแค่ไซส์เดียว ก็แค่ทำภาพนั้นๆมาใหญ่ๆ แล้วเอาไปใส่ไว้ใน drawable ก็เรียบร้อยแล้ว

        แต่วิธีแบบนั้นไม่ถูกต้องครับ เพราะอะไร? ลองดูตัวอย่างต่อไปนี้ครับ

        เจ้าของบล็อกนำภาพ android_platform.png ใส่ไว้ใน drawable โดยที่โฟลเดอร์อื่นๆจะไม่มีการใส่ภาพชื่อดังกล่าวนี้ไว้ ซึ่งเป็นภาพขนาด 300 x 300 px เท่านั้น


ภาพที่ใช้


        เจ้าของบล็อกจะไม่ดึงมาแสดงบน Image View ให้ดูนะ แต่จะดึงภาพมาเก็บไว้ในรูป Bitmap กับ Drawable แล้วจะให้ดูขนาดของภาพเมื่อดึงมาใช้งาน
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");

        โดยโค๊ดที่ใช้นี้ก็แค่ดึงภาพมาเก็บเป็น Bitmap กับ Drawable จากนั้นก็เช็คขนาดของภาพแล้วแสดงบน Text View พอลองรันจากเครื่องที่มี Density เป็น mdpi ก่อนนะ จะได้ดังนี้


        จะเห็นว่าภาพก็จะมีขนาด 300 x 300 px ปกตินั่นแหละ


ทีนี้มาดูภาพขนาด hdpi กันบ้าง


        จะเห็นว่าขนาดเปลี่ยนไปเป็น 450 x 450 ซะงั้น นั่นก็คือ 300 x 6 / 4 นั่นเอง จะเห็นว่าอิง Density ด้วย ทั้งๆที่เอาภาพใส่ไว้ในโฟลเดอร์ drawable


ลองดูขนาดอื่นๆบ้าง อย่าง 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]


        ลองไปฝึกแต่ละวิธีดูกันเองนะ