ทดลองทำ Dark Mode เพื่อไม่ให้ user บ่นว่าแสบตา
อย่าให้ user บ่นว่าสีแอพแสบตา ถนอมสายตาให้เขาบ้างเนอะ และเรามารักษาให้ user อยู่กับเราไปนานๆกันเถอะ
ตัว dark mode นั้นจริงๆมีชื่อแบบ official ใน Android Developer ว่า Dark Theme เน้อ มาใน Android 10 หรือ API Level 29 นั่นเอง
ประโยชน์ของการใช้ Dark Theme มีดังนี้
- ลดการใช้พลังงาน อันนี้ขึ้นอยู่กับหน้าจอของมือถือเครื่องนั้นๆด้วยน้า
- ช่วยให้ user มองเห็น UI แอพเราได้มากขึ้น สำหรับผู้ที่มีสายตาเลือนราง และคนที่ตาแพ้แสง เช่นเจ้าของบล็อกนั่นเอง
- ทำให้ user สามรถใช้งานแอพเราในที่ที่มีแสงน้อยได้มากขึ้น เราจะไม่แสบตาในที่มืดกันอีกต่อไป (เอาจริงๆก็ไม่ควรเล่นในที่มืดป่ะ)
- แน่นอนทำแล้ว user ชอบ user รักแน่นอน อย่างแอพจอยลดาก็มี user เรียกร้องว่าให้ทำ dark mode สักทีเถอะ จนทางทีม developer เขาทำให้จ้า
เราได้ยินครั้งแรกจากงาน Google I/O Extended 2019 ที่ Android GDE อย่างพี่เอกได้พูดวิธีการเปลี่ยนไว้ทั้งหมด 4 วิธีเช่นกัน ดังนั้นเราจะลองแต่ละวิธีที่บอกไว้ใน document เพื่อดูความแตกต่างกัน
และมีหลายๆแอพที่ใช้จริงแล้ว เช่น Messenger, Medium, Notion, Github เป็นต้น
ถ้าแอพในไทย เช่น จาละดอย จอยลดา นั่นเอง
มาลองทำแต่ละวิธีกันดูดีกว่า
วิธีแรก : เปลี่ยน Theme ให้ support กับ Dark Theme ซะ
วิธีนี้ง่ายๆเลย ไปที่ style.xml
แล้วเปลี่ยนเป็น Theme DayNight
ซะ หน้าตาจะประมาณนี้
<style name="AppTheme" parent="Theme.AppCompat.DayNight">
ถ้าเป็นสายใช้ Material Component เพื่อความสวยงามชาติตระกูล Android จะเป็นแบบนี้
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">
ทดสอบดูกันจ้า โดยเปิด dark mode ที่เครื่องเนอะ
ในที่นี้แอบปรับสี background ไปเพราะ ของเดิมที่ใส่สีพื้นหลังเป็นสีเทา พอมา dark mode มันดันไม่ dark ด้วยนี่แหะ
สิ่งที่พึงระวังก็คือ อย่า hard code พวก resource ต่างๆ หรือ icon ต่างๆ สำหรับ light theme ควรทำเผื่อ dark theme ด้วย และ attributes ที่สำคัญสำหรับในเรื่องนี้ก็คือ
?android:attr/textColorPrimary
สีตัวหนังสือ ควรเลือกสีที่อ่านเห็นชัดเจนทั้ง light theme และ dark theme?attr/colorControlNormal
สีของ icon ตอน disabled state
ทั้งนี้ทั้งนั้นควรดู color theming design ด้วยนะ
แต่ก็ไม่ใช่มือถือทุกเครื่องนี่นา ที่มันเปิดปิด dark mode ได้ ดังนั้นเราสามารถใช้คำสั่งนี้เพื่อให้แสดง theme ของแอพได้
ถ้าเราอยากให้มัน dark mode ก็ใช้คำสั่งนี้
AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_YES)
จริงๆในคำสั่ง setDefaultNightMode()
ก็มีหลาย mode ให้เลือกกัน ซึ่งชื่อตัวแปรมันค่อนข้างตรงตัวมากเลยนะ
สามารถดูเพิ่มเติมได้ที่นี่เน้อ
https://developer.android.com/reference/androidx/appcompat/app/AppCompatDelegate#setdefaultnightmode
วิธีที่สอง : Force ให้เข้าสู่สายมืด
แน่นอนเราเป็น developer ที่ขี้เกียจ และไม่อยากรบกวนเพิ่มงานให้ designer งั้นไปแก้ที่ Hardware ไปเลยสิ
วิธีการทำ ไปใส่สิ่งนี้ใน Style.xml
โดยตัว theme ที่ใช้ใน activity นั้จะต้องเป็น Light
เพราะใน Dark
จะไม่มีผลนะเออ
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<!-- Add force dark here. -->
<item name="android:forceDarkAllowed">true</item>
</style>
แต่เอาจริงๆมันรองรับ API Level 29 ขึ้นไปมั้งนะ อาจจะเป็นวิธีที่ไม่ค่อย work อ่ะ แล้วทางเราไม่มีเครื่องทดลองด้วยนะ นอกจาก Emulator ผลที่ได้เหมือนมันไม่ force ให้ง่ะ ข้ามๆไปเนอะ
วิธีที่สาม : แล้ว Best Practices ของทาง Android หล่ะ
ใน document เขาจะบอกประมาณนี้
- สำหรับ Launch Screen เราต้องลบ hardcode color ทิ้ง และใช้ resource color แทน ส่วนสีขาวที่เป็น default เขาแนะนำให้ใช้
?android:attr/colorBackground
แทน - ใน Configuration Changes เราจะ trigger ด้วย
uiMode
ในแต่ละActivity
ที่เราใส่ในAndroidManifest.xml
เพื่อให้แอพสร้าง activity ที่เป็น dark theme ให้เรา โดยใส่ไปแบบนี้android:configChanges:"uiMode"
สรุปรวมๆคือ เขาแนะนำให้แยก color resource อีกชุดนึงสำหรับ dark mode โดยปกติอ่ะ จะมี resource พวกสีที่ชื่อว่า color.xml
อยู่ใน folder res/values
เนอะ ทีนี้เราจะสร้าง color.xml
ขึ้นมาอีกหนึ่งไฟล์ โดยให้อยู่ใน folder res/values-night
เพื่อเวลาที่เราเปลี่ยนเป็น dark theme แล้วแอพจะใช้ชุดสีชุดนี้เลยเนอะ
สิ่งที่พึงระลึกได้ตั้งนานแล้วก็คือ เราไม่ควร hard code ในการ set สีต่างๆเข้าไปนั่นเอง เช่นการใช้ hex color กำหนดเข้าไป หรือแม้กระทั่งการใช้ color resource จากฝั่ง Android เอง เช่น @android:color/white
งี้ ควรกำหนดชื่อสีเข้าไปใน color resource ของเราเอง
นอกจากเรื่องสีแล้ว สิ่งนึงที่น่าปวดหัวอีกก็คือ Drawable ต่างๆนั่นเอง ทางเราเลยใช้รูปที่เป็น vector แล้วมาเปลี่ยนค่าสีที่ hard code เป็นสีใน color.xml
ของเรานั่นเอง
เวลาเราแก้สีอะไรต่างๆน้านนน ก็จะมีความสงสัยว่า เอ้ออตอนเป็น dark mode แล้วจะเป็นยังไงนะ ไปที่ Layout Editor แล้วกด icon "Orientation for Preview" จากนั้นไปที่ Night Mode แล้วติ๊กที่ Night
ผลที่ได้จะเป็นประมาณนี้เนอะ
มาดูการ set value กันชัดๆจ้า เราจะ set แค่บางสีเท่านั้นนะ ที่จะให้แสดงสีที่เหมาะสมเมื่อเป็น dark mode เนอะ
ปล. สำหรับการตั้งชื่อสี หรือหาสี หวังว่าเว็บนี้น่าจะช่วยได้นะจ๊ะ
ถ้าเราอยากทำ setting menu ให้เลือกตอนสลับ light mode กับ dark mode หล่ะ
มาดูในแอพจอยลดากัน เขาจะเปลี่ยนโหมดกลางคืนที่ setting เนอะ หน้าตาจะเป็นแบบนี้
ก็คือใช้ตัว setting ซึ่งอยู่ใน Android Jetpacks นั่นเองงงง~~~ เราจึงนำมาสรุปบางส่วนเนอะ สามารถอ่าน document เต็มๆได้ที่นี่
https://developer.android.com/guide/topics/ui/settings
ขั้นตอนแรกเลย เพิ่ม dependency กันก่อนเลย
implementation 'androidx.preference:preference-ktx:1.1.1'
จากนั้นกด sync gradle รอบนึง
ต่อไปเราจะเริ่มสร้างสิ่งที่เรียกว่า PreferenceScreen กัน โดยมันจะแบ่งเป็น hierarchy ต่างๆ โดยเจ้าตัว PreferenceScreen เป็น parent ตามตัวอย่างใน document จะเป็นดังนี้
ในลูกของ PreferenceScreen จะมีตัว switch toggle เปิดปิดที่ชื่อว่า SwitchPreferenceCompat
และตัวที่เฉยๆไว้บอกข้อความไปก่อนก็คือ Perference
นั่นเอง
และถ้าแอพเราใหญ่ และมี settings ต่างๆเต็มไปหมด เราจึงต้องใช้ PreferenceCategory
ในการแบ่งหมวดหมู่ต่างๆให้สวยงามยิ่งขึ้น โดยไส้ในจะเป็นอะไรก็ได้ ใส่เท่าไหร่ก็ได้
จริงๆมีตัวอย่างแบบกดเปิดแล้วไปอีกหน้า โดยไปทำการใส่ fragment ลงไปเพิ่มใน Preference
ซึ่งอันนี้ขอข้ามไปก่อนเนอะ เนื่องจากเป็นบล็อก dark mode
https://developer.android.com/guide/topics/ui/settings/organize-your-settings
สำหรับ design ต่างๆเข้าไปดูเพิ่มเติมได้ที่นี่จ้า
https://source.android.com/devices/tech/settings/settings-guidelines
และมี codelab ด้วยนะเออ
https://developer.android.com/codelabs/android-training-adding-settings-to-app
นอกจาก SwitchPreferenceCompat
และ Preference
เฉยๆแล้ว ยังมีตัวอื่นอีกเช่น EditTextPreference
, ListPreference
สามารถไปส่องเพิ่มได้ที่นี่
https://developer.android.com/reference/androidx/preference/package-summary
https://developer.android.com/guide/topics/ui/settings/components-and-attributes
ในที่นี้ เราจะสร้างแบบธรรมดา คือเปิดปิด dark mode ได้ และบอกเลข version ของแอพเนอะ ดังนั้น เราจะต้องสร้างไฟล์ที่ res/xml
ที่มีชื่อว่า preference_settings.xml
ข้างในจะเป็นดังนี้
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
app:key="pref_dark_mode"
app:title="Enable Darkmode" />
<Preference
app:key="pref_app_version"
app:summary="1.0.5"
app:title="App Version" />
</PreferenceScreen>
ใส่เสร็จจะเจอหน้าตาแบบนี้
อันดับถัดมานั้นเราจะทำการสร้าง fragment ขึ้นมา โดย extend จาก PreferenceFragmentCompat
ทำการ override onCreatePreferences
ขึ้นมา แล้วใส่เจ้า xml ที่เราสร้างเมื่อกี้แบบนี้
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preference_setting, rootKey)
}
}
จากนั้น เราทำการแปะ fragment ที่เพิ่งสร้างใหม่นี้ตามปกติ พอกดบิ้วแอพไปแล้วจะเป็นเช่นนี้
ซึ่งไม่ผิดหรอก แต่มันไม่สวยเว้ย!~
เราเองได้ลองแบบ fragment แปะ fragment ด้วยกัน ผลที่ได้คือ โอโหววว พื้นใสไปไหน สุดท้ายเราเลยสร้าง Activity
ขึ้นมาใหม่ตัวนี้ ใส่ toolbar ลงไป แล้วแปะ fragment นี้ไป หน้าตาสวยงามใช้ได้หล่ะ
การทำงานในที่นี้คือ เมื่อเปลี่ยนระหว่าง light/dark mode แล้ว รบกวนช่วยเปลี่ยนทันทีให้หน่อยสิ ไปที่ SettingsActivity
แล้ว register และ unregister เมื่อค่า SharePreference เปลี่ยนไป
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this)
}
override fun onDestroy() {
super.onDestroy()
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this)
}
จากนั้นมา override function ที่ชื่อว่า onSharedPreferenceChanged
เพื่อรับค่าต่างๆที่เปลี่ยนแปลงไปเนอะ
เมื่อรันแอพแล้วเวลาเรา toggle เปิดปิด dark mode มันจะเปลี่ยนตามหล่ะ
ต่อมาเราจะเอาค่ามาใช้งานในการ set dark/light theme เนอะ สำหรับเวลาเปิดแอพมาใหม่ให้เปลี่ยนตามธีมที่เราเลือกไว้ โดยทำการอัญเชิญ PreferenceManager ขึ้นมา แล้วทำการ get value by key ขึ้นมา โดยตัว enable dark mode นั้นเราจะใช้ key ที่ชื่อว่า pref_dark_mode
และทำการ check ถ้าค่าเป็น true ให้ enable dark mode นะ ถ้าไม่ก็ไม่ต้องเน้อ
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
when (sharedPreferences.getBoolean("pref_dark_mode", false)) {
true -> AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_YES)
false -> AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_NO)
}
ในที่นี้เราจะใช้วิธีที่ 1 คือ set theme เป็น DatNight และวิธีที่ 3 คือทำการใช้ color resource เนื่องจากบาง layout เราเองไม่ได้ระบุสี จึงต้องใช้ทั้งสองวิธีนี้ผสมกันจ้า
แน่นอนว่าพอเราเอา function นี้ไปใส่ใน MainApplication
ที่ onCreate
แน่นอนว่ามันจะเปลี่ยนสีหลังจากเข้าแอพมาใหม่เท่านั้น ทีนี้มันจะเปลี่ยนตามทั้งแอพเลย
จริงๆแล้วเราสามารถสร้าง PreferenceScreen ได้โดยโค้ดก็ได้เช่นกันจ้า
https://developer.android.com/guide/topics/ui/settings/programmatic-hierarchy
ก่อนจะจบกันไป สามารถอ่านเรื่อง dark mode เพิ่มเติมได้ที่ document นี้เลยจ้า
https://developer.android.com/guide/topics/ui/look-and-feel/darktheme
ส่วนวิธีสุดท้ายที่พี่เอกพูดตอนนั้นคือ ช่างแม่ง อ่ะจบบล็อกแล้วแยกย้ายเลยแล้วกันเนอะ
แอพอ่านบล็อก redesign ใหม่ล่าสุดของเพจเราจ้า
ติดตามข่าวสารและบทความใหม่ๆได้ที่
และช่องทางใหม่ใน Twiter จ้า