22 March 2020

Notification in Android ตอนที่ 3 - ทำให้ Notification สมบูรณ์ยิ่งขึ้น

Updated on

หลังจากที่ได้ลองสร้าง Notification แบบง่ายๆกันไปในบทความที่แล้ว ในบทความนี้ก็จะมาพูดถึงส่วนสำคัญอื่นๆที่ทำให้ Notification แสดงผลได้อย่างสมบูรณ์กันมากขึ้นดีกว่า

บทความในซีรีย์เดียวกัน

• Notification in Android ตอนที่ 1 - เรื่องพื้นฐานของ Notification ที่ควรรู้
• Notification in Android ตอนที่ 2 - คำสั่งพื้นฐานของ Notification
• Notification in Android ตอนที่ 3 - ทำให้ Notification สมบูรณ์ยิ่งขึ้น
• Notification in Android ตอนที่ 4 - Notification Action
• Notification in Android ตอนที่ 5 - Notification Channel
• Notification in Android ตอนที่ 6 - กำหนด Notification Style ในรูปแบบต่างๆ
• Notification in Android ตอนที่ 7 - การแจ้งเตือนแบบ Heads-up notification
• Notification in Android ตอนที่ 8 - อัปเดตข้อมูลให้กับ Notification

ต่อจากความเดิมตอนที่แล้ว

จากคำสั่งในบทความที่แล้ว จะได้ Notification ที่แสดงผลออกมาเป็นแบบนี้


ถ้าดูแบบผ่านๆก็อาจจะเหมือนเสร็จสมบูรณ์แล้ว แต่มาลองดูกันว่ามีอะไรที่เจ้าของบล็อกต้องปรับเพิ่มเพื่อให้เป็น Notification ที่สมบูรณ์กว่านี้

ลองกำหนดสีให้กับ Notification ดูสิ

เพื่อเพิ่มความโดดเด่นและเป็นภาพจำที่ดีสำหรับผู้ใช้ ที่จะรู้ได้ทันทีว่าสีของ Notification นี้เป็นของแอปอะไร

โดยการกำหนดสีจะต้องกำหนดเป็นแบบ ARGB ที่เป็น Color Integer ดังเช่นตัวอย่างนี้

val notification = NotificationCompat.Builder(...).apply {
    ...
    color = Color.parseColor("#3ddc84")
}.build()

ซึ่งสีที่กำหนดไว้ใน Notification ก็จะมีผลเฉพาะบางส่วนของ Notification เท่านั้น นักพัฒนาไม่สามารถเปลี่ยนสีในจุดอื่นๆได้ตามใจชอบ (ถ้าจะทำแบบนั้นต้องใช้เป็น Decorated Custom View Style ที่ต้องสร้างขึ้นมาเองทั้งหมด) ทั้งนี้ก็เพื่อให้ Notification Drawer แสดงผลอย่างเป็นมิตรกับสายตาของผู้ใช้อยู่เสมอ


และถ้าต้องการกำหนดสีด้วย Color Resource ก็ให้ใช้คำสั่งแบบนี้แทน

val notification = NotificationCompat.Builder(...).apply {
    ...
    color = ContextCompat.getColor(context, R.color.primary)
}.build()

ซึ่งความสามารถในการกำหนดสีให้กับ Notification ได้ถูกเพิ่มเข้ามาใน Android 5.0 Lollipop (API 21) แต่ได้ถูกปรับเปลี่ยนมาเรื่อยๆจนถึงเวอร์ชันล่าสุด

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


และตั้งแต่ Android 6.0 Marshmallow (API 23) ขึ้นไป สีของ Notification จะไม่ได้ตรงกับที่กำหนดไว้ในโค้ดเป๊ะๆ ทั้งนี้ก็เพื่อป้องกันผู้ใช้กำหนดสีไม่เหมาะสม เช่น สีที่กำหนดมีความอ่อนเกินไปจนมองเห็นได้ยาก เป็นต้น โดยระบบแอนดรอยด์จะปรับสีให้เหมาะสมกับพื้นหลัง


นอกจากนี้ในแอนดรอยด์เวอร์ชันใหม่ๆที่มี Dark Theme เพิ่มเข้ามา ก็จะมีการปรับความสว่างสีให้เหมาะกับ Light Theme และ Dark Theme อีกด้วย


ดังนั้นอย่าแปลกใจว่าสีที่เห็นใน Notification ไม่ตรงกับที่กำหนดไว้ในโค้ด

เลือกใช้ภาพไอคอนที่เหมาะสมกับการแสดงผลใน Notification

นักพัฒนาสามารถกำหนดภาพไอคอนสำหรับ Notification ได้ตามใจชอบ โดยจะต้องเป็นภาพจาก Drawable Resource เท่านั้น แต่ก็ใช่ว่าจะเลือกภาพไหนก็ได้มาใช้นะ

ใช้ภาพแบบ Iconography ที่มีสีเดียว

ไม่ควรใช้ภาพที่มีรายละเอียดเยอะ หรือประกอบไปด้วยสีมากกว่า 1 สีขึ้นไป


นั่นก็เพราะว่าแอนดรอยด์จะมีการปรับสี (Tint) ไอคอนของ Notification ให้เหมาะสมกับเครื่อง ดังนั้นภาพไอคอนที่นักพัฒนากำหนดไว้ก็อาจจะถูกปรับเปลี่ยนสีได้เสมอ


จึงเป็นที่มาว่าทำไมควรกำหนดภาพไอคอนของ Notification ด้วยภาพแบบ Iconography ที่มีสีเดียวเท่านั้น

เตรียมภาพไอคอนตามขนาดหน้าจอต่างๆ

แอนดรอยด์ได้กำหนด Guideline สำหรับขนาดไอคอนที่จะใช้เป็นภาพไอคอนของ Notification ไว้ดังนี้


XXXHDPI : ใช้ภาพขนาด 96 x 96 px
XXHDPI : ใช้ภาพขนาด 72 x 72 px
XHDPI : ใช้ภาพขนาด 48 x 48 px
HDPI : ใช้ภาพขนาด 36 x 36 px
MDPI : ใช้ภาพขนาด 24 x 24 px

และถ้าเป็น API 24 ขึ้นไป นักพัฒนาสามารถใช้ Vector Drawable ขนาด 24 x 24 px แทนได้เลย

ดังนั้นภาพไอคอนของ Notification ใน Drawable Resource โดยแยกตาม Qualifier ควรจะเป็นลักษณะแบบนี้

res
 +-- drawable-anydpi-v24
 |     \-- ic_notification.xml
 +-- drawable-hdpi
 |     \-- ic_notification.xml
 +-- drawable-ldpi
 |     \-- ic_notification.xml
 +-- drawable-mdpi
 |     \-- ic_notification.xml
 +-- drawable-xhdpi
 |     \-- ic_notification.xml
 +-- drawable-xxhdpi
 |     \-- ic_notification.xml
 \-- drawable-xxxhdpi
       \-- ic_notification.xml

การใส่ภาพให้ครอบคลุมทุกขนาดหน้าจอและเวอร์ชันแอนดรอยด์ตาม Guideline ของแอนดรอยด์จะช่วยให้ Notification ของนักพัฒนาสามารถแสดงได้เหมาะสมไม่ว่าจะเป็นอุปกรณ์แอนดรอยด์ใดๆก็ตาม โดยไม่ต้องกังวลว่าจะแสดงภาพไอคอนผิดเพี้ยนไปจากปกติ

ทำให้ Notification สามารถขยายดูข้อมูลทั้งหมดได้เสมอ

จะเห็นว่า Content Text ที่เจ้าของบล็อกกำหนดใน Notification นั้น เป็นข้อความที่ยาวมาก จนทำให้ข้อความบางส่วนถูกตัดออกไป ทำให้ผู้ใช้ไม่สามารถเห็นข้อความทั้งหมดได้

แอนดรอยด์ในเวอร์ชันหลังๆมานี้ จะให้ผู้ใช้ขยาย Notification ได้ เพื่อให้ผู้ใช้สามารถดูรายละเอียดของ Notification แบบเต็มๆได้


แต่ทว่า Notification แบบปกตินั้นไม่ได้ออกแบบมาให้ขยายได้ตั้งแต่แรก ดังนั้นนักพัฒนาจึงต้องเขียนโค้ดเพิ่มเข้าไปเอง

นั่นก็เพราะว่าเดิมที Notification ไม่สามารถขยายเพื่อดูเนื้อหาทั้งหมดได้ เนื่องจากมีรูปแบบในการแสดงผลเป็นแบบ Standard Style

เพื่อทำให้ขยายได้ นักพัฒนาก็จะต้องเพิ่ม Big Text Style เข้าไปใน Notification ด้วยเพราะเวลาย่อ Notification จะแสดงเป็นแบบ Standard Style และเวลาขยายจะเปลี่ยนไปแสดงแบบ Big Text Style แทน


ดังนั้นจะต้องเพิ่ม Big Text Style เข้าไปในตอนสร้างข้อมูลสำหรับ Notification แบบนี้

val text = "Why don't you look at Sleeping For Less website. There's a lot of interest article about Android application development."
val notification = NotificationCompat.Builder(...).apply {
    ...
    setContentText(text)
    setStyle(NotificationCompat.BigTextStyle().bigText(text))
}.build()

จะเห็นว่าการทำให้ Notification รองรับการแสดงผลแบบ Big Text Style นั้นสามารถทำได้ง่ายมาก เพียงแค่เพิ่มคำสั่งเข้าไป 1 บรรทัดเท่านั้น

ถึงแม้ว่าข้อความที่ใช้ใน Content Text กับ Big Text จะเป็นข้อความเดียวกัน แต่ต้องใส่ไว้ทั้ง 2 ตัวเสมอ เพราะว่า Content Text ใช้สำหรับ Standard Style ส่วน Big Text ใช้สำหรับ Big Text Style นั่นเอง

กำหนด Large Icon ในเวลาที่ต้องการแสดงภาพใน Notification

เพราะ Notification แบบปกติจะแสดงภาพไอคอนเท่านั้น โดยภาพไอคอนจะถูกปรับสีให้เหมาะสมกับการแสดงผล ซึ่งจะเป็นสีโทนเดียว จึงทำให้นักพัฒนาไม่สามารถใช้ภาพประกอบสำหรับ Notification ใส่เป็นภาพไอคอนได้

ถ้าต้องการใส่ภาพประกอบด้วย นักพัฒนาจะต้องกำหนดภาพที่ชื่อว่า Large Icon เพิ่มเข้าไป

val largeIcon: Bitmap = ...
val notification = NotificationCompat.Builder(...).apply {
    ...
    setLargeIcon(largeIcon)
}.build()

ในการกำหนด Large Icon จะต้องกำหนดเป็น Bitmap เพื่อให้รองรับภาพจากที่ใดก็ได้ ขอแค่ทำให้อยู่ในรูปแบบ Bitmap ก็พอ

โดยภาพ Large Icon บน Android 5.0 Marshmallow (API 21) จะอยู่แทนที่ตำแหน่งของภาพไอคอน แล้วย่อภาพไอคอนให้มีขนาดเล็กลงและอยู่ที่มุมขวาล่างของภาพ Large Icon แทน


แต่ใน Android 6.0 Marshmallow (API 23) เป็นต้นมา ได้มีการปรับตำแหน่งของภาพ Large Icon ให้ไปอยู่ที่ขวาสุดของ Notification แทน

กำหนด Priority ของ Notification (ในเวอร์ชันเก่า) และ Importance ของ Notification Channel (ในเวอร์ชันใหม่)

นักพัฒนาสามารถกำหนดลำดับความสำคัญของ Notification ได้ เพื่อแยกระหว่าง Notification ที่สำคัญมากๆ และไม่ได้สำคัญมากนัก ซึ่งจะส่งผลต่อการแสดงผลและการจัดลำดับของ Notification ใน Notification Drawer ด้วย

แต่ทว่าการจัดลำดับความสำคัญของ Notification จะมีอยู่ 2 แบบ โดยขึ้นอยู่กับเวอร์ชันของแอนดรอยด์

• เวอร์ชันที่ต่ำกว่า Android 8.0 Oreo (API 26) จะใช้ Priority ที่กำหนดไว้ใน Notification
• ตั้งแต่เวอร์ชัน Android 8.0 Oreo (API 26) เป็นต้นไป จะใช้ Importance ที่กำหนดไว้ใน Notification Channel

Priority ของ Notification สำหรับเวอร์ชันที่ต่ำกว่า Android 8.0 Oreo (API 26)

สำหรับคำสั่งของ Priority จะอยู่ในตอนที่สร้าง Notification โดยมีคำสั่งลักษณะแบบนี้

val notification = NotificationCompat.Builder(...).apply {
    ...
    priority = NotificationCompat.PRIORITY_HIGH
}.build()

โดย Priority ของ Notification จะมีทั้งหมด 5 ระดับด้วยกัน

NotificationCompat.PRIORITY_MAX
NotificationCompat.PRIORITY_HIGH
NotificationCompat.PRIORITY_DEFAULT
NotificationCompat.PRIORITY_LOW
NotificationCompat.PRIORITY_MIN

ถ้าไม่ได้กำหนดค่า Priority ให้กับ Notification จะถือว่ามีค่าเป็น Default เสมอ

และถึงแม้ว่าจะมีให้กำหนดทั้งหมด 5 ระดับด้วยกันก็จริง แต่ในการใช้งานจริงจะใช้อยู่แค่ 3 ระดับเท่านั้น นั่นก็คือ High, Default และ Low

High Priority : มีไว้สำหรับ Notification ที่สำคัญมากต่อผู้ใช้ ระบบจะแสดงด้วยวิธีที่โดดเด่น เพื่อให้ผู้ใช้เห็นได้ง่าย อย่างเช่น Head-up Notification ซึ่งจะทำให้นักพัฒนามั่นใจได้ว่าผู้ใช้จะมองเห็นอย่างแน่นอน
Default Priority : Notification ที่ไม่ได้มีความสำคัญมาก แต่ต้องการแสดงให้ผู้ใช้เห็น ควรกำหนดเป็น Default Priority เสมอ ซึ่งจะแสดงผลเป็น Notification แบบปกติ และสามารถถูกซ่อนในบางสถานการณ์อย่าง Do Not Disturb ได้
Low Priority : สำหรับ Notification ที่มีความสำคัญต่ำมาก ไม่จำเป็นต้องให้ผู้ใช้เห็นก็ได้ และในบางกรณีก็อาจจะถูกแสดงด้วยวิธีที่ไม่โดดเด่นมากนัก

Importance ของ Notification Channel สำหรับ Android 8.0 Oreo (API 26) เป็นต้นไป

คำสั่งของ Importance จะถูกกำหนดในตอนที่สร้าง Notification Channel ขึ้นมา โดยมีคำสั่งแบบนี้

val id = "new_promotion"
val name = "New Promotion"
val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH)

โดย Importance ของ Notification Channel จะมีทั้งหมด 7 ระดับด้วยกัน

NotificationManager.IMPORTANCE_MAX
NotificationManager.IMPORTANCE_HIGH
NotificationManager.IMPORTANCE_DEFAULT
NotificationManager.IMPORTANCE_LOW
NotificationManager.IMPORTANCE_MIN
NotificationManager.IMPORTANCE_NONE
NotificationManager.IMPORTANCE_UNSPECIFIED

ถึงแม้ว่า Importance จะมีให้กำหนดมากถึง 7 ระดับด้วยกัน แต่ในการใช้งานจริงๆจะนิยมใช้กันแค่ 3 ระดับเท่านั้น คือ High, Default และ Low โดยรูปแบบและเงื่อนไขจะเหมือนกับ Priority ของ Notification เลย

กำหนดทั้ง Importance และ Priority ให้เหมือนกัน

เนื่องจาก Importance ใช้กับเวอร์ชันใหม่ๆ ส่วน Priority ใช้กับเวอร์ชันเก่า ถ้าต้องการทำให้แอปรองรับทั้ง 2 แบบ นักพัฒนาควรกำหนด Importance ใน Notification Channel และ Priority ใน Notification ทั้งคู่ให้เหมือนกัน

// Importance for API 26 or higher
val channel = NotificationChannel(/* ID */, /* Name */, NotificationManager.IMPORTANCE_DEFAULT)

// Priority for lower than API 26
val notification = NotificationCompat.Builder(...).apply {
    priority = NotificationCompat.PRIORITY_DEFAULT
}

ปัจจัยที่มีผลต่อ Priority/Importance ของ Notification

อย่างที่บอกไปว่าการกำหนด Priority หรือ Importance จะมีผลต่อการแสดงผลของ Notification โดยขึ้นอยู่กับเงื่อนไขต่างๆของเครื่องใน ณ ตอนนั้น ซึ่งเบื้องต้นจะประกอบไปด้วย

• สถานะ Awake และ Sleep ของเครื่อง
• การเปิด/ปิดโหมด Do Not Disturb
• การใช้งานแอปอื่นๆแบบ Fullscreen
• การเปิด/ปิด Notification Snooze
• การตั้งค่าจากผู้ใช้สำหรับการแสดงผล Notification ในแอปนั้นๆ
• เวอร์ชันของแอนดรอยด์

ดังนั้นนักพัฒนาจึงไม่สามารถคาดหวังให้ Notification ทำงานได้เหมือนที่ต้องการเสมอไป เนื่องจากระบบแอนดรอยด์มีการจัดการวิธีแสดงผลของ Notification สำหรับแต่ละ Priority/Importance ด้วยปัจจัยอื่นๆที่แตกต่างกันไป เพื่อให้เหมาะสมกับการใช้งานของผู้ใช้ในแต่ละสถานการณ์

ควรกำหนด Priority หรือ Importance ให้เหมาะสมกับเนื้อหา

เหตุที่ระบบแอนดรอยด์ต้องมีการจัดการเรื่อง Priority/Importance ให้กับ Notification นั่นก็เพราะว่าในการใช้งานจริงนั้น แต่ละแอปก็จะมี Notification ของตัวเองเยอะมากมาย ซึ่งสามารถแบ่งออกได้เป็นหลายกลุ่มหรือประเภท

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

กำหนด Category ให้กับ Notification อย่างเหมาะสม

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

val notification = NotificationCompat.Builder(...).apply {
    ...
    setCategory(NotificationCompat.CATEGORY_PROMO)
}.build()

ดังนั้นถ้าสามารถกำหนดหมวดหมู่ของ Notification ที่จะแสดงได้ ก็แนะนำให้ใส่ไว้ดีกว่า

ผู้ใช้ควรกดที่ Notification แล้วทำอะไรบางอย่างในแอปได้

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

เพื่อให้ Notification ของแอปสามารถมีอะไรบางอย่างได้ นักพัฒนาก็จะต้องกำหนดสิ่งที่เรียกว่า Pending Intent เพิ่มเข้าไป เพื่อให้มันทำงานในตอนที่ผู้ใช้กดที่ Notification

val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.akexorcist.com"))
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val notification = NotificationCompat.Builder(...).apply {
    ...
    setContentIntent(pendingIntent)
    setAutoCancel(true)
}.build()

จากตัวอย่างโค้ดข้างบนนี้ เมื่อผู้ใช้กดที่ Notification ของแอป จะให้เปิดหน้าเว็ปตาม URL ที่กำหนดไว้

ถ้าแค่ต้องการให้เปิดแอปขึ้นมาเฉยๆ ก็ให้สร้าง Intent แบบนี้แทน

val context = ...
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)

โดย Pending Intent มีไว้สำหรับกำหนด Intent ที่อยากจะให้เกิดขึ้นเมื่อผู้ใช้กดที่ Notification ซึ่งนักพัฒนาสามารถสร้าง Intent ที่เหมาะสมกับ Notification แต่ละอันได้ตามใจชอบ เช่น Notification ที่แจ้งเตือนว่ามีโปรโมชั่นใหม่ เมื่อผู้ใช้กดก็สั่งให้เปิดที่หน้าแสดงโปรโมชั่นนั้นในแอปได้เลย เป็นต้น

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

ในที่สุด Notification ก็สมบูรณ์มากขึ้นแล้ว

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

และถ้าต้องการให้ Notification ทำได้มากกว่านี้ ก็ต้องติดตามกันต่อไปในบทความหน้าแล้วล่ะ