ทดลองทำ Dark Mode เพื่อไม่ให้ user บ่นว่าแสบตา

Android Dec 4, 2020

อย่าให้ user บ่นว่าสีแอพแสบตา ถนอมสายตาให้เขาบ้างเนอะ และเรามารักษาให้ user อยู่กับเราไปนานๆกันเถอะ

ไม่มีรูป Android ให้เราเลย อ่ะยืมรูป iPhone มาแปป / Photo by Daniel Korpai / Unsplash

ตัว 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 เพื่อดูความแตกต่างกัน

กลับมาอัพเดตข่าวสารกับชาวโลกในงาน Google I/O Extended Bangkok 2019
เอาจริงๆเหมือนปีนี้งาน Google I/O ไม่ค่อยมีอะไรใหม่ๆชวนว๊าวมากเท่าปีที่แล้วเลยแหะ ในความเห็นส่วนตัวเรานะสำหรับส่วน developer

และมีหลายๆแอพที่ใช้จริงแล้ว เช่น 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 ด้วยนะ

Material Design
Build beautiful, usable products faster. Material Design is an adaptable system—backed by open-source code—that helps teams build high quality digital experiences.
https://material.io/develop/android/theming/color

แต่ก็ไม่ใช่มือถือทุกเครื่องนี่นา ที่มันเปิดปิด 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 เนอะ

ปล. สำหรับการตั้งชื่อสี หรือหาสี หวังว่าเว็บนี้น่าจะช่วยได้นะจ๊ะ

Coolors - The super fast color schemes generator!
Generate or browse beautiful color combinations for your designs.

ถ้าเราอยากทำ 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 แน่นอนว่ามันจะเปลี่ยนสีหลังจากเข้าแอพมาใหม่เท่านั้น ทีนี้มันจะเปลี่ยนตามทั้งแอพเลย

ขออภัยกับรูปปกด้วยจ้า ฮือออออ https://youtu.be/Q_khmgkdzfI

จริงๆแล้วเราสามารถสร้าง 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 ใหม่ล่าสุดของเพจเราจ้า

MikkiPastel - Apps on Google Play
First application from “MikkiPastel” on play store beta feature- read blog from https://www.mikkipastel.com by this application- read blog content by chrome custom tab- update or refresh new content by pull to refresh- share content to social network

ติดตามข่าวสารและบทความใหม่ๆได้ที่

อย่าลืมกด like กด share บทความกันด้วยนะคะ :)

Posted by MikkiPastel on Sunday, 10 December 2017

และช่องทางใหม่ใน Twiter จ้า

Tags

Minseo Chayabanjonglerd

I am a full-time Android Developer and part-time contributor with developer community and web3 world, who believe people have hard skills and soft skills to up-skill to da moon.