material-components-android - 在 Jetpack Compose 中使用自定义图像或文本样式实现可重用 Composable
问题描述
我目前正在开发两个 Android 项目,它们都使用 Jetpack Compose。这两个项目都使用了一个包含自定义组合的自行开发的库。
我现在的问题是我不知道如何在库中引用特定于项目的文本样式、图像或其他设计特征。不幸的是,Material Design 的实现是不够的。
举个例子:两个应用程序都应该有一个错误屏幕,以防出现问题。两个应用程序中的错误屏幕结构应该相同。错误屏幕应包含错误消息、错误图像和重试按钮。为了避免重复代码,我将可组合的错误屏幕移到了库中。那个时候没问题。
但错误屏幕也应该适应应用程序主题。目前,我在位于库模块内的可组合的错误屏幕中使用可绘制对象。按钮使用 MaterialTheme.typography.button 样式,消息使用 MaterialTheme.typography.body1 样式。
但这不是我想在这里实现的行为。我想为两个应用程序使用不同的错误图像,并且按钮和消息样式可能完全不同,具体取决于使用可组合的应用程序。
有什么聪明的方法可以实现这种行为吗?
编辑:我使用错误屏幕作为这个问题的一个易于理解的示例,但没有在库中实现它。但是我实现了一个可组合的配置文件,我面临着完全相同的问题。目前我正在使用该方法将文本样式作为参数传递,但这变得非常难看:
@Composable
fun Profile(
modifier: Modifier = Modifier,
user: User,
sections: List<Section>,
userIsLoggedIn: Boolean,
onLogin: () -> Unit,
onLogout: () -> Unit,
loginButtonTextStyle: TextStyle = MaterialTheme.typography.button.copy(
textAlign = TextAlign.Center
),
logoutButtonTextStyle: TextStyle = MaterialTheme.typography.button.copy(
color = MaterialTheme.colors.onSurface,
textAlign = TextAlign.Center
),
usernameTextStyle: TextStyle = MaterialTheme.typography.h5.copy(
color = MaterialTheme.colors.onSurface,
textAlign = TextAlign.Center
),
sectionTitleTextStyle: TextStyle = MaterialTheme.typography.subtitle1.copy(
color = MaterialTheme.colors.onSurface
),
itemTitleTextStyle: TextStyle = MaterialTheme.typography.body1,
) {
Column(
modifier = modifier
.verticalScroll(rememberScrollState())
.padding(top = 24.dp)
) {
if (userIsLoggedIn) {
UsernameHeader(
username = user.username,
textStyle = usernameTextStyle
)
} else {
LoginButton(
onLogin = onLogin,
textStyle = loginButtonTextStyle,
)
}
SectionsContainer(
sections = sections,
sectionTitleTextStyle = sectionTitleTextStyle,
itemTitleTextStyle = itemTitleTextStyle
)
if (userIsLoggedIn) {
LogoutButton(
onLogout = onLogout,
textStyle = logoutButtonTextStyle
)
}
}
}
@Composable
private fun UsernameHeader(
username: String,
textStyle: TextStyle
) {
Text(
text = "Hello $username",
style = textStyle,
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth()
)
Spacer(modifier = Modifier.height(32.dp))
}
@Composable
private fun LoginButton(
textStyle: TextStyle,
onLogin: () -> Unit
) {
Button(
onClick = onLogin,
modifier = Modifier
.padding(
start = 16.dp,
end = 16.dp,
)
) {
Text(
text = "LOGIN",
style = textStyle,
modifier = Modifier
.fillMaxWidth()
.padding(top = 7.dp, bottom = 9.dp)
)
}
Spacer(modifier = Modifier.height(24.dp))
}
@Composable
private fun LogoutButton(
textStyle: TextStyle,
onLogout: () -> Unit
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 32.dp)
) {
TextButton(
onClick = onLogout,
modifier = Modifier
.padding(
start = 24.dp,
end = 24.dp,
top = 7.dp,
bottom = 9.dp
)
) {
Text(
text = "LOGOUT",
style = textStyle,
)
}
}
}
@Composable
private fun SectionsContainer(
modifier: Modifier = Modifier,
sections: List<Section>,
sectionTitleTextStyle: TextStyle,
itemTitleTextStyle: TextStyle
) {
Column(modifier = modifier) {
for (section in sections) {
ProfileSection(
section = section,
titleTextStyle = sectionTitleTextStyle,
itemTitleTextStyle = itemTitleTextStyle,
)
}
}
}
@Composable
private fun ProfileSection(
modifier: Modifier = Modifier,
section: Section,
titleTextStyle: TextStyle,
itemTitleTextStyle: TextStyle,
) {
Column(
modifier.padding(bottom = 16.dp)
) {
Text(
text = section.title.toUpperCase(Locale.ROOT),
style = titleTextStyle,
modifier = Modifier
.padding(
start = 16.dp,
end = 16.dp,
top = 9.dp,
bottom = 10.dp
)
.fillMaxWidth()
)
for (item in section.items) {
ProfileItem(
item = item,
itemTitleTextStyle = itemTitleTextStyle
)
}
}
}
@Composable
private fun ProfileItem(
item: Item,
itemTitleTextStyle: TextStyle
) {
when (item) {
is TextItem -> ProfileTextItem(
textItem = item,
titleTextStyle = itemTitleTextStyle
)
is ImageItem -> ProfileImageItem(imageItem = item)
}
}
@Composable
private fun ProfileTextItem(
textItem: TextItem,
titleTextStyle: TextStyle
) {
Text(
text = textItem.title,
style = titleTextStyle,
modifier = Modifier
.padding(
start = 16.dp,
end = 16.dp,
top = 15.dp,
bottom = 12.dp
)
.fillMaxWidth()
)
}
@Composable
private fun ProfileImageItem(imageItem: ImageItem) {
Image(
painter = painterResource(id = imageItem.drawableResId),
contentDescription = imageItem.contentDescription,
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.FillWidth
)
}
我使用的模型非常简单:
部分:
data class Section(
val title: String,
val items: List<Item>
)
物品:
interface Item
data class TextItem(
val title: String
) : Item
data class ImageItem(
val drawableResId: Int,
val contentDescription: String?
) : Item
用户:
data class User(
val username: String
)
然后,我在两个应用程序的活动中使用此配置文件可组合,如下所示:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val systemUiController = remember { SystemUiController(window) }
CompositionLocalProvider(SystemUiControllerAmbient provides systemUiController) {
SampleApp()
}
}
}
@Composable
fun SampleApp() {
SampleTheme {
val systemUiController = SystemUiControllerAmbient.current
systemUiController.setSystemBarsColor(color = MaterialTheme.colors.surface)
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = { TopAppBarContainer() },
bottomBar = { BottomNavigationContainer() }
) { innerPadding ->
ProfileContainer(Modifier.padding(innerPadding))
}
}
}
@Composable
private fun TopAppBarContainer() {
TopAppBar(
backgroundColor = MaterialTheme.colors.surface,
contentColor = MaterialTheme.colors.onSurface
) {
Text(
text = "Profile",
style = MaterialTheme.typography.h2,
modifier = Modifier.padding(16.dp)
)
}
}
@Composable
private fun ProfileContainer(modifier: Modifier = Modifier) {
Surface(modifier = modifier) {
var userIsLoggedIn by remember { mutableStateOf(false) }
Profile(
Modifier.fillMaxWidth(),
user = User("Username"),
sections = fakeSections,
userIsLoggedIn = userIsLoggedIn,
onLogin = { userIsLoggedIn = true },
onLogout = { userIsLoggedIn = false }
)
}
}
@Composable
private fun BottomNavigationContainer() {
BottomNavigation(
backgroundColor = MaterialTheme.colors.surface,
contentColor = MaterialTheme.colors.onSurface,
) {
BottomNavigationItem(
selected = false,
onClick = { },
icon = {
Icon(
painter = painterResource(R.drawable.logo),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
},
label = {
Text(
text = "Home",
style = MaterialTheme.typography.caption,
color = grey60
)
}
)
BottomNavigationItem(
selected = false,
onClick = { },
icon = {
Icon(
painter = painterResource(R.drawable.ic_assortment),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
},
label = {
Text(
text = "Assortment",
style = MaterialTheme.typography.caption,
color = grey60
)
}
)
BottomNavigationItem(
selected = false,
onClick = { },
icon = {
Icon(
painter = painterResource(R.drawable.ic_heart),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
},
label = {
Text(
text = "Wishlist",
style = MaterialTheme.typography.caption,
color = grey60
)
}
)
BottomNavigationItem(
selected = false,
onClick = { },
icon = {
Icon(
painter = painterResource(R.drawable.ic_cart),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
},
label = {
Text(
text = "Cart",
style = MaterialTheme.typography.caption,
color = grey60
)
}
)
BottomNavigationItem(
selected = true,
onClick = { },
icon = {
Icon(
painter = painterResource(R.drawable.ic_user),
contentDescription = null,
tint = bonprixRed,
modifier = Modifier.size(24.dp)
)
},
label = {
Text(
text = "Profile",
style = MaterialTheme.typography.caption,
color = red
)
}
)
}
}
@Preview
@Composable
fun DefaultPreview() {
SampleApp()
}
}
解决方案
推荐阅读
- c# - 如何在asp.net核心控制器中画图
- python - Create new variable based on groupby & value comparison
- php - 当父级是公共的时,如何将子构造函数设为私有?
- javascript - 有没有办法过滤我们的树显示的内容?
- ruby-on-rails - Active Storage Permanent Image URL
- java - How to link a JFrame class with another JPanel using JButton
- ms-access - Access 2010 报告的最后一个记录问题
- java - Android Studio - Click item in listview, get a value from that position
- mfc - 是否有自动化工具或技术可以让我读取 MFC 网格控件的内容
- gnupg - private-keys-v1.d 目录下的哪个私钥文件属于哪个密钥?