欢迎,Android旅行者!您准备好通过Compose宇宙展开另一场激动人心的旅程了吗?在本节中,我们将采用实践方法来打造一个创新的BankCardUi()
组合,这是我们从头开始精心开发的作品。
🎯 今天的目标:
-
掌握通过编程方式实现较低饱和度颜色的艺术。
-
通过巧妙使用
Modifier.aspectRatio()
来优化UI美学。 -
通过
Canvas()
绘制动态、引人注目的圆形,释放创造力。 -
通过我们量身定制的
SpaceWrapper()
组合来巧妙地排列元素。
💻 环境:
-
IDE: Android Studio Hedgehog | 版本2023.1.1
-
Compose工具包:
androidx.compose:compose-bom | 版本2023.08.00
-
测试环境: Pixel 5模拟器,API 32
让我们一起踏上这段Android创新之旅,开启新的创造可能性。
像往常一样,在Android Studio中选择Empty Activity
选项来开始。然后创建一个名为BankCardUi.kt
的文件,让我们开始第一步:
// your package com...
// 所有需要的导入
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
// 我们的明星:BankCardUi
@Composable
fun BankCardUi() {
// 银行卡纵横比
val bankCardAspectRatio = 1.586f // (例如,宽度:高度 = 85.60mm:53.98mm)
Card(
modifier = Modifier
.fillMaxWidth()
// Compose中的纵横比
.aspectRatio(bankCardAspectRatio),
elevation = CardDefaults.cardElevation(defaultElevation = 16.dp)
) {
Box {
BankCardBackground(baseColor = Color(0xFF1252c8))
}
}
}
// 背景色的一抹色彩
@Composable
fun BankCardBackground(baseColor: Color) {
Canvas(
modifier = Modifier
.fillMaxSize()
.background(baseColor)
) {
}
}
// 使用@Preview来一睹为快
@Composable
@Preview
fun BankCardUiPreview() {
Box (Modifier.padding(16.dp)) {
BankCardUi()
}
}
-
UI设计中的纵横比是指元素的
宽度
和高度
之间的比例关系。 -
对于银行卡来说,标准尺寸至关重要。银行卡、信用卡和身份证的普遍接受的尺寸由ISO/IEC 7810 ID-1标准定义,宽度为85.60mm,高度为53.98mm。
-
纵横比的公式是
宽度 / 高度
。因此,对于标准银行卡,它是85.60mm / 53.98mm ≈ 1.586
。因此,val bankCardAspectRatio = 1.586f
。 -
这个纵横比确保我们的UI卡片无论屏幕尺寸如何,都能模仿标准的银行卡。
-
我们将
.aspectRatio(bankCardAspectRatio)
修饰符应用于我们的Card
组合。这告诉Compose在卡片的宽度
和高度
之间保持这个比例。 -
.fillMaxWidth()
修饰符确保卡片拉伸到其父容器的整个宽度,高度会自动调整以保持纵横比。
点击Preview
,见证银行卡轮廓的诞生!
现在是进行一些颜色科学的时候了。本步骤通过颜色处理,特别是调整饱和度水平,为我们的BankCardUi()
增添深度和活力。创建ColorUtils.kt
文件,内容如下:
// your package com...
import androidx.compose.ui.graphics.Color
fun Color.toHsl(): FloatArray {
val redComponent = red
val greenComponent = green
val blueComponent = blue
val maxComponent = maxOf(redComponent, greenComponent, blueComponent)
val minComponent = minOf(redComponent, greenComponent, blueComponent)
val delta = maxComponent - minComponent
val lightness = (maxComponent + minComponent) / 2
val hue: Float
val saturation: Float
if (maxComponent == minComponent) {
// 灰度颜色,没有饱和度和色调未定义
hue = 0f
saturation = 0f
} else {
// 计算饱和度
saturation = if (lightness > 0.5) delta / (2 - maxComponent - minComponent) else delta / (maxComponent + minComponent)
// 计算色调
hue = when (maxComponent) {
redComponent -> 60 * ((greenComponent - blueComponent) / delta % 6)
greenComponent -> 60 * ((blueComponent - redComponent) / delta + 2)
else -> 60 * ((redComponent - greenComponent) / delta + 4)
}
}
// 返回HSL值,确保色调在0-360范围内
return floatArrayOf(hue.coerceIn(0f, 360f), saturation, lightness)
}
fun hslToColor(hue: Float, saturation: Float, lightness: Float): Color {
val chroma = (1 - kotlin.math.abs(2 * lightness - 1)) * saturation
val secondaryColorComponent = chroma * (1 - kotlin.math.abs((hue / 60) % 2 - 1))
val matchValue = lightness - chroma / 2
var red = matchValue
var green = matchValue
var blue = matchValue
when ((hue.toInt() / 60) % 6) {
0 -> { red += chroma; green += secondaryColorComponent }
1 -> { red += secondaryColorComponent; green += chroma }
2 -> { green += chroma; blue += secondaryColorComponent }
3 -> { green += secondaryColorComponent; blue += chroma }
4 -> { red += secondaryColorComponent; blue += chroma }
5 -> { red += chroma; blue += secondaryColorComponent }
}
// 从RGB分量创建颜色
return Color(red = red, green = green, blue = blue)
}
fun Color.setSaturation(newSaturation: Float): Color {
val hslValues = this.toHsl()
// 调整饱和度,同时保持色调和亮度不变
return hslToColor(hslValues[0], newSaturation.coerceIn(0f, 1f), hslValues[2])
}
-
饱和度是指颜色的强度或纯度。在饱和度很高的颜色中,色调看起来鲜艳丰富。相反,饱和度较低的颜色看起来更加淡化或灰色。
-
在UI设计中,通过调整饱和度可以创建视觉上的兴趣和层次感,帮助某些元素突出或融入背景。
-
HSL代表色调(Hue)、饱和度(Saturation)和亮度(Lightness)。它是一种柱面色彩模型,以一种对人类来说通常比RGB(红、绿、蓝)更直观的方式表示和操作颜色。
-
在HSL中,色调确定颜色的类型,饱和度定义该颜色的强度,亮度控制亮度。
-
toHsl()
函数计算颜色的色调、饱和度和亮度。 -
它首先找出颜色的红色、绿色和蓝色分量中的最大值和最小值,这有助于确定色调和饱和度。
-
根据哪个RGB分量(红色、绿色或蓝色)占主导地位,计算出
色调
。 -
饱和度
然后根据亮度和最大和最小分量之间的差异(delta)来计算。
- 此方法用于将HSL(色调、饱和度、亮度)值转换回
Color
对象,通常以RGB(红、绿、蓝)色彩模型表示。
-
setSaturation()
的目的是在保持色调和亮度不变的情况下改变颜色的饱和度。 -
此方法首先使用
toHsl()
将颜色从RGB空间转换为HSL,调整饱和度值,然后使用hslToColor()
将其转换回RGB颜色。
现在,更新BankCardBackground()
,通过改变饱和度水平创建两个视觉上不同的圆圈,为卡片的背景增添深度和趣味。让我们使用Color.setSaturation()
函数分别设置为0.75f
和0.5f
。
......
@Composable
fun BankCardBackground(baseColor: Color) {
val colorSaturation75 = baseColor.setSaturation(0.75f)
val colorSaturation50 = baseColor.setSaturation(0.5f)
// 使用 Canvas 绘制形状
Canvas(
modifier = Modifier
.fillMaxSize()
.background(baseColor)
) {
// 绘制圆形
drawCircle(
color = colorSaturation75,
center = Offset(x = size.width * 0.2f, y = size.height * 0.6f),
radius = size.minDimension * 0.85f
)
drawCircle(
color = colorSaturation50,
center = Offset(x = size.width * 0.1f, y = size.height * 0.3f),
radius = size.minDimension * 0.75f
)
}
}
...
-
Jetpack Compose 中的
Canvas
组合提供了一个灵活的空间来绘制自定义形状和图形。它特别适用于创建复杂的设计或无法通过标准组合实现的视觉效果。 -
在
BankCardBackground
函数中,Canvas
绘制了具有不同饱和度的圆形。这是我们实现的颜色处理技术的实际应用。
-
使用
drawCircle
函数在画布上绘制圆形。 -
您创建了两个具有不同饱和度(
colorSaturation75
和colorSaturation50
)的圆形。这些饱和度是通过使用setSaturation
函数对baseColor
进行处理获得的。 -
drawCircle
的center
参数确定了每个圆形在画布上的位置。它是根据画布大小的比例计算的(size.width
和size.height
),可以根据画布大小进行响应式定位。 -
每个圆形的
radius
是根据画布的较小尺寸(size.minDimension
)的比例确定的,确保圆形在不同屏幕尺寸或画布尺寸下适当缩放。
预览一下,看看稍微酷一点的卡片 UI。随意调整上述提到的值,探索不同的效果。
本步骤将在 BankCardUi()
中添加必要的元素 - 点和数字,以使其具有数字银行卡的真实感。我们将创建 BankCardNumber()
和 BankCardDotGroup()
组合来实现点和数字。
首先,从这里下载 Lato
谷歌字体。然后在 res
文件夹下,右键单击并创建一个名为 font
的新目录。将所有 .ttf
文件放入新创建的 folder
中,并将它们重命名为小写字母。
添加 val LatoFont
、BankCardNumber()
、BankCardDotGroup()
组合,并更新 BankCardUi()
的内容。像下面这样更新 BankCardUi.kt
:
// 定义 LatoFont
val LatoFont = FontFamily(
Font(R.font.lato_black, FontWeight.Black),
Font(R.font.lato_black_italic, FontWeight.Black, FontStyle.Italic),
Font(R.font.lato_bold, FontWeight.Bold),
Font(R.font.lato_bold_italic, FontWeight.Bold, FontStyle.Italic),
Font(R.font.lato_italic, FontWeight.Normal, FontStyle.Italic),
Font(R.font.lato_light, FontWeight.Light),
Font(R.font.lato_light_italic, FontWeight.Light, FontStyle.Italic),
Font(R.font.lato_regular, FontWeight.Normal),
Font(R.font.lato_thin, FontWeight.Thin),
Font(R.font.lato_thin_italic, FontWeight.Thin, FontStyle.Italic)
)
@Composable
fun BankCardUi() {
...
) {
Box {
BankCardBackground(baseColor = Color(0xFF1252C8))
BankCardNumber("1234567890123456")
}
}
}
...
@Composable
fun BankCardNumber(cardNumber: String) {
Row(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 32.dp),
horizontalArrangement = Arrangement.SpaceBetween, // 均匀分布子项
verticalAlignment = Alignment.CenterVertically // 垂直居中对齐子项
) {
// 绘制前三组点
repeat(3) {
BankCardDotGroup()
}
// 显示最后四位数字
Text(
text = cardNumber.takeLast(4),
style = TextStyle(
fontFamily = LatoFont,
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
letterSpacing = 1.sp,
color = Color.White
)
)
}
}
@Composable
fun BankCardDotGroup() {
Canvas(
modifier = Modifier.width(48.dp),
onDraw = { // 您可以根据需要调整宽度
val dotRadius = 4.dp.toPx()
val spaceBetweenDots = 8.dp.toPx()
for (i in 0 until 4) { // 绘制四个点
drawCircle(
color = Color.White,
radius = dotRadius,
center = Offset(
x = i * (dotRadius * 2 + spaceBetweenDots) + dotRadius,
y = center.y
)
)
}
})
}
...
FontFamily
中的每个Font
表示Lato
字体的不同样式和粗细。这种多样性使我们能够独特地为不同的文本元素设置样式。
- 使用
Canvas()
绘制四个点,表示卡号的隐藏部分。尝试调整dotRadius
和spaceBetweenDots
来改变这些点的外观。
-
用于显示卡号。通常,银行卡将卡号分组显示,最后一组可见,其他组用点表示以保证安全性。
-
使用
Row()
组合进行布局,提供均匀的间距和垂直对齐。在其中,重复三次BankCardDotGroup()
来表示隐藏的数字,然后将最后四位数字显示为文本。
随意尝试不同的字体粗细或样式,或调整数字和点的位置以适应您的设计偏好。
开始看起来更好了。
在这一部分中,我们将为 BankCardUi()
添加最终的 UI 细节,例如卡类型和持卡人信息。
添加 SpaceWrapper()
以方便间距设置,并添加 BankCardLabelAndText()
以将标签-文本对添加到银行卡。BankCardUi()
现在将包含所有所需的元素:
@Composable
fun BankCardUi() {
...
Box {
BankCardBackground(baseColor = Color(0xFF1252C8))
BankCardNumber("1234567890123456")
// 定位到左上角
SpaceWrapper(
modifier = Modifier.align(Alignment.TopStart),
space = 32.dp,
top = true,
left = true
) {
BankCardLabelAndText(label = "card holder", text = "John Doe")
}
// 定位到左下角
SpaceWrapper(
modifier = Modifier.align(Alignment.BottomStart),
space = 32.dp,
bottom = true,
left = true
) {
Row {
BankCardLabelAndText(label = "expires", text = "01/29")
Spacer(modifier = Modifier.width(16.dp))
BankCardLabelAndText(label = "cvv", text = "901")
}
}
// 定位到右下角
SpaceWrapper(
modifier = Modifier.align(Alignment.BottomEnd),
space = 32.dp,
bottom = true,
right = true
) {
// 可以使用图像代替
Text(
text = "WISA", style = TextStyle(
fontFamily = LatoFont,
fontWeight = FontWeight.W500,
fontStyle = FontStyle.Italic,
fontSize = 22.sp,
letterSpacing = 1.sp,
color = Color.White
)
)
}
}
...
}
...
@Composable
fun BankCardLabelAndText(label: String, text: String) {
Column(
modifier = Modifier
.wrapContentSize(),
verticalArrangement = Arrangement.SpaceBetween
) {
Text(
text = label.uppercase(),
style = TextStyle(
fontFamily = LatoFont,
fontWeight = FontWeight.W300,
fontSize = 12.sp,
letterSpacing = 1.sp,
color = Color.White
)
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = text,
style = TextStyle(
fontFamily = LatoFont,
fontWeight = FontWeight.W400,
fontSize = 16.sp,
letterSpacing = 1.sp,
color = Color.White
)
)
}
}
@Composable
fun SpaceWrapper(
modifier: Modifier = Modifier,
space: Dp,
top: Boolean = false,
right: Boolean = false,
bottom: Boolean = false,
left: Boolean = false,
content: @Composable BoxScope.() -> Unit
) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.then(if (top) Modifier.padding(top = space) else Modifier)
.then(if (right) Modifier.padding(end = space) else Modifier)
.then(if (bottom) Modifier.padding(bottom = space) else Modifier)
.then(if (left) Modifier.padding(start = space) else Modifier)
) {
content()
}
}
...
```...
-
在垂直排列中显示标签及其对应的文本。
-
在
BankCardUi()
中用于展示卡片上的各种信息,如持卡人姓名、过期日期和 CVV 码。
- 旨在为其子元素提供方便的间距。它允许您在特定的边(上、右、下、左)添加间距,并相应地定位其子元素。
通过将所有组件组合在一起,您现在应该在模拟器或实际设备上看到一个很酷的数字银行卡。
第 5 部分:可重用的组合函数
BankCardUi()
的工作正常。然而,它依赖硬编码的值,并且需要可自定义;因此,它是一段不可工作的代码。为了解决这个问题,添加一些参数,使 BankCardUi()
变得灵活。
@Composable
fun BankCardUi(
// 设计灵活的组合函数
modifier: Modifier = Modifier, // 将 Modifier 作为参数
baseColor: Color = Color(0xFF1252C8),
cardNumber: String = "",
cardHolder: String = "",
expires: String = "",
cvv: String = "",
brand: String = ""
) {
// 银行卡纵横比
val bankCardAspectRatio = 1.586f // (例如,宽度:高度 = 85.60mm:53.98mm)
Card(
modifier = modifier
.fillMaxWidth()
// 在 Compose 中设置纵横比
.aspectRatio(bankCardAspectRatio),
elevation = CardDefaults.cardElevation(defaultElevation = 16.dp)
) {
Box {
BankCardBackground(baseColor = baseColor)
BankCardNumber(cardNumber = cardNumber)
// 定位到左上角
SpaceWrapper(
modifier = Modifier.align(Alignment.TopStart),
space = 32.dp,
top = true,
left = true
) {
BankCardLabelAndText(label = "持卡人", text = cardHolder)
}
// 定位到左下角
SpaceWrapper(
modifier = Modifier.align(Alignment.BottomStart),
space = 32.dp,
bottom = true,
left = true
) {
Row {
BankCardLabelAndText(label = "有效期", text = expires)
Spacer(modifier = Modifier.width(16.dp))
BankCardLabelAndText(label = "CVV", text = cvv)
}
}
// 定位到右下角
SpaceWrapper(
modifier = Modifier.align(Alignment.BottomEnd),
space = 32.dp,
bottom = true,
right = true
) {
// 可以使用图像代替
Text(
text = brand, style = TextStyle(
fontFamily = LatoFont,
fontWeight = FontWeight.W500,
fontStyle = FontStyle.Italic,
fontSize = 22.sp,
letterSpacing = 1.sp,
color = Color.White
)
)
}
}
}
}
...
@Composable
@Preview
fun BankCardUiPreview() {
Box(Modifier.fillMaxSize().padding(16.dp)) {
BankCardUi(
modifier = Modifier.align(Alignment.Center),
baseColor = Color(0xFFFF9800),
cardNumber = "1234567890123456",
cardHolder = "John Doe",
expires = "01/29",
cvv = "901",
brand = "WISA"
)
}
}
现在 BankCardUi()
组合函数已经准备好了,更新 MainActivity.kt
。
// your package com...
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Box(
Modifier
.fillMaxSize()
.padding(16.dp)) {
BankCardUi(
modifier = Modifier.align(Alignment.Center),
baseColor = Color(0xFFFF9800),
cardNumber = "1234567890123456",
cardHolder = "John Doe",
expires = "01/29",
cvv = "901",
brand = "WISA"
)
}
}
}
}
}
设计灵活的组合函数
-
创建隔离且高效的组合函数是一个好主意,它具有默认参数值和抽象的 lambda 表达式或数据源。
-
避免硬编码尺寸,使用外部文件来存储样式、尺寸和字符串。
将 Modifier 作为参数
- 允许传入 Modifier 参数可以增加灵活性,特别是对于在
Box()
等容器中的对齐方式。
启动应用程序,亲眼目睹结果。使用 Compose 构建 UI 的速度和简便性令人惊叹,尤其是与传统的 XML 方法相比。
总结
🔗 访问完整代码: 您刚刚掌握了 BankCardUi() 组合函数的艺术 - 恭喜!为了将这个宝贵的资源随时可用,请访问此存储库获取完整的代码。