Compose主題切換——讓你的APP也能一鍵換膚

開(kāi)端

應(yīng)用換膚,這真的是一個(gè)老生常談的問(wèn)題豌拙,從原生安卓開(kāi)始陕悬、到后來(lái)的 Flutter ,再到現(xiàn)在的 Compose 按傅,雖說(shuō)老生常談捉超,但其實(shí)還是新瓶裝舊酒。

安卓原生的主題切換這里不再說(shuō)了逞敷,這不是本文的重點(diǎn),況且那個(gè)一篇文章估計(jì)也說(shuō)不清??灌侣。

Flutter 的主題切換主要依賴(lài)于 provider 狀態(tài)管理推捐,其實(shí)在 Compose 中也差不多,且聽(tīng)完娓娓道來(lái)侧啼!

GitHub 地址在文章末尾牛柒。先來(lái)看看實(shí)現(xiàn)效果吧:


compose換膚1.gif

經(jīng)過(guò)

其實(shí) Compose 雖說(shuō)換膚實(shí)現(xiàn)很簡(jiǎn)單,但是這也需要在你遵守 Compose 開(kāi)發(fā)規(guī)范的前提下痊乾,比如定義顏色的時(shí)候不使用硬編碼皮壁,而使用 MaterialTheme 中的顏色,當(dāng)然還有 Shape 哪审、Typography 等蛾魄。

Compose 中的主題大家應(yīng)該都很熟悉了,但應(yīng)該還有不是很熟悉的,這里先來(lái)簡(jiǎn)單說(shuō)下吧滴须。

Compose 中的主題

當(dāng)你創(chuàng)建一個(gè)新的 Compose 項(xiàng)目之后舌狗,系統(tǒng)會(huì)自動(dòng)幫你創(chuàng)建下面的結(jié)構(gòu):

image.png

可以看到系統(tǒng)一共創(chuàng)建了四個(gè)文件,顧名思義扔水,分別為:顏色痛侍、形狀、主題魔市、類(lèi)型主届,本文咱們主要看顏色及主題。

先來(lái)看看 Color 文件默認(rèn)是什么樣子的吧:

val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)

上面就是 Color 文件中的代碼待德,只是簡(jiǎn)單定義了四種顏色君丁。

下面再來(lái)看看 Theme 文件吧:

private val DarkColorPalette = darkColors(
    primary = Purple200,
    primaryVariant = Purple700,
    secondary = Teal200
)

private val LightColorPalette = lightColors(
    primary = Purple500,
    primaryVariant = Purple700,
    secondary = Teal200
)

@Composable
fun PlayAndroidTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) {
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

    MaterialTheme(
        colors = colors,
        typography = Typography,
        shapes = Shapes,
        content = content
    )
}

可以看到 Theme 中先是定義了深色和淺色兩個(gè)顏色,然后通過(guò)判斷是否為夜間模式來(lái)動(dòng)態(tài)適配磅网。

下面來(lái)看看 lightColors 方法吧:

fun lightColors(
    primary: Color = Color(0xFF6200EE),
    primaryVariant: Color = Color(0xFF3700B3),
    secondary: Color = Color(0xFF03DAC6),
    secondaryVariant: Color = Color(0xFF018786),
    background: Color = Color.White,
    surface: Color = Color.White,
    error: Color = Color(0xFFB00020),
    onPrimary: Color = Color.White,
    onSecondary: Color = Color.Black,
    onBackground: Color = Color.Black,
    onSurface: Color = Color.Black,
    onError: Color = Color.White
): Colors

通過(guò)上面代碼可以得知谈截,可以在這里設(shè)置每種顏色值,深淺模式都類(lèi)似涧偷,都可以進(jìn)行設(shè)置簸喂。

使用主題

上面簡(jiǎn)單說(shuō)了下 Compose 中的主題,那么主題寫(xiě)好之后應(yīng)該如何進(jìn)行使用呢燎潮?來(lái)看代碼:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        // 使用主題
        PlayAndroidTheme {
            NavGraph()
        }
    }
}

沒(méi)錯(cuò)喻鳄,就這么簡(jiǎn)單,只需要在頁(yè)面外層嵌套下剛才設(shè)置的主題即可∪贩猓現(xiàn)在主題是設(shè)置上了除呵,那應(yīng)該如何使用剛才設(shè)置到主題中的那些顏色呢?亦或是別的資源爪喘?上代碼:

// 顏色使用
MaterialTheme.colors.primary
// 形狀使用
MaterialTheme.shapes.large
// 字體類(lèi)型使用
MaterialTheme.typography.body1

還記得上面所說(shuō)的需要遵守 Compose 的開(kāi)發(fā)規(guī)范吧颜曾,所說(shuō)的其實(shí)就是使用這些資源的時(shí)候盡量使用咱們定義好的。

解決

如何切換主題

首先需要思考如何來(lái)進(jìn)行主題的切換秉剑,整個(gè)主題肯定使用在項(xiàng)目的開(kāi)始——啟動(dòng) Activity 中泛豪,但切換主題的頁(yè)面肯定不在一塊,那這個(gè)時(shí)候應(yīng)該如何在切換主題頁(yè)面切換了之后讓 Activity 知道呢侦鹏?

最開(kāi)始的時(shí)候我的想法還是不夠 Compose 诡曙,我想的是使用廣播,在切換主題頁(yè)面點(diǎn)擊之后發(fā)送一個(gè)廣播略水,然后在 Activity 中進(jìn)行接收价卤,然后接收到之后刷新。我確實(shí)這樣做了渊涝,功能也確實(shí)實(shí)現(xiàn)了慎璧,但是總感覺(jué)哪里不對(duì)床嫌,感覺(jué) Compose 中不應(yīng)該這樣才對(duì)。

中午在食堂吃飯的時(shí)候突然想到:Compose 中全部都是以狀態(tài)驅(qū)動(dòng) UI 改變的炸卑,我直接將主題切換設(shè)置成一個(gè)狀態(tài)不得了既鞠!

開(kāi)搞

說(shuō)干就干,首先先來(lái)設(shè)置下咱們默認(rèn)的幾套主題吧:

// 天藍(lán)色
const val SKY_BLUE_THEME = 0

// 灰色
const val GRAY_THEME = 1

// 深藍(lán)色
const val DEEP_BLUE_THEME = 2

// 綠色
const val GREEN_THEME = 3

// 紫色
const val PURPLE_THEME = 4

// 橘黃色
const val ORANGE_THEME = 5

// 棕色
const val BROWN_THEME = 6

// 紅色
const val RED_THEME = 7

// 青色
const val CYAN_THEME = 8

// 品紅色
const val MAGENTA_THEME = 9

這里為了之后代碼簡(jiǎn)單點(diǎn)沒(méi)有使用枚舉盖文,其實(shí)都差不多的嘱蛋。

然后添加一個(gè)主題的狀態(tài):

/**
 * 主題狀態(tài)
 */
val themeTypeState: MutableState<Int> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
    mutableStateOf(getDefaultThemeId())
}

可以看到主題狀態(tài)中有一個(gè)名為 getDefaultThemeId 的方法,用來(lái)獲取默認(rèn)主題 ID五续,來(lái)看下吧:

/**
 * 獲取當(dāng)前默認(rèn)主題
 */
fun getDefaultThemeId(): Int = DataStoreUtils.getSyncData(CHANGED_THEME, SKY_BLUE_THEME)

這里使用了 DataStore 來(lái)進(jìn)行數(shù)據(jù)的存取洒敏,DataStore 也是 Jetpack 中的一員,感興趣的可以看看我之前寫(xiě)的文章:再抱一抱DataStore

然后修改下主題方法:

@Composable
fun PlayAndroidTheme(
    themeId: Int = 0,
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colors = if (darkTheme) {
        playDarkColors()
    } else {
        getThemeForThemeId(themeId)
    }

    MaterialTheme(
        colors = colors,
        typography = typography,
        shapes = Shapes,
        content = content
    )
}

可以看到上面添加了一個(gè)參數(shù) themeId 用來(lái)表示當(dāng)前需要使用的主題 id疙驾,getThemeForThemeId 方法用來(lái)通過(guò)主題 ID 來(lái)獲取需要的主題顏色集凶伙,來(lái)看下實(shí)現(xiàn)吧:

/**
 * 通過(guò)主題 ID 來(lái)獲取需要的主題
 */
private fun getThemeForThemeId(themeId: Int) = when (themeId) {
    SKY_BLUE_THEME -> {
        playLightColors(
            primary = primaryLight
        )
    }
    GRAY_THEME -> {
        playLightColors(
            primary = gray_theme
        )
    }
    // 別的主題類(lèi)似,篇幅原因省略
}

接下來(lái)修改下 Activity 中的調(diào)用吧:

PlayAndroidTheme(themeTypeState.value) {
    NavGraph()
}

由于 themeTypeState 是一個(gè) State它碎,所以當(dāng)它的值改變的時(shí)候函荣,就會(huì)自動(dòng)刷新 UI。

收尾

創(chuàng)建主題列表

先做下準(zhǔn)備工作扳肛,先來(lái)創(chuàng)建一個(gè)用來(lái)存放主題信息的實(shí)體類(lèi)吧:

data class ThemeModel(val color: Color, val colorId: Int, val colorName: String)

接下來(lái)將咱們添加的主題放到一個(gè) List 中:

// 主題model列表
private val themeList = arrayListOf(
    ThemeModel(primaryLight, SKY_BLUE_THEME, "天藍(lán)色"),
    ThemeModel(gray_theme, GRAY_THEME, "灰色"),
    ThemeModel(deep_blue_theme, DEEP_BLUE_THEME, "深藍(lán)色"),
    ThemeModel(green_theme, GREEN_THEME, "綠色"),
    ThemeModel(purple_theme, PURPLE_THEME, "紫色"),
    ThemeModel(orange_theme, ORANGE_THEME, "橘黃色"),
    ThemeModel(brown_theme, BROWN_THEME, "棕色"),
    ThemeModel(red_theme, RED_THEME, "紅色"),
    ThemeModel(cyan_theme, CYAN_THEME, "青色"),
    ThemeModel(magenta_theme, MAGENTA_THEME, "品紅色"),
)

創(chuàng)建主題切換頁(yè)面

萬(wàn)事具備傻挂,只欠東風(fēng)。現(xiàn)在主題這塊已經(jīng)全部準(zhǔn)備好了挖息,只需要再創(chuàng)建一個(gè)主題切換的頁(yè)面金拒,點(diǎn)擊的時(shí)候保存下來(lái)主題 ID 并刷新下 themeTypeState 的值即可。

image.png

由上圖可以看出這個(gè)布局最好使用 LazyVerticalGrid套腹,然后設(shè)置下一行放 5 個(gè) item 即可:

LazyVerticalGrid(
    cells = GridCells.Fixed(5),
    modifier = Modifier.padding(horizontal = 10.dp)
) {
    items(themeList) { item: ThemeModel ->
        ThemeItem(playTheme, item) {
            val playTheme = item.colorId
            themeTypeState.value = playTheme
            DataStoreUtils.putSyncData(CHANGED_THEME, playTheme)
        }
    }
}

OK了绪抛,結(jié)束了,這樣就可以了电禀,已經(jīng)可以實(shí)現(xiàn)文章開(kāi)頭 Gif 圖的那種效果了幢码。

結(jié)局

這么快就到結(jié)局了,還有點(diǎn)不舍尖飞,哈哈哈症副。光顧著寫(xiě)實(shí)現(xiàn)了,還沒(méi)放 Github 地址呢葫松,在這里:https://github.com/zhujiang521/PlayAndroid

別忘了切換分支:compose

上面對(duì)主題的講解只是一帶而過(guò)瓦糕,如果大家想系統(tǒng)地學(xué)習(xí) Compose 的話(huà)底洗,可以購(gòu)買(mǎi)我的新書(shū)《Jetpack Compose:Android全新UI編程》進(jìn)行閱讀腋么,里面有完整的 Compose 框架供大家學(xué)習(xí)。

京東購(gòu)買(mǎi)地址

當(dāng)當(dāng)購(gòu)買(mǎi)地址

如果對(duì)你有幫助的話(huà)亥揖,別忘記點(diǎn)個(gè) Star珊擂,感激不盡圣勒。

其實(shí)還有一些細(xì)節(jié)的東西我沒(méi)有說(shuō)到,大家如果有疑問(wèn)的話(huà)可以在評(píng)論區(qū)提出來(lái)摧扇。

好了圣贸,先寫(xiě)到這里吧,再會(huì)扛稽!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吁峻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子在张,更是在濱河造成了極大的恐慌用含,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帮匾,死亡現(xiàn)場(chǎng)離奇詭異啄骇,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)瘟斜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)缸夹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人螺句,你說(shuō)我怎么就攤上這事虽惭。” “怎么了壹蔓?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵趟妥,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我佣蓉,道長(zhǎng)披摄,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任勇凭,我火速辦了婚禮疚膊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘虾标。我一直安慰自己寓盗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布璧函。 她就那樣靜靜地躺著傀蚌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蘸吓。 梳的紋絲不亂的頭發(fā)上善炫,一...
    開(kāi)封第一講書(shū)人閱讀 52,736評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音库继,去河邊找鬼箩艺。 笑死窜醉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的艺谆。 我是一名探鬼主播榨惰,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼静汤!你這毒婦竟也來(lái)了琅催?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤虫给,失蹤者是張志新(化名)和其女友劉穎恢暖,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體狰右,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡杰捂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棋蚌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫁佳。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谷暮,靈堂內(nèi)的尸體忽然破棺而出蒿往,到底是詐尸還是另有隱情,我是刑警寧澤湿弦,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布瓤漏,位于F島的核電站,受9級(jí)特大地震影響颊埃,放射性物質(zhì)發(fā)生泄漏蔬充。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一班利、第九天 我趴在偏房一處隱蔽的房頂上張望饥漫。 院中可真熱鬧,春花似錦罗标、人聲如沸庸队。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)彻消。三九已至,卻和暖如春宙拉,著一層夾襖步出監(jiān)牢的瞬間宾尚,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工鼓黔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留央勒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓澳化,卻偏偏與公主長(zhǎng)得像崔步,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缎谷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容