Android compose 自定义主题

修改上一个demo,使用自定义主题。直接修改Theme.kt, 修改里面的颜色、字体形状的配置。并且创建自定义包装函数,代码如下:

Kotlin 复制代码
package com.example.testcompose1.ui.theme

import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

// 定义自定义颜色方案 .创建两个颜色方案:浅色主题和深色主题。

// 浅色主题的颜色方案
val LightColorScheme = lightColorScheme(
    primary = Color(0xFF6200EE),          // App主色调。用于关键交互元素(选中状态、按钮、下划线)
    secondary = Color(0xFF03DAC6),        // 次色调。次要强调色,用于次要按钮、图标等
    tertiary = Color(0xFF3700B3),         // 第三强调色(补充色),用于特殊强调的元素
    background = Color(0xFFFFFBFE),       // App 页面的整体背景色
    surface = Color(0xFFFFFBFE),          // 控件 / 卡片的背景色(比如 TextField、Card、按钮的背景)
    onPrimary = Color.White,                     // 显示在 primary 颜色上的文字 / 图标色(保证对比度)
    onSecondary = Color.Black,                   // 显示在 secondary 颜色上的文字 / 图标色
    onBackground = Color(0xFF1C1B1F),     // 显示在 background 上的文字 / 图标色
    onSurface = Color(0xFF1C1B1F),        // 显示在 surface 上的文字 / 图标色
    // 可以继续自定义其他颜色
)

// 深色主题的颜色方案
val DarkColorScheme = darkColorScheme(
    primary = Color(0xFFBB86FC),
    secondary = Color(0xFF03DAC6),
    tertiary = Color(0xFF3700B3),
    background = Color(0xFF1C1B1F),
    surface = Color(0xFF2C2B2F),
    onPrimary = Color.Black,
    onSecondary = Color.Black,
    onBackground = Color.White,
    onSurface = Color.White,
)

// 字体样式
val AppTypography = Typography(
    titleLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Bold,
        fontSize = 22.sp,
        lineHeight = 28.sp,
        letterSpacing = 0.sp
    ),
    bodyLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.5.sp
    ),
    // 可以继续自定义其他样式
)

// 形状
val AppShapes = Shapes(
    extraSmall = RoundedCornerShape(4.dp),
    small = RoundedCornerShape(8.dp),
    medium = RoundedCornerShape(12.dp),
    large = RoundedCornerShape(16.dp),
    extraLarge = RoundedCornerShape(24.dp)
)

// 创建自定义主题包装函数
// 这个函数会判断当前是否为深色模式(通过系统设置或用户手动选择),然后应用对应的颜色方案、字体和形状。
// 一会我加入一个状态来控制用户的手动深色模式选择。即增加一个按钮切换。
@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(), // 默认跟随系统
    dynamicColor: Boolean = false,               // 是否启用动态颜色(Android 12+)
    content: @Composable () -> Unit
) {
    // 根据暗色标志选择颜色方案
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            // 动态颜色(基于壁纸)
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }
        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = AppTypography,
        shapes = AppShapes,
        content = content
    )
}

// 自动生成的。
@Composable
fun TestCompose1Theme(
    darkTheme: Boolean = isSystemInDarkTheme(), // 是否深色主题
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = true, //动态取色
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }

        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }

    // 统一应用所有 Material Design 组件的样式
    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography, // 排版配置.管理App中文字样式
        content = content
    )
}

再修改MainActivity,使用这个主题。

Kotlin 复制代码
package com.example.testcompose1

import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.testcompose1.ui.theme.MyAppTheme
import com.example.testcompose1.ui.theme.TestCompose1Theme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MyAppTheme(
                darkTheme = ThemeManager.isDarkTheme // 覆盖系统设置
                ,dynamicColor = false // 暂时禁用动态颜色。
            ) {
                todoJobList()
            }
        }
    }
}

@Preview
@Composable
fun todoJobList(
    viewModel: TodoViewModel = viewModel()  // 获取ViewModel实例。 在同一个activity作用域中是单例。
) {
    val context = LocalContext.current

    // 使用 remember 和 mutableStateOf 保存输入框的文本
    var text by remember { mutableStateOf("") }
    // 使用 mutableStateListOf 保存待办项列表
//    val todoItems = remember { mutableStateListOf<String>() }
    // 将 StateFlow 转换为 Compose 可观察的 State
    val todoItems by viewModel.todoItems.collectAsState()

    Column(modifier = Modifier.padding(16.dp)) {
        ThemeSwitch()  // 添加开关
        Spacer(modifier = Modifier.height(8.dp))
        // 文本输入框
        TextField(
            value = text,
            onValueChange = { text = it }, // 反向绑定,视图变化--> 数据变化
            label = { Text("输入待办事项") },
            colors = TextFieldDefaults.colors(
                focusedContainerColor = MaterialTheme.colorScheme.surface, // 获得焦点时的背景色
                unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant, // 失去焦点时,输入框背景色
                focusedIndicatorColor = MaterialTheme.colorScheme.primary, // 输入框底部下划线的颜色。
                unfocusedIndicatorColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
            ),
            modifier = Modifier.fillMaxWidth()
        )
        // 添加按钮
        Button(
            onClick = {
                viewModel.addItem(text)
                text = ""
            },
            shape = MaterialTheme.shapes.small,  // 使用主题形状
            colors = ButtonDefaults.buttonColors(
                containerColor = MaterialTheme.colorScheme.primary, // 容器背景色,按钮底色
                contentColor = MaterialTheme.colorScheme.onPrimary // 内容颜色,按钮上文字 / 图标的颜色
            ),
            modifier = Modifier.padding(top = 8.dp)
        ) {
            Text("添加")
        }

        // 显示待办列表
        Spacer(modifier = Modifier.height(16.dp))
        Text("待办列表", style = MaterialTheme.typography.titleMedium)
        LazyColumn {
            items(items = todoItems) { item ->
                TodoItem(item = item
                    , onDelete = { viewModel.removeItem(item) })
            }
        }
    }
}

@Composable
fun TodoItem(item: String, onDelete: () -> Unit  // 添加删除回调,删除逻辑放在上层。即把回调传给里面的按钮。
 ) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 4.dp),
        shape = MaterialTheme.shapes.medium,  // 使用主题形状
        colors = CardDefaults.cardColors(
            containerColor = MaterialTheme.colorScheme.surface
        )
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(vertical = 8.dp),
            horizontalArrangement = Arrangement.SpaceBetween // 横向布局子元素两端对齐,剩余空白空间平均分配到子元素之间
        ) {
            Text(text = item
                ,style = MaterialTheme.typography.bodyLarge,
                color = MaterialTheme.colorScheme.onSurface)
            IconButton(onClick = onDelete) {
                Icon(Icons.Default.Delete, contentDescription = "删除"
                , tint = MaterialTheme.colorScheme.error)
            }
        }
    }
}

// 主题切换开关
@Composable
fun ThemeSwitch() {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically
    ) {
        Text(
            text = "深色模式",
            style = MaterialTheme.typography.bodyLarge,
            color = MaterialTheme.colorScheme.onSurface
        )
        Switch(
            checked = ThemeManager.isDarkTheme,
            onCheckedChange = { ThemeManager.toggleTheme() }
        )
    }
}

// 为了允许手动切换深色/浅色模式,在应用中保存用户的选择,并在主题中读取. 后面改用DataStore保存
object ThemeManager {
    var isDarkTheme by mutableStateOf(false)
        private set

    fun toggleTheme() { // 切换是否为深色主题
        isDarkTheme = !isDarkTheme
    }
}

ok. 主题替换了,UI也使用了主题中定义的颜色、字体、形状等。而且加了一个切换是否为深色主题的按钮。测试下:

点击顶部的切换主题的按钮:

ok. 但是上面的是否为深色主题的文案怎么看不到了。

开关这一行的Row容器加个背景试试:

ok. 现在能看到顶部的深色模式文字了。 再显示成圆角的形状:

注意这个调用顺序,先定形状再铺背景。效果:

ok.

相关推荐
GitLqr1 小时前
Flutter 3.44 插件内置 Kotlin (KGP) 双向兼容适配指南
android·flutter·dart
随遇丿而安6 小时前
第11周:Activity 跳转与传值 + 跳转优化
android
私人珍藏库7 小时前
[Android] BBLL 开源第三方B哩电视TV端
android·app·生活·工具·多功能
杉氧9 小时前
跨平台资源管理:一套代码如何搞定 Android、iOS 和 Web 的图片与多语言?
android·架构·android jetpack
安卓修改大师10 小时前
安卓修改大师实战:从反编译到定制的完整APK修改指南
android
柚鸥ASO优化11 小时前
安卓APP推广的“降本增效”密码:守好商店内,打好商店外
android·aso优化
我是一颗柠檬12 小时前
【Java项目技术亮点】EXPLAIN深度分析与慢查询治理
android·java·开发语言
Android-Flutter12 小时前
android compose shadow 阴影 使用
android·kotlin·compose
帅次12 小时前
Android 高级工程师面试:Java 多线程与并发 近1年高频追问 22 题
android·java·面试
2501_9437823513 小时前
【共创季稿事节】摩斯电码转换器:编码表与双向转换的实现
android·华为·鸿蒙·鸿蒙系统