๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Dev/Android

[Android] XML ์—†์ด ํ…์ŠคํŠธ, ๋ฒ„ํŠผ ๋ฐฐ๊ฒฝ ๋‘ฅ๊ธ€๊ฒŒ ๋งŒ๋“ค๊ธฐ(XML ๊ทธ๋งŒ ๋งŒ๋“ค์ž)

by JUNE.C 2022. 4. 17.

๐Ÿง  ๋ฐฐ๊ฒฝ

 ์•ˆ๋“œ๋กœ์ด๋“œ๋ฅผ ์ฒ˜์Œ ์ ‘ํ–ˆ์„ ๋•Œ๋ถ€ํ„ฐ ํ…์ŠคํŠธ, ๋ฒ„ํŠผ์„ ๋‘ฅ๊ธ€๊ฒŒ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ ์ˆ˜๋งŽ์€ XML ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์™”๋‹ค.

๊ฝค ๋งŽ์€ ํ”„๋กœ์ ํŠธ๋ฅผ ํ•ด์™”์ง€๋งŒ, ์ฝ”๋„ˆ๊ฐ’์„ ๋„ฃ๊ธฐ ์œ„ํ•ด์„œ ๊ธฐ๊ณ„์ ์œผ๋กœ XML Shape๋ฅผ ์“ฐ๊ณ ์žˆ์—ˆ๋‹ค.

ํšŒ์‚ฌ ์‹ ์‚ฌ์—… ์ถœ์‹œ๋ฅผ ์•ž๋‘๊ณ  ํ”„๋กœ์ ํŠธ ์ •๋ฆฌ๋ฅผ ํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ, ์ •๋ง ๋ณด๊ธฐ ์‹ซ์€ ์ •๋„์˜ shape XML๋“ค์ด ์žˆ์—ˆ๋‹ค.

์‚ฌ์—… ์•„์ดํ…œ์— ๋งŽ์€ ์ƒํƒœ ๊ฐ’(status)๊ณผ ๋‹ค์–‘ํ•œ ๋ทฐ๊ฐ€ ์žˆ์—ˆ๊ธฐ์— ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ๋ณด๋‹ค ํŒŒ์ผ์ด ํ›จ์”ฌ ๋” ๋งŽ์•˜๋‹ค.

์‹ฌ์ง€์–ด ์†์„ ๋งŽ์ด ๊ฑฐ์ณ ๊ฐ„ ์žฅ๊ธฐ ํ”„๋กœ์ ํŠธ์ด๊ณ , ์ž…์‚ฌํ•˜๊ธฐ ์ „๊นŒ์ง€ ์ปจ๋ฒค์…˜์ด ์—†์—ˆ๊ธฐ์— ์ถฉ๊ฒฉ ๊ทธ ์ž์ฒด๋‹ค.๐Ÿคฌ

 

 ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ์ด๋Ÿฐ ์ƒํ™ฉ์„ ๋ณด๋ฉด ๋ฐ”๋กœ ๊ณ ์น˜๊ณ  ์‹ถ์ง€ ์•Š๊ฒ ๋Š”๊ฐ€๐Ÿฅธ

 

์นœํ•œ ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ์ˆ˜์†Œ๋ฌธํ–ˆ์ง€๋งŒ ๋ชจ๋‘ XML Shape๋ฅผ ์“ฐ๊ณ  ์žˆ์—ˆ๋‹ค.๐Ÿ˜‡

์„ ๋งํ•˜๋Š” ๊ธฐ์—… ํ”„๋กœ์ ํŠธ๋„ ์ด๋ ‡๋‹ค๊ณ  ํ•˜๋‹ˆ ๋†€๋ž๊ธด ํ–ˆ๋‹ค.


๐Ÿ™ƒ ๊ณ ๋ฏผํ•œ ํ•ด๊ฒฐ์ฑ…๋“ค

1. MaterialButton ํ™œ์šฉ

 MaterialButton์—๋Š” cornerRadius๋ผ๋Š” ์†์„ฑ์ด ์žˆ๋‹ค. inset, ripple ๋“ฑ์„ ์ œ๊ฑฐํ•œ๋‹ค๋ฉด ํ…์ŠคํŠธ๋ทฐ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์ง€ ์•Š๊ฒ ๋Š๋ƒ๋Š” ์ƒ๊ฐ์„ ํ–ˆ๋‹ค.

 Button์ด TextView ํ•˜์œ„ ๊ฐ์ฒด๋ผ๊ณ  ํ•˜์ง€๋งŒ, ๋ถˆํ•„์š”ํ•œ ๊ตฌํ˜„์ด ํฌํ•จ๋˜์–ด ์žˆ์œผ๋‹ˆ ๊ฐ์ฒด ์ง€ํ–ฅ์  ์ธก๋ฉด์—์„œ๋Š” ๋งž์ง€ ์•Š๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค.

 

2. ShapeDrawable

 XML๋กœ ์ •์˜๋œ Shape๋„ ๊ฒฐ๊ตญ์—” ๋ Œ๋”๋˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ ์ฝ”๋“œ๋กœ ๊ทธ๋ ค์งˆ ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ–ˆ๋‹ค.

ShapeDrawable์ด๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๊ทธ๋ƒฅ ๊ฒ€์ƒ‰ํ•ด๋ดค๋Š”๋ฐ ์กด์žฌํ–ˆ๋‹ค.๐Ÿ˜ฎ

๊ทธ๋ฆฌ๊ณ  Radius์™€ Solid๊นŒ์ง€ ์ฑ„์šธ ์ˆ˜ ์žˆ์—ˆ์ง€๋งŒ, Stroke ์„ค์ •์ด ๋ถˆ๊ฐ€๋Šฅํ–ˆ๋‹ค.๐Ÿ˜ข

 

3. GradientDrawable

์•ˆ๋“œ๋กœ์ด๋“œ XML attributes๋ฅผ ์‚ดํŽด๋ณด๋‹ˆ Shape XML์—์„œ ์‚ฌ์šฉํ•˜๋Š” corner/solid/stroke ๋“ฑ GradientDrawable์˜ ์†์„ฑ๋“ค๋กœ ์ •์˜๋˜์–ด ์žˆ์—ˆ๋‹ค.

GradientDrawable์—๋Š” Stroke๊นŒ์ง€ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.๐Ÿ‘

 

4. Compose๐Ÿ˜‡

Compose์งฑ

 


๐Ÿ“Œ Code

GradientDrawable์„ ์ƒ์„ฑํ•˜์—ฌ View์˜ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์— ์„ค์ •ํ•ด์ฃผ๋Š” ํ˜•์‹์„ ๊ณ ์•ˆํ–ˆ๋‹ค.

View ํ™•์žฅ ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ๋กœ ์„ธํŒ…ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, DataBinding์„ ํ™œ์šฉํ•˜์—ฌ XML ๋ทฐ์—์„œ ๋ฐ”๋กœ ์ ์šฉ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

@BindingAdapter(
    value = [
        "setTopLeftCorners",
        "setTopRightCorners",
        "setBottomRightCorners",
        "setBottomLeftCorners",
        "setInsideBackgroundColor",
        "setStrokeWidth",
        "setStrokeColor"
    ], requireAll = false
)
fun View.setBackgroundShape(
    @Px topLeftCorners: Float? = null,
    @Px topRightCorners: Float? = null,
    @Px bottomRightCorners: Float? = null,
    @Px bottomLeftCorners: Float? = null,
    @ColorInt insideBackgroundColor: Int? = null,
    @Px strokeWidth: Float? = null,
    @ColorInt strokeColor: Int? = null
) {
    val corners = floatArrayOf(
        topLeftCorners ?: 0f, topLeftCorners ?: 0f,
        topRightCorners ?: 0f, topRightCorners ?: 0f,
        bottomRightCorners ?: 0f, bottomRightCorners ?: 0f,
        bottomLeftCorners ?: 0f, bottomLeftCorners ?: 0f
    )

    GradientDrawable().apply {
        setStroke(strokeWidth?.toInt() ?: 0, strokeColor ?: android.R.color.transparent)
        cornerRadii = corners
        setColor(insideBackgroundColor ?: android.R.color.transparent)
    }.run {
        this@setBackgroundShape.background = this
    }
}

 

๋Œ€๋ถ€๋ถ„์˜ ๋‘ฅ๊ทผ ํ˜•ํƒœ ๋ทฐ๋Š” ์ฝ”๋„ˆ๊ฐ’์ด ์„œ๋กœ ๋‹ค๋ฅด์ง€๋Š” ์•Š๊ธฐ์— ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ๋‹คํ˜•์„ฑ์„ ์ฑ™๊ฒผ๋‹ค.

@BindingAdapter(
    value = [
        "setTopCorners",
        "setBottomCorners",
        "setInsideBackgroundColor",
        "setStrokeWidth",
        "setStrokeColor"
    ], requireAll = false
)
fun View.setBackgroundShape(
    @Px topCorners: Float? = null,
    @Px bottomCorners: Float? = null,
    @ColorInt insideBackgroundColor: Int? = null,
    @Px strokeWidth: Float? = null,
    @ColorInt strokeColor: Int? = null
) {
    setBackgroundShape(
        topLeftCorners = topCorners,
        topRightCorners = topCorners,
        bottomRightCorners = bottomCorners,
        bottomLeftCorners = bottomCorners,
        insideBackgroundColor = insideBackgroundColor,
        strokeWidth = strokeWidth,
        strokeColor = strokeColor
    )
}

@BindingAdapter(
    value = [
        "setCorners",
        "setInsideBackgroundColor",
        "setStrokeWidth",
        "setStrokeColor"
    ], requireAll = false
)
fun View.setBackgroundShape(
    @Px corners: Float? = null,
    @ColorInt insideBackgroundColor: Int? = null,
    @Px strokeWidth: Float? = null,
    @ColorInt strokeColor: Int? = null
) {
    setBackgroundShape(
        topLeftCorners = corners,
        topRightCorners = corners,
        bottomRightCorners = corners,
        bottomLeftCorners = corners,
        insideBackgroundColor = insideBackgroundColor,
        strokeWidth = strokeWidth,
        strokeColor = strokeColor
    )
}

 

๋ทฐ ์ฝ”๋“œ๋กœ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒ ๋‹ค.

binding.txtHelloWorld.setBackgroundShape(
            corners = 8.toPx,
            insideBackgroundColor = ContextCompat.getColor(this, R.color.purple_500),
            strokeWidth = 2.toPx,
            strokeColor = ContextCompat.getColor(this, R.color.black)
)

// ์ถ”๊ฐ€ ํŒ(?) : Px ๋‹จ์œ„์ด๊ธฐ์— DpToPx ๋ณ€ํ™˜์ด ํ•„์š”ํ•˜๋‹ค
val Int.toPx: Float
    get() {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), resources.displayMetrics)
    }

 

์•„๋ž˜ ์Šคํฌ๋ฆฐ์ƒท์˜ ์ƒ๋‹จ ๋ฒ„ํŠผ์€ ๋ฐฉ๊ธˆ ์ •์˜ํ•œ ํ•จ์ˆ˜์— ์˜ํ•ด ๋งŒ๋“ค์–ด์กŒ์œผ๋ฉฐ, ํ•˜๋‹จ ๋ฒ„ํŠผ์€ Shape XML๋กœ ๋งŒ๋“ค์–ด์กŒ๋‹ค.

๋™์ผํ•œ Drawable ์ฆ๋ช… ๋


๐Ÿ“ฎ ๋‹จ์ , ๊ทธ๋ฆฌ๊ณ  Compose(?)

์ฝ”๋“œ ๋˜๋Š” ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์ด๊ธฐ์— ํ”„๋ฆฌ๋ทฐ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์ด ํฐ ๋‹จ์ ์ผ ๊ฒƒ ๊ฐ™๋‹ค.

๋‚˜๋Š” ํ”„๋ฆฌ๋ทฐ๋ฅผ ํฐ ํ‹€, ํ˜•ํƒœ๋ฅผ ์•Œ์•„๋ณด๊ธฐ ์œ„ํ•ด์„œ๋งŒ ์‚ฌ์šฉํ•˜๊ธด ํ•ด์„œ, ๋‚˜ ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ํฐ ๋ฌธ์ œ๋Š” ์•„๋‹ˆ๋‹ค.

 

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ปค์Šคํ…€๋ทฐ๋กœ TextView๋ฅผ ๋งŒ๋“ค๊ฑฐ๋‚˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ˜•ํƒœ๋กœ ์ œ์ž‘ํ•˜์—ฌ ์—ฌ๋Ÿฌ ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒ ์ง€๋งŒ

๋ฌธ์ œ๋ฅผ ๋Š๋ผ์ง€ ๋ชปํ•˜์—ฌ ์ฑ„ํƒํ•œ ๋ฐฉ๋ฒ•์€ ์•„๋‹ˆ๋‹ค.

TDS(Toss Design System)์ฒ˜๋Ÿผ ์†Œ์† ๋””์ž์ธ ํŒ€์ด ์ผ๊ด€์ ์ธ ํ˜•ํƒœ์˜ ๋ทฐ๋ฅผ ๊ณ ์•ˆํ•œ๋‹ค๋ฉด ์ปค์Šคํ…€๋ทฐ๋ฅผ ๋งŒ๋“ค์–ด ํ™•์žฅ์„ฑ์žˆ๊ฒŒ ์ ์šฉํ•ด๋ณผ ์ƒ๊ฐ์€ ์žˆ๋‹ค.

 

๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€ Compose๋กœ ๋„˜์–ด๊ฐ€๋Š” ๊ฒƒ์ด๋‹ค.

Modifier๋กœ ์‰ฝ๊ฒŒ ์ฝ”๋„ˆ ๊ฐ’์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๊ณ  ์ง๊ด€์ ์ด๋ฉฐ ํ”„๋ฆฌ๋ทฐ๊นŒ์ง€ ํ•ด๊ฒฐ๋œ๋‹ค.

Button(
       shape = RoundedCornerShape(23.dp),
       border = BorderStroke(3.dp, Color.Red)
)

ํ•˜์ง€๋งŒ ๊ธฐ์กด ํ”„๋กœ์ ํŠธ๋ฅผ Compose๋กœ ์ „ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ๋ฌด๋ฆฌ๊ฐ€ ์žˆ๋‹ค.

 

๋ฌธ์ œ(๋ฌธ์ œ๊ฐ€ ์•„๋‹ ์ˆ˜๋„ ์žˆ์ง€๋งŒ)๋ฅผ ์ง๋ฉดํ•˜๊ณ ์„œ์•ผ ๊ณ ๋ฏผ์„ ํ–ˆ๋‹ค๋Š” ๊ฒŒ ์Šค์Šค๋กœ ์•„์‰ฌ์›Œ์„œ ๋‹น์—ฐํ•˜๊ฒŒ ๋„˜์–ด๊ฐ„ ๊ฒƒ๋“ค์„ ๋Œ์•„๋ณด๊ณ  ์žˆ๋‹ค.

๋‹ค์–‘ํ•œ ๋ฌธ์ œ ์ค‘ ๋ทฐ์™€ ๊ด€๋ จ๋œ ๋ฌธ์ œ ๋Œ€๋ถ€๋ถ„์€ Compose๋กœ ํ•ด๊ฒฐ์ด ๊ฐ€๋Šฅํ•˜๊ธฐ์— ํ”„๋กœ์ ํŠธ ๋‹จ์œ„๋กœ ๋นจ๋ฆฌ ๋ง›๋ณด๊ณ  ์‹ถ๋‹ค.

ํšŒ์‚ฌ์—์„œ ์‹ ์‚ฌ์—…์„ ๋˜ ์ค€๋น„ํ•˜๊ณ  ์žˆ๋Š”๋ฐ ๊ทธ ํ”„๋กœ์ ํŠธ๋Š” Compose๋ฅผ 100% ์‚ฌ์šฉํ•  ์˜ˆ์ •์ด๋‹ค.

(๋ฐ๋“œ๋ผ์ธ ํƒ“์— XML๋กœ ๋„˜์–ด๊ฐ€๋Š” ๊ฒƒ์€ ์ ˆ๋Œ€ ํ•˜์ง€๋ง์ž๊ณ , ๊ธˆ์ง€ํ•  ๊ฒƒ์ด๋ผ๊ณ  ํŒ€์— ์„ ์–ธํ•ด๋†จ๋‹ค. ๋‚ด๊ฐ€ ๋ญ๋ผ๊ณ )

 

์ง€๊ธ‹์ง€๊ธ‹ํ•œ XML, RecyclerView ํƒˆ์ถœ!?๐Ÿ‘Š

 

 

 

๋Œ“๊ธ€