28 มีนาคม 2559

[Android Code] ยินดีต้อนรับเข้าสู่โลกของ Vector ด้วย Vector Drawable


        ในที่สุดการพัฒนาแอพแอนดรอยด์ก็ได้เข้ามาสู่โลกของ Vector กันเสียที ถึงแม้ว่าเดิมทีมันจะมีเข้ามาตั้งแต่สมัย Android 5.0 Lollipop แล้วล่ะ แต่ในตอนนั้นก็ยังมีข้อจำกัดอยู่หลายอย่างที่ทำให้นักพัฒนาก็ยังคงใช้ Bitmap สำหรับภาพที่แสดงในแอพกันอยู่ดี แต่ด้วยการมาของ Android Support Library ที่รองรับ Vector Drawable สำหรับเวอร์ชันเก่าๆนี่แหละ จึงเป็นเรื่องที่นักพัฒนาแอนดรอยด์ควรเริ่มหันมาลองทำความรู้จักและหัดใช้งานกันได้แล้วนะครับ

แอพมันอ้วนเพราะไฟล์ภาพ!! (แต่ถ้าแฟนอ้วน ไม่เกี่ยวกับไฟล์ภาพนะ)

        ทุกวันนี้ขนาดของแอพส่วนใหญ่นั้นมีผลพวงมาจากไฟล์ภาพที่ใช้นั่นแหละ ถ้าผู้ที่หลงเข้ามาอ่านลองเอาขนาดของ APK มาเทียบกับขนาดไฟล์ภาพที่ใช้ในนั้นก็จะพบว่า ขนาดของไฟล์ภาพนั้นส่งผลกับขนาดของ APK โคตรๆ เพราะโค้ดจริงๆนั้นมีขนาดไม่ถึง MB ด้วยซ้ำ (ถ้าไม่นับรวม Library)

        เพราะอะไรล่ะ?

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

        ดังนั้นวิธีแก้ปัญหาก็คือการทำภาพหลายๆขนาดโดยแยกตาม DPI ของหน้าจอ ถ้าเครื่องที่ใช้เป็น MDPI ก็จะดึงภาพที่ขนาดเล็กไปแสดง แต่ถ้าเป็น XXHDPI ก็จะดึงภาพที่ขนาดใหญ่ไปแสดง


        ปัญหาที่ตามมาก็คือ มันไม่ใช่ภาพเล็กๆแบบภาพ Launcher Icon นี่สิ บางภาพต้องใช้ขนาด 800x800px มันก็เลยเป็นแบบนี้


        แค่ภาพ 3 ขนาดนี้คิดรวมๆกันแล้วก็ปาไป 2.2 MB แล้ว

        เฮ้ย บ้าป่าว งานเว็ปและแอพเค้าต้องทำเป็นภาพแบบ For Web สิ

        ใช่ครับ เป็น Best Practice อย่างหนึ่งของการนำภาพมาใช้งานในแอพ ถ้าเป็น Adobe Photoshop ก็จะมี Save for web ให้เลือก ซึ่งจะช่วยบีบอัดไฟล์ภาพให้มีขนาดเล็กลง ใครไม่ได้ทำแบบนี้ จับตีมือหักเลยนะครับ

        เมื่อ Save for web แล้วก็จะถูกลดขนาดจนเหลือแค่นี้


        คิดๆดูแล้วก็เหลือแค่ 458 KB เอง ลดไปได้ตั้งเยอะแน่ะ! และถ้าลด Quality เหลือประมาณ 80% - 90% ก็จะลดขนาดไฟล์ได้อีกหน่อยด้วยนะ!!

        แต่ถึงแม้ว่าจะลดขนาดได้เหลือ 458 KB ก็ยังรู้สึกว่าไฟล์ภาพยังใหญ่เกินไปอยู่ดี

        จะดีกว่ามั้ยถ้าไฟล์ภาพบางไฟล์นั้นเปลี่ยนมาใช้เป็น Vector Drawable แทนแล้วทำให้ขนาดภาพลดลงไปมากกว่าเดิมอีก แถมไม่ต้องทำภาพหลายขนาดด้วย เพราะทุกวันนี้ภาพหลายๆภาพที่ใช้ในแอพส่วนใหญ่มันดันถูกสร้างจาก Adobe Illustrator หรือ Sketch นี่สิ!!

        ทำไมต้องให้คนทำภาพมานั่งวาดใน Adobe Illustrator หรือ Sketch แล้วมานั่ง Export เป็น JPG แล้วส่งให้นักพัฒนาไปนั่ง Import ไฟล์เข้าโปรเจคเพื่อให้มันทำเป็นภาพหลายๆขนาดล่ะ จะดีกว่ามั้ยถ้าภาพหลายๆภาพที่สร้างขึ้นมาจาก Vector ตั้งแต่แรก ถูกเอาไปใช้งานในแอพโดยตรงเลย

Vector Drawable

        เป็น Drawable แบบใหม่ที่ถูกนำมาใช้งานใน Android 5.0 Lollipop ขึ้นไป ซึ่งรองรับการแสดงภาพแบบ Vector ซึ่งจะช่วยลดภาระนักพัฒนาให้น้อยลง โดยไฟล์จะอยู่ในรูปของ XML นะครับ เป็น XML Vector Graphic ซึ่งจะมีลักษณะแบบนี้


        แล้วเวลาแอพทำงานก็จะเอาข้อมูลใน XML ตัวนี้ไปวาดเป็นภาพนั่นเอง ซึ่งมีข้อดีคือ

        • ใช้แค่ไฟล์เดียวก็รองรับกับ DPI ทุกแบบ
        • ไฟล์ XML มีขนาดเล็กมากเมื่อเทียบกับ JPG หรือ PNG
        • เป็นภาพแบบ Vector จึงสามารถย่อ/ขยายแล้วภาพไม่เสียคุณภาพ

        และนอกจากนี้ยังเอาไปทำเป็น Animation ได้อีกด้วยนะ โดยใช้ Animated Vector Drawable เข้ามาช่วย แต่ในบทความนี้เจ้าของบล็อกจะยังไม่อธิบายตรงนี้นะครับ ขอเน้นไปที่ Vector Drawable เป็นหลักก่อน

เมื่อก่อน Vector Drawable ก็ไม่ได้น่าพิศมัยซักเท่าไรนัก

        ถึงแม้ว่า Vector Drawable จะมีมาตั้งแต่ในสมัย Android 5.0 Lollipop ที่เปิดตัวไปตั้งแต่เดือนพฤศจิกายน 2557 ซึ่งก็ราวๆปีครึ่งแล้ว แต่นักพัฒนาแอนดรอยด์ก็ไม่ค่อยอยากจะใช้มันซักเท่าไร

        ก็เพราะมันไม่ Backward Compatible น่ะสิ!!

        ถึงแม้ว่าทุกวันนี้จะมีผู้ใช้แอนดรอยด์ที่เป็น Android 5.0 ขึ้นไป เยอะขึ้นแล้วก็ตาม ซึ่งคิดเป็น 38.4% ของทั้งหมด แต่ก็ยังมีผู้ใช้อีกหลายๆคนที่ยังใช้เวอร์ชันต่ำกว่า Android 5.0 ซึ่งมีปริมาณเยอะกว่าอีก และแอพส่วนใหญ่นั้นก็รองรับเวอร์ชันต่ำกว่า Android 5.0 อยู่แล้วด้วย (เพื่อให้ครอบคลุมกลุ่มผู้ใช้งานให้กว้างมากที่สุด)

        ดังนั้นถ้าจะใช้ Vector Drawable ในแอพก็จะมีอยู่ 2 ทางด้วยกัน คือ

        • ทำแอพให้รองรับเวอร์ชันต่ำสุดที่ Android 5.0 เท่านั้น จะได้รองรับ Vector Drawable อย่างเต็มที่
        • ถ้าอยากจะรองรับเวอร์ชันต่ำกว่า Android 5.0 ด้วย ให้ใช้ Bitmap Drawable สำหรับเวอร์ชันต่ำกว่า Android 5.0 ลงไป ส่วน Android 5.0 ขึ้นไปก็ให้ใช้ Vector Drawable แทน

        ข้อแรกคงเป็นไปได้ เพราะมีแอพบางตัวที่ทำฟีเจอร์ที่ใช้งานใน Android 5.0 ขึ้นไป ซึ่งเจ้าของบล็อกก็เจออยู่บ่อยๆ แต่ก็ต้องแลกกับกลุ่มผู้ใช้ที่น้อยลง (ต้องรออีกพักใหญ่ๆเพื่อให้ผู้ใช้เปลี่ยนมาเป็น Android 5.0 ขึ้นไป)

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

ไม่ต้องห่วง Android Support Library ช่วยคุณได้!!

        การมาของ Android Support Library ในเวอร์ชัน 23.2 เรียกได้ว่าเป็นอะไรที่แจ่มแมวมากๆ เพราะว่าในเวอร์ชันนี้มันทำให้เครื่องที่เวอร์ชันต่ำกว่า Android 5.0 ลงไป สามารถรองรับ Vector Drawable (ขั้นต่ำ API 7)  กับ Animated Vector Drawable (ขั้นต่ำ API 11) ได้แล้วนั่นเอง

        นั่นหมายความว่านักพัฒนาสามารถใช้ Vector Drawable ในแอพได้โดยที่ไม่ต้องกังวลเรื่องเวอร์ชันของแอนดรอยด์แล้ว เย้!!

        ดังนั้นจึงเป็นนิมิตหมายที่ดีสำหรับการเปลี่ยนมาใช้ Vector Drawable ภายในแอพแทน Bitmap Drawable ของเก่า

วิธีการสร้าง Vector Drawable

        เจ้าของบล็อกจะขอพูดถึงการใช้งานร่วมกับ Android Support Library เลยนะครับ และจะไม่ได้สร้าง XML เอง แต่จะ Import มาจากไฟล์ SVG แทน ยกตัวอย่างไฟล์ภาพนี้ที่เจ้าของบล็อกวาดใน Adobe Illustrator


        เพื่อให้ Vector Drawable รองรับกับเวอร์ชันต่ำกว่า Android 5.0 ให้อัพเดท Android Support Library ใน Android SDK ให้เป็นเวอร์ชันล่าสุดซะก่อนนะ


        และมีเงื่อนไขสำคัญว่าโปรเจคของผู้ที่หลงเข้ามาอ่านควรใช้ AppCompat v7 นะครับ

        ในขั้นตอนการกำหนดให้โปรเจคใช้ Vector Drawable ของ Android Support Library จะต้องเช็คก่อนว่าโปรเจคของผู้ที่หลงเข้ามาอ่านนั้นใช้ Gradle Plugin เวอร์ชันอะไร โดยเข้าไปเช็คได้ที่ build.gradle ของที่อยู่ตัวนอกสุดของโปรเจค


        ในการกำหนดให้ใช้งาน Vector Drawable ของ Android Support Library ให้เข้าไปกำหนดใน build.gradle ใน Module หลักของแอพ (โดยปกติ Module จะชื่อ app)

        สำหรับ Gradle Plugin เวอร์ชัน 2.0 ขึ้นไปให้กำหนดลงใน defaultConfig แบบนี้

apply plugin: 'com.android.application'

android {
    ...

    defaultConfig {
        ...
        
        vectorDrawables.useSupportLibrary = true
    }
    buildTypes {
        ...
    }
}

dependencies {
    ...
}


        แต่ถ้าโปรเจคของผู้ที่หลงเข้ามาอ่านใช้ Gradle Plugin เป็นเวอร์ชัน 1.5 ให้กำหนดแบบนี้แทน

apply plugin: 'com.android.application'

android {
    ...

    defaultConfig {
        ...

        generatedDensities = []
    }

    aaptOptions {
        additionalParameters "--no-version-vectors"
    }

    buildTypes {
        ...
    }
}

dependencies {
    ...
}

        เท่านี้โปรเจคของผู้ที่หลงเข้ามาอ่านก็พร้อมสำหรับ Vector Drawable ของ Android Support Library แล้ว

        ในการ Import ไฟล์ SVG แล้วแปลงให้กลายเป็น Vector Drawable สามารถทำได้ง่ายมาก เพราะ Android Studio มีเมนูนี้ให้อยู่แล้ว โดยคลิกขวาที่โฟลเดอร์ res แล้วเลือกไปที่ New > Vector Asset เพื่อเปิดหน้าต่าง Vector Asset Studio ขึ้นมา


        ถ้าผู้ที่หลงเข้ามาอ่านต้องการ Vector Drawable ของ Material Design Icon ในนี้ก็มีให้เลือกได้ทันที แต่ถ้ามีไฟล์ SVG เป็นของตัวเองก็สามารถเลือกเพื่อแปลงเป็น Vector Drawable ได้เลยเช่นกัน


        ซึ่งทางทีม Android Studio แนะนำว่าให้ใช้วิธีนี้เพื่อแปลงไฟล์ SVG ให้เป็น Vector Drawable เท่านั้น เพราะบ่อยครั้งไฟล์ SVG ที่ถูกแปลกจากที่อื่นนั้นไม่สมบูรณ์หรืออาจจะเกิดปัญหาได้ การใช้ Vector Asset Studio จะมีหน้าต่าง Preview เพื่อแสดงให้เห็นว่าไฟล์ที่ Import เข้ามาจะแสดงผลยังไงในแอพด้วย อย่าลืมกำหนดขนาดที่ต้องการด้วยนะ (มีผลกับเวอร์ชันต่ำกว่า Android 5.0)

        เรื่องเล่าส่วนตัว - ก่อนหน้านี้เจ้าของบล็อกใช้ Plugin ของ Android Studio ตัวหนึ่งเพื่อแปลงไฟล์ SVG ให้กลายเป็น Vector Drawable แล้วพบว่ามันรันได้เกือบทุกเครื่องน่ะแหละ แต่ดันมีปัญหากับเครื่องที่เป็น Android 5.0 อยู่แค่เวอร์ชันเดียวเท่านั้น แต่พอเอาไฟล์ SVG เดิมมา Import ด้วย Vector Asset Studio ก็หมดปัญหาไปในทันที

        สุดท้ายก็จะกลายเป็นไฟล์ Vector Drawable แบบนี้


        เวลาใช้งานใน Image View กับ Image Button (หรือคลาสใดๆก็ตามที่สืบทอดมาจาก 2 คลาสนี้) จากเดิมที่กำหนดภาพด้วยคำสั่ง android:src

<ImageView
    android:id="@+id/imageView"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:src="@drawable/vector_sfl_avatar" />

         ก็ให้เปลี่ยนมาใช้เป็น app:srcCompat แทน (อย่าลืมกำหนด Namespace สำหรับ app ด้วยล่ะ)

<ImageView
    android:id="@+id/imageView"
    android:layout_width="100dp"
    android:layout_height="100dp" 
    app:srcCompat="@drawable/vector_sfl_avatar" />

        เพียงเท่านี้ก็สามารถใช้งาน Vector Drawable ได้โดยไม่ต้องห่วงเรื่องเวอร์ชันของแอนดรอยด์แล้ว

        ส่วนโค้ดตอน Runtime สามารถใช้คำสั่ง setImageResource โดยตรงได้เลย

        ถ้าสังเกตดีๆก็จะเห็นว่ามันเกิดมาเพื่อใช้กับ Image View หรือ Image Button เท่าน้ัน และไม่สามารถใช้กำหนดเป็นภาพพื้นหลังได้ (กำหนดได้เฉพาะ src เท่านั้น) ดังนั้นการใช้ Vector Drawable ควรดูตามความเหมาะสมด้วยนะครับ และภาพที่ใช้ไม่ควรซับซ้อนมากนัก (ภาพตัวอย่างที่ใช้นั่นแหละครับคือซับซ้อนเกินไปหน่อยตรงลายบนเสื้อ)

ทำไม Vector Drawable ถึงรองรับกับเวอร์ชันต่ำกว่า Android 5.0 ได้?

        เพื่อให้เข้าใจเบื้องหลังการทำงานมากขึ้น ดังนั้นมาดูกันดีกว่าว่าเกิดอะไรขึ้น ทำไม Vector Drawable ถึงรองรับกับเวอร์ชันเก่าๆได้

        และนี่คือสิ่งที่เกิดขึ้นหลังจาก Gradle ทำการ Build Project เสร็จ


        ไฟล์ Vector Drawable นั้นจะถูกแปลงให้กลายเป็นภาพ PNG เพื่อใช้ในเวอร์ชันเก่าๆ ส่วนเวอร์ชัน 21 หรือ Android 5.0 ก็จะใช้เป็น Vector Drawable แทน ซึ่งสิ่งหนึ่งที่น่าประทับใจในตอนสร้างเป็นไฟล์ PNG ก็คือไฟล์ที่ออกมาจะถูกบีบอัดให้มีขนาดเล็กสุดๆ เรียกได้ว่าเจ้าของบล็อก Export ไฟล์ออกมามีขนาด 13KB แต่เมื่อเทียบกับที่ Android Studio กลับมีขนาดเหลือแค่ 9KB เท่านั้นเอง (เหมือนใช้โปรแกรม ImageOptim ช่วยลดขนาดไฟล์อีกทีนั่นเอง)

        นั่นล่ะครับสาเหตุที่ Vector Drawable มันรองรับเวอร์ชันต่ำกว่า Android 5.0 ได้

ข้อจำกัดของ Vector Drawable บนเวอร์ชันต่ำกว่า Android 5.0

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

        • XML ใน Vector Drawable จะไม่รองรับ <group> และ <clip-path> แต่อาจจะรองรับในภายหลัง ซึ่งตรงนี้ก็ให้ข้ามไป เพราะไปใช้ Vector Asset Studio เข้ามาช่วยแทน
        • ไม่รองรับ Attribute ต่างๆของ Vector Drawable รวมไปถึง Auto Mirroring สำหรับ RTL
        • XML ใน Vector Drawable ไม่สามารถ Reference ไปยัง Resource อื่นๆได้ (เช่น กำหนดขนาดด้วย @dimen/... ในนี้ไม่ได้)
        • ควรใช้ Vector Asset Studio ในการแปลง SVG ให้กลายเป็น Vector Drawable
        • ไฟล์ SVG ที่แปลงเป็น Vector Drawable เมื่อถูกแปลงเป็น PNG อาจจะแสดงผลไม่เหมือนต้นฉบับ ซึ่งการใช้ Vector Asset Studio จะช่วย Preview ให้เห็นได้ทันที
        • ในการดึงไฟล์ Vector Drawable มาใช้งาน ถ้าเป็น Android 5.0 ขึ้นไปก็จะ Cast Class ให้เป็น VectorDrawable น่ะแหละ แต่ถ้าเป็นเวอร์ชันต่ำกว่า Android 5.0 ให้ Cast Class เป็น BitmapDrawable แทน (เพราะเบื้องหลังมันคือแปลงเป็นไฟล์ PNG นั่นเอง)
        • ขนาดภาพ PNG ที่ถูกสร้างขึ้นมาจาก Vector Drawable จะขึ้นอยู่กับขนาดที่กำหนดไว้ในไฟล์นั้นๆ ดังนั้นควรกำหนดขนาดของภาพให้เหมาะสมกับที่จะเอาไปใช้งานด้วย (กำหนดเป็นหน่วย dp)

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="100dp"
    android:height="100dp"
    ... >

    ...

</vector>

        เพราะถ้าไม่กำหนดขนาดที่เหมาะสม เวลา Build มันจะถูกสร้างเป็น PNG ที่มีขนาดเล็กเกิน (ขนาดประมาณ 32dp) ซึ่งจะทำให้ได้ภาพเบลอในเวอร์ชันที่ต่ำกว่า Android 5.0 ลงไป


สรุป

        Vector Drawable ที่ถูกนักพัฒนามองข้ามกันไปเพราะเรื่องของ Backward Compability ตอนนี้ก็สามารถใช้งานได้อย่างหายห่วงแล้ว แต่ก็อาจจะมีข้อจำกัดอยู่บ้างในบางอย่าง แต่ว่าการใช้งานทั่วไปก็ไม่มีปัญหาอะไร ดังนั้นถ้าเป็นไปได้ก็อยากจะแนะนำให้นักพัฒนาเปลี่ยนมาใช้ Vector Drawable กันดูบ้างนะครับ เพราะมันจะช่วยลดเวลาและภาระในการจัดการภาพภายในแอพของผู้ที่หลงเข้ามาอ่านได้เป็นอย่างดี ไม่ต้องมานั่งคำนวณขนาดภาพที่จะต้องทำในแต่ละหน้าจออีกต่อไป ลดขนาดแอพไปในตัวด้วยล่ะ และที่สำคัญคือมี Material Design Icon ให้เลือกใช้ได้เลย เย้

        และเนื่องจาก Support Vector Drawable จะสร้างไฟล์ PNG เพื่อให้ทำงานบนเวอร์ชันเก่าๆได้ ดังนั้นทางที่ดีควรทำ APK แยกเวอร์ชันระหว่าง API 21 และต่ำกว่า เพื่อไม่ให้ APK มีขนาดใหญ่เกินจำเป็น

        มามะ มาเข้าสู่โลกของ Vector กันเถอะ

แหล่งอ้างอิงข้อมูล

        • Android Support Library 23.2 [Android Developers Blog]
        • AppCompat v23.2 — Age of the vectors




เหล่าพันธมิตรแอนดรอยด์

Devahoy Layer Net NuuNeoI The Cheese Factory Somkiat CC Mart Routine Artit-K Arnondora Kamonway Try to be android developer Oatrice Benz Nest Studios Kotchaphan@Medium Jirawatee@Medium Travispea@Medium