Jetpack-Compose 學(xué)習(xí)筆記(六)—— Compose 主題 Theme 一探究竟,換膚還能如此 Easy & Silky榜田?

斷更一時(shí)爽益兄,一直斷更一直爽~ 哈哈哈,就當(dāng)給自己放了個(gè)長(zhǎng)假吧箭券。最近的行情太糟了净捅,身邊有同學(xué)已經(jīng)被畢業(yè),兩個(gè)多月終于降薪找到下家··· 這里呼吁大家一定要存好六個(gè)月沒(méi)有工作還能正常生活的銀子辩块,以備不時(shí)之需蛔六!希望疫情能早日平息,經(jīng)濟(jì)可以快速恢復(fù)吧~

自己也沒(méi)想到這個(gè)系列可以到第六篇废亭,斷更確實(shí)很久了国章,居然還收到了小伙伴的催更,感謝你們的不離不棄豆村。閑話少說(shuō)液兽,我們這次要介紹的是 Compose 主題,那么 Compose 主題 Theme 到底有什么掌动?用 Compose 實(shí)現(xiàn)換膚簡(jiǎn)單嗎四啰?一起來(lái)看看吧宁玫!

Jetpack Compose 的主題 Theme 就是一套 UI 風(fēng)格,其中包括字體拟逮、字號(hào)撬统、色值等等,類比于 Android View 體系中的 Theme.MaterialComponents.DayNight.DarkActionBar等等的主題樣式敦迄。與 View 體系最大的不同在于,它完全拋棄了 xml 文件的設(shè)置凭迹,所有樣式都是通過(guò)代碼設(shè)置的罚屋,主題樣式大體可以分為 色值、文案樣式嗅绸、形狀樣式 三大類脾猛。先來(lái)看看主題中的色值。

1. Color 色值

許多組件不僅支持設(shè)置它自己的背景色鱼鸠,還可以設(shè)置它包含的其他可組合項(xiàng)的默認(rèn)色值猛拴,使用 contentColorFor方法就可以實(shí)現(xiàn)。例如下面 code 1:

// code 1
Surface (color = Color.Yellow,contentColor = Color.Red) {
    Text(text = "July 2021",style = typography.body2)
}

你會(huì)發(fā)現(xiàn)蚀狰,Surface的背景色為黃色愉昆,而 Text中文案為 紅色,如果將 Text換為 Icon麻蹋,那么 Icon的色調(diào)也會(huì)變?yōu)榧t色跛溉,感興趣的同學(xué)可以試試。

類似 Surface的還有 TopAppBar可組合項(xiàng)扮授,下面是它們的實(shí)現(xiàn)源碼:

// code 2
Surface(
  color: Color = MaterialTheme.colors.surface,
  contentColor: Color = contentColorFor(color),
  ...

TopAppBar(
  backgroundColor: Color = MaterialTheme.colors.primarySurface,
  contentColor: Color = contentColorFor(backgroundColor),
  ...

Compose 官方推薦使用 Surface來(lái)給任何可組合項(xiàng)設(shè)置顏色芳室,因?yàn)樗鼤?huì)設(shè)置適當(dāng)?shù)膬?nèi)容顏色 CompositionLocal值,看 code 2 中 Surfacecolor屬性就默認(rèn)設(shè)置了 MaterialTheme.colors.surface色值刹勃。不推薦直接調(diào)用 Modifier.background設(shè)置顏色堪侯,因?yàn)樗](méi)有設(shè)置任何的默認(rèn)色值。在實(shí)際開發(fā)中荔仁,其實(shí)咱也沒(méi)咋用到 MaterialTheme伍宦,所以這里還是看個(gè)人吧~

// code 3
-Row(Modifier.background(MaterialTheme.colors.primary)) {    // 不推薦
+Surface(color = MaterialTheme.colors.primary) {    // 推薦
+  Row(
...

在可組合項(xiàng)中,一些 UI 的參數(shù)是有默認(rèn)值的咕晋,比如 Alpha 透明度雹拄、ContentColor 內(nèi)容色等。我們可以使用CompositionLocalProvider類去自定義這些屬性的默認(rèn)值掌呜。比如:

// code 4
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
    Text(text = "Hello, 修之竹~")
}

對(duì)比沒(méi)有加 CompositionLocalProvider的情況滓玖,會(huì)發(fā)現(xiàn)文案顏色更淺。這是因?yàn)橹式叮J(rèn)情況下 Text文案的 alpha值為 ContentAlpha.high势篡,這里設(shè)置為 ContentAlpha.disabled翩肌,還有一個(gè) ContentAlpha.mediumalpha值的大小排序?yàn)椋?code>high > medium > disabled禁悠。具體的值可以查看源碼念祭,它還分了高對(duì)比度和低對(duì)比度兩種情況。

Compose 在暗夜模式支持方面也做的不錯(cuò)碍侦。比如粱坤,是否在淺色模式中運(yùn)行的判斷很簡(jiǎn)單:

// code 5
val isLightTheme = MaterialTheme.colors.isLight

此外,如果在實(shí)際中就是使用的 MaterialTheme中的色值來(lái)設(shè)置瓷产,那么需要注意的是站玄,Compose 默認(rèn)的可組合項(xiàng)中常見(jiàn)的情況是在淺色模式中將容器設(shè)為 primary色值,在暗夜模式中將其設(shè)為 surface色值濒旦,許多組件默認(rèn)都是使用這種模式株旷,例如TopAppBar(應(yīng)用欄) 和 BottomNavigation(底部導(dǎo)航欄)。

2. 文案樣式

文案樣式也可以復(fù)用 MaterialTheme中已有的字體樣式尔邓,當(dāng)然也可以先將已有的樣式 copy 一份晾剖,然后修改其中的某些屬性。比如可以修改字間距:

// code 6
    Text(
        text = "Hello, 修之竹~",
        // style = MaterialTheme.typography.body1    // 復(fù)用 MaterialTheme 中的字體樣式
        style = MaterialTheme.typography.body1.copy(    // copy 已有樣式并修改字間距屬性的值
             letterSpacing = 5.sp
        ),
        fontSize = 20.sp    // 在Text中設(shè)置 fontSize 可重寫覆蓋 MaterialTheme.typography.body1 TextStyle 中的字體大小
    )

2.1 AnnotatedString 類來(lái)設(shè)置多種樣式

AnnotatedString用來(lái)代替 SpannableString最好不過(guò)了梯嗽,因?yàn)樗娴谋?SpannableString好用多了齿尽!再也不用擔(dān)心使用 SpannableString引發(fā)的數(shù)組越界問(wèn)題了。代碼及效果如下慷荔,當(dāng)然還可以實(shí)現(xiàn)許多其他的文案樣式雕什,感興趣的同學(xué)可以自行查閱 SpanStyle的官方文檔。

// code 7
val annotatedString = buildAnnotatedString {
    withStyle(SpanStyle(color = Color.Red, fontWeight = FontWeight.Bold)) {
        append("Kotlin ")
    }
    append("是世上 ")
    withStyle(SpanStyle(fontSize = 24.sp)) {
        append("最好的語(yǔ)言")
    }
}
Text(text = annotatedString)
圖 1

SpanStyle是設(shè)置文案的樣式的显晶,作用于字符單位贷岸;而如果要針對(duì)文案的行高、對(duì)齊方式等進(jìn)行設(shè)置磷雇,則需要使用ParagraphStyle偿警,顧名思義它是針對(duì)段落樣式的。

3. 形狀樣式

MaterialTheme主題中也有 Shape形狀屬性唯笙,在許多的官方 Composable 組件中都有這個(gè) Shape屬性螟蒸,比如 Button組件的 Shape屬性默認(rèn)值就是 MaterialTheme.shapes.small

// code 8
fun Button(
    ···
    shape: Shape = MaterialTheme.shapes.small,
    ···
) {
}

Shapes.kt提供了 small崩掘、medium七嫌、large3 種不同的屬性值,其實(shí)都是 RoundedCornerShape的具體實(shí)現(xiàn)苞慢,只不過(guò)圓角的大小不太一樣罷了诵原,具體數(shù)值可查看源碼。

如果需要在自定義 Composable 組件中使用 Shape,有兩種方法:一是使用擁有 Shape屬性的官方 Composable 組件绍赛;二是使用 Modifier中可設(shè)置 shape的方法去接收自定義 Composable 組件傳進(jìn)來(lái)的 Shape參數(shù)值蔓纠。先來(lái)看看第一種方法褒搔,如 code 9 所示绊汹。

// code 9
@Composable
fun RoundedCornerImage(painter: Painter, cornerSize: Int) {
    Surface(
        shape = RoundedCornerShape(cornerSize.dp)
    ) {
        Image(
            painter = painter,
            contentDescription = "圓角圖片"
        )
    }
}

這是個(gè)可以設(shè)置圖片圓角大小的自定義 Composable 組件,因?yàn)樾枰玫?Shape設(shè)置圓角昼窗,所以使用了 Surface這個(gè)組件的 Shape 屬性來(lái)具體實(shí)現(xiàn)蚯妇。

第二種方法就是借助 Modifier的方法敷燎,比如 Modifier.clip(shape: Shape)Modifier.background(color: Color, shape: Shape = RectangleShape)箩言、Modifier.border(width: Dp, brush: Brush, shape: Shape)等等懈叹。比較簡(jiǎn)單,感興趣的同學(xué)可以試試分扎。

4. 切換主題

上面說(shuō)了這么多,其實(shí)都是針對(duì)單個(gè)主題說(shuō)的胧洒,在實(shí)際應(yīng)用中畏吓,我們可以做個(gè)切換主題的小功能,如下圖 2 所示:

圖 2

其中包含了色值卫漫、字體菲饼、形狀的切換,用到的思路和原理都是一樣的列赎,所以這里就只拿主題色值的切換來(lái)說(shuō)明宏悦。想要實(shí)現(xiàn)這一功能,首先需要明白的是包吝,點(diǎn)擊事件之后切換主題的回調(diào)該怎么做饼煞?

總不能給所有設(shè)置色值的地方都設(shè)置一個(gè)監(jiān)聽(tīng)器吧?那樣做想想都覺(jué)得“酸爽”诗越。其實(shí)砖瞧,在 Compose 中,我們可以將當(dāng)前主題用一個(gè) MutableState對(duì)象來(lái)保存嚷狞,然后將主題中的色值集合與這個(gè)狀態(tài)相關(guān)聯(lián)块促,當(dāng)用戶切換主題改變了這個(gè) MutableState值之后,與之關(guān)聯(lián)的色值集合就會(huì)收到回調(diào)進(jìn)行切換床未,同時(shí)通知 Compose 進(jìn)行重組竭翠,這樣就使用新的色值集合進(jìn)行渲染了。

關(guān)于 MutableState狀態(tài)的相關(guān)知識(shí)薇搁,可以查閱我的另一篇文章:Jetpack-Compose 學(xué)習(xí)筆記(五)—— State 狀態(tài)是個(gè)啥斋扰?又是新概念?

OK,整體的思路有了褥实,咱們?cè)僭敿?xì)看看具體是如何實(shí)現(xiàn)的呀狼。按照之前的分析,我們需要在每次渲染頁(yè)面的時(shí)候讀取當(dāng)前主題的值损离,所以哥艇,首先得先獲取當(dāng)前的主題值。我這里是使用 MMKV存儲(chǔ)當(dāng)前主題值僻澎,主題值是 String類型貌踏,如下 code 10 所示:

// code 10
    //獲取選中的主題 id
    val chosenThemeId = remember {
        mutableStateOf(
            MMKV.defaultMMKV().getString(MMKVConstant.ChosenThemeCode, ThemeKinds.DEFAULT.name)
                ?: ThemeKinds.DEFAULT.name
        )
    }
    
enum class ThemeKinds {
    DEFAULT,    //默認(rèn)主題
    RED,    //紅色主題
    YELLOW,    //黃色主題
    BLUE    //藍(lán)色主題
}

然后自定義主題,在這里需要規(guī)定主題用到的色值窟勃、文案樣式祖乳、形狀樣式等。在每次切換主題后秉氧,在這里還需要根據(jù)傳入的當(dāng)前主題值眷昆,設(shè)置相應(yīng)的色值組等等。詳細(xì)如下代碼:

// code 11
@Composable
fun CustomTheme(
    chosenThemeId: MutableState<String>,
    content: @Composable () -> Unit
) {
    //自定義主題色值
    val colors = when (chosenThemeId.value) {
        ThemeKinds.DEFAULT.name -> {
            LightColors
        }
        ThemeKinds.RED.name -> {
            RedThemeColors
        }
        ThemeKinds.YELLOW.name -> {
            YellowThemeColors
        }
        ThemeKinds.BLUE.name -> {
            BlueThemeColors
        }
        else -> {
            DarkColors
        }
    }

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

//紅色主題色值
private val RedThemeColors = lightColors(
    primary = Color(0xFFFF4040),
    background = Color(0x66FF4040)
)

//黃色主題色值
private val YellowThemeColors = lightColors(
    primary = Color(0xFFDAA520),
    background = Color(0x66FFD700)
)

//藍(lán)色主題色值
private val BlueThemeColors = lightColors(
    primary = Color(0xFF436EEE),
    background = Color(0x6600FFFF)
)

private val DarkColors = darkColors(
    primary = Color.White,
    primaryVariant = Red700,
    onPrimary = Color.Black,
    secondary = Red300,
    onSecondary = Color.Black,
    error = Red200
)

private val LightColors = lightColors(
    primary = Color.Black,
    primaryVariant = Red900,
    onPrimary = Color.White,
    secondary = Red700,
    secondaryVariant = Red900,
    onSecondary = Color.White,
    error = Red800,
)

可以看到汁咏,在我們自定義的主題 CustomTheme最后亚斋,還是使用的 MaterialTheme,只不過(guò)將官方的 MaterialThemecolors設(shè)置成了我們自己的 colors攘滩,同理帅刊,我們還可以設(shè)置文案 typography和 形狀 shapes等參數(shù)。

其實(shí)漂问,所謂的色值組就是一個(gè) Colors對(duì)象赖瞒,Compose 中默認(rèn)就有 lightColorsdarkColors兩種 Colors對(duì)象,分別用于暗夜模式和白天模式的主題色值的設(shè)置蚤假,我們這里統(tǒng)一是以白天模式的 lightColors對(duì)象為基準(zhǔn)來(lái)進(jìn)行其他主題色值的設(shè)置栏饮,作為例子這里就重寫了 primarybackground兩個(gè)屬性,分別用來(lái)設(shè)置文案色值和背景色的色值勤哗。

定義好自定義主題中的各個(gè)色值組后抡爹,別忘了最后還是要設(shè)置到 MaterialTheme中的 colors屬性中,然后我們才可以通過(guò)調(diào)用 MaterialTheme colors來(lái)使用自定義主題中的各個(gè)色值芒划。下面的代碼就是使用樣例:

// code 12
CustomTheme(chosenThemeId) {
        Surface(color = MaterialTheme.colors.background) {
            ···
        }
    }

所以冬竟,如果我們要新增一組色值,我們只需要在 CustomTheme中新增一組主題色值就可以了民逼,不用去改動(dòng)設(shè)置色值的代碼泵殴,改動(dòng)代碼量較少。

再來(lái)看看切換主題的點(diǎn)擊觸發(fā)事件拼苍,顯然是在這幾個(gè)小方塊里笑诅,而且每個(gè)方塊代表一種主題调缨,具體的代碼如下:

// code 13
@Composable
fun ThemeColorCube(themeItem: ThemeItem, chosenThemeId: MutableState<String>, onClick: () -> Unit) {
    Surface(
        shape = RoundedCornerShape(10.dp),
        elevation = 5.dp,
        color = themeItem.mainColor,
        modifier = Modifier
            .size(85.dp)
            .padding(10.dp)
            .clickable {
                onClick()
            }
    ) {
        Row(
            horizontalArrangement = Arrangement.Center,
            verticalAlignment = Alignment.CenterVertically
        ) {
            if (themeItem.id.name == chosenThemeId.value) {
                Image(
                    modifier = Modifier.size(20.dp),
                    painter = painterResource(id = R.drawable.ic_checkbox_selected_gray),
                    contentScale = ContentScale.FillBounds,
                    contentDescription = "被選中標(biāo)記圖"
                )
            } else {
                Text(
                    text = themeItem.name,
                    textAlign = TextAlign.Center,
                    style = TextStyle(color = MaterialTheme.colors.primary)
                )
            }
        }
    }
}

data class ThemeItem(
    val id: ThemeKinds,    //主題 id
    val name: String,    //主題 name
    val mainColor: Color,    //主色
)

點(diǎn)擊事件的回調(diào)在主頁(yè)面 LazyRow列表的方法中:

// code 14
LazyRow() {
    items(themeList) { item: ThemeItem ->
        ThemeColorCube(themeItem = item, chosenThemeId) {
            //點(diǎn)擊色塊選擇其中的一種顏色
            MMKV.defaultMMKV().putString(MMKVConstant.ChosenThemeCode, item.id.name)
            chosenThemeId.value = item.id.name
        }
    }
}

可以看到,點(diǎn)擊之后吆你,需要將選中的主題 id存儲(chǔ)在本地弦叶,以便下次打開 App 可以獲取到選中的主題并設(shè)置相應(yīng)的主題色值組,更為重要的是更新 MutableState對(duì)象妇多,即通過(guò) CustomTheme傳進(jìn)來(lái)的 chosenThemeId的值伤哺。由于 MutableState的特性,所有引用它的地方者祖,都會(huì)觸發(fā)重組立莉,從而會(huì)使得 CustomTheme重組,重組會(huì)根據(jù)到更新后的 chosenThemeId的值來(lái)設(shè)置色值組七问,那么 MaterialTheme.colors的色值組就切換為新選中主題的色值組了蜓耻。

另外文案字體和大小,以及圖片的圓角大小械巡,都是類似的原理刹淌,不再贅述,文末見(jiàn)源碼獲取方法讥耗。

5. 彩蛋 —— 切換主題進(jìn)階版

這就完了么芦鳍?作為主題切換功能來(lái)講,已經(jīng)實(shí)現(xiàn)完了葛账,但,剛剛的切換過(guò)程是不是感覺(jué)比較生硬皮仁?有沒(méi)有更加絲滑的做法籍琳?答案當(dāng)然是有的。

圖 3

如圖3 所示贷祈,每次切換時(shí)趋急,背景色和字體大小、圓角大小都是漸變的势誊,切換過(guò)程絲滑呜达,過(guò)渡自然。

要想實(shí)現(xiàn)絲滑的效果粟耻,先得認(rèn)識(shí)一位新的朋友:animateXxxAsState查近。

5.1 animateXxxAsState

看前綴就知道是為動(dòng)畫而生的,Xxx 是因?yàn)樗性S多重載的參數(shù)方法挤忙,比如 Color霜威、Dp、Float 等册烈,我們這里色值的漸變就是用到的 animateColorAsState方法戈泼。同樣地,文案字體大小的動(dòng)畫以及圓角的動(dòng)畫,分別使用的是 animateFloatAsStateanimateDpAsState方法大猛。

這一類方法非常好用扭倾,官方文檔上是這么介紹 animateColorAsState方法的:

Fire-and-forget animation function for Color.

只需要觸發(fā)調(diào)用它即可,不用管其他的事情挽绩。這里只對(duì) animateColorAsState方法進(jìn)行舉例說(shuō)明膛壹,其他方法以此類推。先來(lái)看看它的聲明:

// code 15
@Composable
fun animateColorAsState(
    targetValue: Color,
    animationSpec: AnimationSpec<Color> = colorDefaultSpring,
    finishedListener: ((Color) -> Unit)? = null
): State<Color>

第一個(gè)參數(shù)就是設(shè)置色值漸變的終值琼牧,一旦設(shè)置的終值改變恢筝,漸變的動(dòng)畫就會(huì)自動(dòng)觸發(fā)。當(dāng)動(dòng)畫還未結(jié)束終值又有變化時(shí)巨坊,則動(dòng)畫會(huì)調(diào)整動(dòng)畫路徑到新的終值撬槽。

第二個(gè)參數(shù)可以設(shè)置動(dòng)畫的執(zhí)行規(guī)范,實(shí)現(xiàn)了 AnimationSpec接口的有 1)FloatSpringSpec趾撵;2)FloatTweenSpec侄柔;3)InfiniteRepeatableSpec;4)KeyframesSpec占调;5)RepeatableSpec暂题;6)SnapSpec;7)SpringSpec究珊;8)TweenSpec. 這些都是針對(duì)動(dòng)畫進(jìn)行的設(shè)置薪者,例如動(dòng)畫時(shí)間,以及動(dòng)畫速度的變化剿涮,類似于插值器言津。

第三個(gè)參數(shù)就很好理解了,即動(dòng)畫完成后的回調(diào)方法取试。

返回值是一個(gè) State狀態(tài)對(duì)象悬槽,所以它可以不斷地去更新值,直至動(dòng)畫完成瞬浓。

需要注意的是初婆,只要?jiǎng)赢嬎饔玫目山M合項(xiàng)沒(méi)有從 Compose 組件樹上被移除,那么這個(gè)動(dòng)畫方法不會(huì)被取消或被停止猿棉。

5.2 Color 漸變實(shí)現(xiàn)

從上一節(jié)可以得知磅叛,animateColorAsState方法返回的是個(gè) State狀態(tài),我們需要這個(gè)返回值去重組更新調(diào)用了該色值的 Composable 組件萨赁,所以宪躯,每種需要漸變的色值都需要聲明一個(gè) State狀態(tài)對(duì)象,我這里統(tǒng)一都放在 ViewModel中管理了:

// code 16
class MainViewModel : ViewModel() {
    var primaryColor: Color by mutableStateOf(Color(0xFF000000)) // 用于文案色值漸變
    var backgroundColor: Color by mutableStateOf(Color(0xFFFFFFFF)) // 用于背景色漸變
    ···
    val chosenThemeId = mutableStateOf(
        MMKV.defaultMMKV().getString(MMKVConstant.ChosenThemeCode, ThemeKinds.DEFAULT.name)
            ?: ThemeKinds.DEFAULT.name
    )
}

當(dāng)切換主題后位迂,主題 id 存儲(chǔ)的 MutableState觸發(fā)重組访雪,然后根據(jù)新的主題 id 獲取到新的色值組详瑞,這時(shí) animateColorAsState中的 targetValue就發(fā)生了變化,觸發(fā)漸變動(dòng)畫臣缀,從而不斷更新 ViewModel中的 primaryColorState 值坝橡,進(jìn)而重組所有引用了 primaryColor值的可組合項(xiàng),這時(shí)漸變效果出現(xiàn)精置。下面是 CustomTheme部分代碼:

// code 17
    val targetColors: AppColors
    if (isSystemInDarkTheme()) {
        //如果是深色模式计寇,則只能是深色模式的色值組,無(wú)法切換
        targetColors = DarkColors
    } else {
        targetColors = when (mainViewModel.chosenThemeId.value) {
            ThemeKinds.RED.name -> {
                RedThemeColors
            }
            ThemeKinds.YELLOW.name -> {
                YellowThemeColors
            }
            ThemeKinds.BLUE.name -> {
                BlueThemeColors
            }
            else -> {
                DefaultColors
            }
        }
    }
    //漸變實(shí)現(xiàn)
    mainViewModel.primaryColor = animateColorAsState(targetColors.primary, TweenSpec(500)).value
    mainViewModel.backgroundColor = animateColorAsState(targetColors.background, TweenSpec(500)).value

這里設(shè)置的漸變時(shí)長(zhǎng)為 500ms脂倦,并且為了方便管理番宁,將所有色值放在 AppColors類中進(jìn)行管理,各個(gè)不同的主題有著各自不同的 AppColors類對(duì)象赖阻,如下所示:

// code 18
@Stable
data class AppColors (
    val primary: Color,
    val background: Color
)

//紅色主題色值
private val RedThemeColors = AppColors(
    primary = Color(0xFFFF4040),
    background = Color(0x66FF4040)
)

//黃色主題色值
private val YellowThemeColors = AppColors(
    primary = Color(0xFFDAA520),
    background = Color(0x66FFD700)
)

至于圓角大小以及文字大小的漸變蝶押,都是一樣的實(shí)現(xiàn)方法,就是需要在 ViewModel中定義需要的 MutableState狀態(tài)對(duì)象火欧,然后使用相應(yīng)的 animateXxxAsState進(jìn)行漸變動(dòng)畫的實(shí)現(xiàn)即可棋电。

碎碎念:其實(shí) Compose 官方教程中的 Theme 主題內(nèi)容不多,且比較簡(jiǎn)單苇侵,所以就想借著主題切換的功能來(lái)鞏固和運(yùn)用這一知識(shí)點(diǎn)赶盔,希望大家能夠?qū)W有所得~ 如有問(wèn)題歡迎留言探討~

如需文中源碼,請(qǐng)?jiān)诠娞?hào)回復(fù):Compose換膚

贊人玫瑰榆浓,手留余香于未!歡迎點(diǎn)贊、轉(zhuǎn)發(fā)~ 轉(zhuǎn)發(fā)請(qǐng)注明出處~

更多內(nèi)容陡鹃,歡迎關(guān)注公眾號(hào): 修之竹

參考文獻(xiàn)

  1. Compose主題切換——讓你的APP也能一鍵換膚沉眶;Zhujiang https://juejin.cn/post/7070671629713408031
  2. Android Jetpack Compose 實(shí)現(xiàn)主題切換(換膚);九狼 https://juejin.cn/post/7057418707357663246
  3. Jetpack Compose - animateXxxAsState杉适;樂(lè)翁龍 https://blog.csdn.net/u010976213/article/details/114488661
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市柳击,隨后出現(xiàn)的幾起案子猿推,更是在濱河造成了極大的恐慌,老刑警劉巖捌肴,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹬叭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡状知,警方通過(guò)查閱死者的電腦和手機(jī)秽五,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)饥悴,“玉大人坦喘,你說(shuō)我怎么就攤上這事盲再。” “怎么了瓣铣?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵答朋,是天一觀的道長(zhǎng)棠笑。 經(jīng)常有香客問(wèn)我梦碗,道長(zhǎng),這世上最難降的妖魔是什么蓖救? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任洪规,我火速辦了婚禮,結(jié)果婚禮上循捺,老公的妹妹穿的比我還像新娘斩例。我一直安慰自己,他們只是感情好巨柒,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布樱拴。 她就那樣靜靜地躺著,像睡著了一般洋满。 火紅的嫁衣襯著肌膚如雪晶乔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天牺勾,我揣著相機(jī)與錄音正罢,去河邊找鬼。 笑死驻民,一個(gè)胖子當(dāng)著我的面吹牛翻具,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播回还,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼裆泳,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了柠硕?” 一聲冷哼從身側(cè)響起工禾,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蝗柔,沒(méi)想到半個(gè)月后闻葵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡癣丧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年槽畔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胁编。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡厢钧,死狀恐怖鳞尔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坏快,我是刑警寧澤铅檩,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站莽鸿,受9級(jí)特大地震影響昧旨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜祥得,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一兔沃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧级及,春花似錦乒疏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至县踢,卻和暖如春转绷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背硼啤。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工议经, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人谴返。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓煞肾,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親嗓袱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子籍救,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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