首页
开发技巧正文内容

Android Compose实战:开发动态银行卡UI

2024年03月19日
阅读时长 2 分钟
阅读量 11
Android Compose实战:开发动态银行卡UI

介绍

欢迎,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创新之旅,开启新的创造可能性。

步骤1:纵横比

像往常一样,在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卡片无论屏幕尺寸如何,都能模仿标准的银行卡。

Compose中的纵横比

  • 我们将.aspectRatio(bankCardAspectRatio)修饰符应用于我们的Card组合。这告诉Compose在卡片的宽度高度之间保持这个比例。

  • .fillMaxWidth()修饰符确保卡片拉伸到其父容器的整个宽度,高度会自动调整以保持纵横比。

点击Preview,见证银行卡轮廓的诞生!

BankCardUi Implementation Part 1

步骤2:颜色处理 - 掌握饱和度

现在是进行一些颜色科学的时候了。本步骤通过颜色处理,特别是调整饱和度水平,为我们的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颜色模型

  • HSL代表色调(Hue)、饱和度(Saturation)和亮度(Lightness)。它是一种柱面色彩模型,以一种对人类来说通常比RGB(红、绿、蓝)更直观的方式表示和操作颜色。

  • 在HSL中,色调确定颜色的类型,饱和度定义该颜色的强度,亮度控制亮度。

转换为HSL - toHsl()

  • toHsl()函数计算颜色的色调、饱和度和亮度。

  • 它首先找出颜色的红色、绿色和蓝色分量中的最大值和最小值,这有助于确定色调和饱和度。

  • 根据哪个RGB分量(红色、绿色或蓝色)占主导地位,计算出色调

  • 饱和度然后根据亮度和最大和最小分量之间的差异(delta)来计算。

转换为颜色 - hslToColor()

  • 此方法用于将HSL(色调、饱和度、亮度)值转换回Color对象,通常以RGB(红、绿、蓝)色彩模型表示。

调整饱和度 - setSaturation()

  • setSaturation()的目的是在保持色调和亮度不变的情况下改变颜色的饱和度。

  • 此方法首先使用toHsl()将颜色从RGB空间转换为HSL,调整饱和度值,然后使用hslToColor()将其转换回RGB颜色。

现在,更新BankCardBackground(),通过改变饱和度水平创建两个视觉上不同的圆圈,为卡片的背景增添深度和趣味。让我们使用Color.setSaturation()函数分别设置为0.75f0.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
        )
    }
}

...

使用 Canvas 绘制形状

  • Jetpack Compose 中的 Canvas 组合提供了一个灵活的空间来绘制自定义形状和图形。它特别适用于创建复杂的设计或无法通过标准组合实现的视觉效果。

  • BankCardBackground 函数中,Canvas 绘制了具有不同饱和度的圆形。这是我们实现的颜色处理技术的实际应用。

绘制圆形

  • 使用 drawCircle 函数在画布上绘制圆形。

  • 您创建了两个具有不同饱和度(colorSaturation75colorSaturation50)的圆形。这些饱和度是通过使用 setSaturation 函数对 baseColor 进行处理获得的。

  • drawCirclecenter 参数确定了每个圆形在画布上的位置。它是根据画布大小的比例计算的(size.widthsize.height),可以根据画布大小进行响应式定位。

  • 每个圆形的 radius 是根据画布的较小尺寸(size.minDimension)的比例确定的,确保圆形在不同屏幕尺寸或画布尺寸下适当缩放。

预览一下,看看稍微酷一点的卡片 UI。随意调整上述提到的值,探索不同的效果。

BankCardUi Implementation Part 2

第三步:点和数字

本步骤将在 BankCardUi() 中添加必要的元素 - 点和数字,以使其具有数字银行卡的真实感。我们将创建 BankCardNumber()BankCardDotGroup() 组合来实现点和数字。

首先,从这里下载 Lato 谷歌字体。然后在 res 文件夹下,右键单击并创建一个名为 font 的新目录。将所有 .ttf 文件放入新创建的 folder 中,并将它们重命名为小写字母。

Lato Fonts

添加 val LatoFontBankCardNumber()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
                    )
                )
            }
        })
}

...

定义 Lato 字体

  • FontFamily 中的每个 Font 表示 Lato 字体的不同样式和粗细。这种多样性使我们能够独特地为不同的文本元素设置样式。

BankCardDotGroup 组合

  • 使用 Canvas() 绘制四个点,表示卡号的隐藏部分。尝试调整 dotRadiusspaceBetweenDots 来改变这些点的外观。

BankCardNumber 组合

  • 用于显示卡号。通常,银行卡将卡号分组显示,最后一组可见,其他组用点表示以保证安全性。

  • 使用 Row() 组合进行布局,提供均匀的间距和垂直对齐。在其中,重复三次 BankCardDotGroup() 来表示隐藏的数字,然后将最后四位数字显示为文本。

随意尝试不同的字体粗细或样式,或调整数字和点的位置以适应您的设计偏好。

开始看起来更好了。

BankCardUi Implementation Part 3

第四步:剩余的 UI

在这一部分中,我们将为 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()
    }
}

...
```...

BankCardLabelAndText 组合函数

  • 在垂直排列中显示标签及其对应的文本。

  • BankCardUi() 中用于展示卡片上的各种信息,如持卡人姓名、过期日期和 CVV 码。

SpaceWrapper 组合函数

  • 旨在为其子元素提供方便的间距。它允许您在特定的边(上、右、下、左)添加间距,并相应地定位其子元素。

通过将所有组件组合在一起,您现在应该在模拟器或实际设备上看到一个很酷的数字银行卡。

BankCardUi 实现第 4 部分

第 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 实现第 5 部分

总结

🔗 访问完整代码: 您刚刚掌握了 BankCardUi() 组合函数的艺术 - 恭喜!为了将这个宝贵的资源随时可用,请访问此存储库获取完整的代码。

免责声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

相关文章

探索多种软件架构模式及其实用应用
2024年11月22日19:06
本文深入探讨了多种软件架构模式,包括有界上下文、边车模式、发布-订阅模式、应用网关、微服务、命令职责分离(CQRS)等,介绍了它们的优点、使用场景以及具体应用实例。文章强调根据具体项目需求和团队能力选择最合适的架构,以构建高效和可维护的解决方案,同时展示了各架构模式间的综合应用,提供了丰富的案例和技术细节。
15个高级Python快捷键助您更快编程
2024年11月21日07:02
本文分享了 15 个高级的 Python 编程快捷键,包括上下文管理器、行内字典合并、函数参数解包、链式比较、dataclasses、海象运算符、反转列表、备忘录缓存、splitlines、enumerate、字典推导、zip 用于并行迭代、itertools.chain 扁平化列表、functools.partial 部分函数和 os.path 文件路径管理等,帮助开发者提高编程效率和代码简洁性。
揭示网页开发的 11 个迷思:停止相信这些误区
2024年11月19日22:05
网页开发充满误解,这篇博文针对11个常见迷思进行揭秘。包括网站开发后不需更新、需掌握所有技术、AI会取代开发者等。强调持续学习、专业化、用户体验的重要性,澄清误区如多任务处理的必要性和最新技术的必需性。文章提醒开发者注重实用而非追求完美代码,以务实态度面对开发工作。
你知道 CSS 的四种 Focus 样式吗?
2024年11月18日21:41
本文介绍了四种 CSS focus 样式::focus、:focus-visible、:focus-within 以及自定义的 :focus-visible-within,帮助提升网站用户体验。:focus 样式应用于被选中元素;:focus-visible 仅在键盘导航时显示;:focus-within 用于父元素;自定义 :focus-visible-within 结合两者效果。合理运用这些样式能使网站更方便键盘用户导航。
利用 Python 实现自动化图像裁剪:简单高效的工作流程
2024年11月11日20:49
使用 Python 和 OpenCV 自动裁剪图像,轻松实现 16:9 的完美构图。这个指南介绍了如何通过代码进行灰度化、模糊处理和边缘检测,最终识别出最重要的部分进行裁剪。特别适合需要批量处理图像的情况,节省大量时间。
每位资深前端开发人员都应了解的 TypeScript 高级概念
2024年11月11日02:07
资深前端开发者应了解 TypeScript 的高级概念,如联合类型、交叉类型、类型保护、条件类型、映射类型、模板字面量类型和递归类型。这些特性可提升代码的可维护性和可扩展性,确保在开发复杂应用时实现更高的类型安全性和效率。