一个可组合的旅程:使用JetPack创建独特的UI组成
#设计 #android #jetpackcompose #ui

Circles and Squares

介绍

不时,我们的团队设计师为我们重新研究的移动应用程序提出了一个绝妙的主意。但是随后,恐怖袭击是:它不是基于我们在Material Design GuidelinesiOS Human Interface Design Guidelines中很容易找到的本地组成部分。它是新鲜的,它是新的,它是习惯

作为开发人员,这是您开始挠头或充其量提出本地替代方案的确切时刻。但是,如果您可以以可重复使用的方式实施它,该怎么办?不仅如此,如果您还可以将一些动画纳入其中怎么办?

今天,我们将探索如何利用曲目毫不费力地实施高度定制的UI,使设计师最疯狂的梦想栩栩如生并创造出惊人的视觉体验。


设计

我们将在此练习中使用的设计是Crypto Wallet应用程序中令人着迷的元素,该应用程序由 Roman Lieliushkin巧妙地制作。您可以在这里查看他令人难以置信的移动设计工作:https://www.behance.net/ozmoweb

Crypto Wallet Design by Roman L.

更具体地说,我们将专注于在屏幕中央实现方牌。这些卡将显示加密货币硬币的价值以及一些其他详细信息。

Introducing "The Crypto Card"


基地

我们在处理这样的自定义UI时迈出的第一步是搜索其最接近的本机可以作为基础。这种方法有助于我们最大程度地减少所需的工作量。幸运的是,在这种情况下,它相对简单。我们可以利用一张本机卡,这为我们提供了圆角的基本结构。

val cardSize = 150.dp
Card(
    modifier = Modifier.size(cardSize).clip(RoundedCornerShape(15.dp)),
    colors = CardDefaults.cardColors(containerColor = Color.Black)
) { ... }

The background

现在,让我们前往绘图板,并考虑一下我们如何利用其他形状来塑造这种独特的形状。

与设计师紧密合作将非常有益。他们可以为定制元素的创建提供宝贵的见解,从而使我们更容易将其翻译成我们的自定义作品。

现在,计划是利用不同的形状来覆盖或切割原始卡,以实现所需的自定义形状。我们将首先创建将充满UI背景颜色的正方形,从而给人以切割的幻觉。


画布时间

在我们的卡中,我们将利用Canvas Composoble,这在我们开发新的自定义组件的计划中起着至关重要的作用。

画布使我们能够在任何x,y坐标对处定位任何大小和颜色的形状或线条。我们的第一步是添加矩形白色盒子以删除原始卡的不必要部分,该卡是通过使用drawRect()
完成的

val cardSize = 150.dp
Card(
    modifier = Modifier.size(cardSize).clip(RoundedCornerShape(15.dp)),
    colors = CardDefaults.cardColors(containerColor = Color.Black)
) {
    Canvas(modifier = Modifier.size(cardSize), onDraw = {
        drawRect(
            color = backgroundColor,
            topLeft = Offset(x = size.width - radius + (radius * 0.2f), y = 12f),
            size = size / 2f,
        )

//...

Covering the card with a rectangle

val cardSize = 150.dp
Card(
    modifier = Modifier.size(cardSize).clip(RoundedCornerShape(15.dp)),
    colors = CardDefaults.cardColors(containerColor = Color.Black)
) {
    Canvas(modifier = Modifier.size(cardSize), onDraw = {
        drawRect(
            color = backgroundColor,
            topLeft = Offset(x = size.width - radius + (radius * 0.2f), y = 12f),
            size = size / 2f,
        )

        drawRect(
            color = backgroundColor,
            topLeft = Offset(x = cardSize.value * 1.3f, y = cardSize.value * -1f),
            size = size / 2f,
        )

//...

Second rectangle in

为了在顶部创建弯曲的形状切口,我在拐角处使用了三个圆圈。通过将背景颜色(白色)与卡颜色(黑色)部分融合在一起,它成功地实现了卡片的左上角的弯曲形状。幸运的是,有一种简单的方法可以使用drawCircle()

在画布中绘制圆圈

Bring the circles

    drawCircle(
        color = Color.Red,
        radius = cardSize.value / 1.5f,
        center = Offset(
            x = size.width - radius + (radius * 0.2f),
            y = radius - (radius * 0.2f)
        )
    )

    drawCircle(
        color = Color.Green,
        radius = radius * 0.8f,
        center = Offset(
            x = size.width / 2.14f,
            y = radius - (radius * 0.2f)
        )
    )

    drawCircle(
        color = Color.Green,
        radius = radius * 0.8f,
        center = Offset(
            x = size.width - radius + (radius * 0.2f),
            y = radius + (radius * 1.93f)
        )
    )

我在这里突出显示它们,因此更容易看到,但是想法是将绿色变成卡片颜色,红色会像背景一样颜色。

One quick change of color and the custom UI starts to take shape… Literally

最后,让我们添加原始设计所拥有的圆形背景以显示加密硬币图标。为此,我们增加了一个圆圈。

One more circle

      drawCircle(
          color = bubbleColor,
          radius = radius * 0.8f,
          center = Offset(
              x = size.width - radius + (radius * 0.2f),
              y = radius - (radius * 0.2f)
          )
      )

完整的背景画布:

Canvas(modifier = Modifier.size(cardSize), onDraw = {
      drawRect(
          color = backgroundColor,
          topLeft = Offset(x = size.width - radius + (radius * 0.2f), y = 12f),
          size = size / 2f,
      )

      drawRect(
          color = backgroundColor,
          topLeft = Offset(x = cardSize.value * 1.3f, y = cardSize.value * -1f),
          size = size / 2f,
      )

      drawCircle(
          color = backgroundColor,
          radius = cardSize.value / 1.5f,
          center = Offset(
              x = size.width - radius + (radius * 0.2f),
              y = radius - (radius * 0.2f)
          )
      )

      drawCircle(
          color = cardBackground,
          radius = radius * 0.8f,
          center = Offset(
              x = size.width / 2.14f,
              y = radius - (radius * 0.2f)
          )
      )

      drawCircle(
          color = cardBackground,
          radius = radius * 0.8f,
          center = Offset(
              x = size.width - radius + (radius * 0.2f),
              y = radius + (radius * 1.93f)
          )
      )

      drawCircle(
          color = bubbleColor,
          radius = radius * 0.8f,
          center = Offset(
              x = size.width - radius + (radius * 0.2f),
              y = radius - (radius * 0.2f)
          )
      )

  })

在白色表面上显示这种合并时,我们涵盖的卡的残余物可以在右上角稍微看到。

Thats ugly

要修复它,我们可以将整个卡包装到一个盒子中,并添加第二个画布作为最后一个元素,该元素用另一个抽签覆盖该区域,并在其顶部带上灰色圆圈。

Canvas(modifier = Modifier.size(cardSize), onDraw = {
    drawRect(
        color = backgroundColor,
        topLeft = Offset(x = size.width - (cardSize.value / 2f) - 7.5f, y = 0f),
        size = size / 5f
    )

    drawCircle(
        color = bubbleColor,
        radius = radius * 0.8f,
        center = Offset(
            x = size.width - radius + (radius * 0.2f),
            y = radius - (radius * 0.2f)
        )
    )
})
//...
Box {
        Card(
//...

,让我们还使我们的组合可自定义,以便可以轻松调整背景和灰色气泡以适合不同的样式和主题。

@Composable
fun CryptoCardBackground(
    cardBackground: Color = Color.Black,
    bubbleColor: Color = Color(0xFFf3f3f3),
    backgroundColor: Color = Color.White,
    cardSize: Dp = 150.dp,
) {

  ...

}

Left: Roman | Right: Us


A Coffee

咖啡时间(休闲时光

让我们花点时间呼吸并反思我们的进步。我们通过在卡片组件的基础建立圆圈和矩形来设法通过合并圆形和矩形来手工制作自己的自定义UI。结果真是太棒了!现在,是时候介绍最后一部分:数据。

为了引入数据,我们将使用手工制作的背景和RowsColumns带有文本。

UI Content

要在视觉上表示我们的数据,我创建了一个数据类,该类别将作为我们组合的输入,并具有所需的所有必需字段:

data class CryptoCardData(
    val name: String,
    val value: Float,
    val valueChange: Int,
    val currentTotal: Long,
    val icon: Int
)

由于我们的最终组合需要能够支持不同的背景颜色,因此我们需要引入 style 属性,该属性将在整个合并中使用以定义文本颜色,卡片填充和背景。

enum class CryptoCardStyle {
    Dark, Light
}

最后,就在变更值旁边(在此图像中为-18%),一个小图标突出显示该值是正值或负数的。因此,我们需要引入一个小的组合,可以为我们处理这一点。

@Composable
private fun ChangeIcon(valueChange: Int = -18) {
    var iconModifier: Modifier = Modifier
    val tint: Color
    val contentDescription: String

    if (valueChange > 0) {
        tint = Color(0xFFFFFFFF)
        iconModifier = Modifier.rotate(180f)
        contentDescription = "Arrow Up"
    } else {
        tint = Color(0xFFa97d72)
        contentDescription = "Arrow Down"
    }

    Icon(
        modifier = iconModifier.size(17.dp),
        painter = painterResource(id = R.drawable.ic_arrow_bottom_left),
        contentDescription = contentDescription,
        tint = tint
    )
}

将所有这些零件放在一起之后,在这里,我们完全自定义的加密卡在Android模拟器中运行。

The finished product

如果对完整实施感兴趣,您可以在这里找到它:CryptoCard.kt


在即将发表的帖子中,我们将探讨如何在加载数据时轻松更新它以支持小动画,以及如何准确地将任何自定义字体纳入Compose。

我希望您今天学到了一些新知识,并感谢您阅读这篇文章!