๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“ฑ| Android/๐Ÿš€ | Jetpack

[Android, Kotlin] compose๋กœ custom checkbox ๋งŒ๋“ค๊ธฐ

by immgga 2023. 8. 18.

 

์ด๋ฒˆ์— design system์„ ๋งŒ๋“œ๋Š” ์ž‘์—…์„ ํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ checkbox๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•  ์ผ์ด ์žˆ์—ˆ๋‹ค.

์ด๋ฒˆ์— ํ•„์ž ๊ฐ™์€ ๊ฒฝ์šฐ ๋ง๊ณ ๋„ ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ๋ฅผ ํ•  ๋•Œ์—๋„ checkbox๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•  ์ผ์ด ์žˆ์„ ๊ฒƒ์ด๋‹ค.

ํ•˜์ง€๋งŒ ๊ธฐ๋ณธ checkbox์˜ ๋ชจ์–‘์œผ๋กœ๋Š” ๋งŒ์กฑ์Šค๋Ÿฝ์ง€ ๋ชปํ•œ ๊ฒฝ์šฐ๋„ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

 

๊ทธ๋ž˜์„œ ์ด๋ฒˆ์— ํ•„์ž๋Š” custom checkbox๋ฅผ ๋งŒ๋“ค์–ด ๋ณด์•˜๋‹ค.

ํ•„์ž๊ฐ€ checkbox๋ฅผ ์–ด๋–ป๊ฒŒ ๋งŒ๋“ค์—ˆ๋Š”์ง€ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ํ™•์ธํ•ด ๋ณด์ž.

 

1. custom checkbox๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ์ค€๋น„๋ฌผ(?)

์ผ๋‹จ ์ฒ˜์Œ ๋งŒ๋“ค ๋•Œ ํ•„์ž๋Š” custom dialog๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๋ฌด์—‡์ด ํ•„์š”ํ•œ์ง€ ์ƒ๊ฐํ•ด ๋ณด์•˜๋‹ค.

๊ทธ๋ž˜์„œ ํ•˜๋‚˜์˜ ๊ฒฐ๋ก ์— ๋„๋‹ฌํ–ˆ๋‹ค!

check ๋˜์—ˆ์„ ๋•Œ์˜ checkbox layout, check ๋˜์ง€ ์•Š์•˜์„ ๋•Œ์˜ checkbox layout

์‚ฌ์‹ค ์ € ๋‘ ๊ฐœ๋งŒ ์žˆ์œผ๋ฉด ๋งŒ๋“ค๊ธฐ ์‰ฌ์šด component๋ผ ์ด๋ฒˆ์— ์ฒ˜์Œ์ด์—ˆ๋˜ ํ•„์ž๋„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

์ด์ œ ์ค€๋น„๋ฌผ(?)์„ ์•Œ์•˜์œผ๋‹ˆ ์ง„์งœ๋กœ ์ฝ”๋“œ๋กœ ํ™•์ธํ•ด ๋ณด์ž.

 

2. custom checkbox ์ฝ”๋“œ

๋จผ์ € check ๋œ ๊ฒฝ์šฐ์™€ ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ์˜ checkbox ui๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ๊ฒ ๋‹ค.

@Composable
private fun CheckBoxSelected() {
    Box(
        modifier = Modifier
            .size(24.dp)
            .clip(RoundedCornerShape(10.dp))
            .background(Theme.colors.primary10),
        contentAlignment = Alignment.Center
    ) {
        CheckIcon(contentDescription = "check icon")
    }
}

@Composable
private fun CheckBoxUnSelected() {
    Box(
        modifier = Modifier
            .size(24.dp)
            .clip(RoundedCornerShape(10.dp))
            .background(Theme.colors.background)
            .border(
                width = 1.dp,
                color = Theme.colors.neutral30,
                shape = RoundedCornerShape(10.dp)
            ),
    )
}

check ๋œ ๊ฒฝ์šฐ์—๋Š” ์ค‘์•™์— check icon์ด ๋“ค์–ด๊ฐ„ ํ˜•ํƒœ๋กœ ๋งŒ๋“ค์—ˆ๊ณ , check ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” ํฐ ๋ฐ”ํƒ•์— ํšŒ์ƒ‰ border๋ฅผ ์ƒ์„ฑํ•ด ์ฃผ์—ˆ๋‹ค.

์ƒ‰์ƒ์€ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ž์œ ์ด์ง€๋งŒ ํ˜„์žฌ ํ•„์ž๋Š” ์ง์ ‘ ๋งŒ๋“  theme๋ฅผ ์‚ฌ์šฉ ์ค‘์ด๊ธฐ์— ์—ฌ๋Ÿฌ๋ถ„๋“ค๊ณผ ์ฝ”๋“œ๊ฐ€ ๋‹ค๋ฅผ ์ˆ˜๊ฐ€ ์žˆ๋‹ค.(๋‚˜์ค‘์— ์‹œ๊ฐ„์ด ๋˜๋ฉด custom theme์— ๊ด€๋ จ๋œ ๊ฒƒ๋„ ์˜ฌ๋ ค๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.)

์œ„์˜ ๋‘ ๊ฐœ๋กœ ํ•ด์ฃผ์ง€ ์•Š๊ณ  ์—ฌ๋Ÿฌ๋ถ„๋“ค์ด ์ง์ ‘ custom ํ•ด์„œ ๋งŒ๋“ค์–ด๋„ ์ƒ๊ด€์—†๋‹ค.

์ด์ œ ๋‹ค์Œ์—๋Š” checkbox code๋ฅผ ํ™•์ธํ•ด ๋ณด์ž.

 

@Composable
@OptIn(ExperimentalMaterialApi::class)
fun CheckBox(
    modifier: Modifier = Modifier,
    checked: Boolean = false,
    onCheckedChange: (Boolean) -> Unit
) {
    var checkBoxState by remember { mutableStateOf(checked) }

    Card(
        modifier = modifier
            .size(24.dp),
        shape = RoundedCornerShape(10.dp),
        onClick = {
            checkBoxState = !checkBoxState
            onCheckedChange(checkBoxState)
        },
        elevation = 0.dp
    ) {
        if (checkBoxState) CheckBoxSelected()
        else CheckBoxUnSelected()
    }
}

click listener๋กœ ์ƒํƒœ๋ฅผ ๋ฐ”๊พธ๊ณ , ํด๋ฆญ ์ดํŽ™ํŠธ์ธ ripple effect๊ฐ€ ์ ์šฉ๋˜์–ด ์žˆ๋Š” card composable function์„ ์‚ฌ์šฉํ•ด ์ฃผ์—ˆ๋‹ค.

์ด ๋ถ€๋ถ„๋„ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ทจํ–ฅ์— ๋”ฐ๋ผ ๋งŒ์•ฝ ripple effect๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” box composable function์„ ์ด์šฉํ•ด modifier clickable์„ ์ด์šฉํ•ด๋„ ๋œ๋‹ค.

๋˜ํ•œ elevation์ด 0dp์ธ๋ฐ ์™œ param์„ ์ •์˜ํ•ด ๋†“์•˜๋ƒ๋ฉด card์— ๊ธฐ๋ณธ์ ์œผ๋กœ 1dp๊ฐ€ ์ ์šฉ๋˜์–ด ์žˆ๋Š”๋ฐ ํ•„์ž๋Š” elevation์ด ์—†๋Š” checkbox๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ์—ˆ๋‹ค.

 

card ํ˜น์€ box๋กœ ํ‹€์„ ๋งŒ๋“ค์–ด์ฃผ๊ณ  ๋‚˜๋ฉด content ์•ˆ์— ์œ„์— ์ƒ์„ฑํ•œ checkBoxState๋ฅผ ์ด์šฉํ•ด state์— ๋”ฐ๋ผ ์ด์ „์— ๋งŒ๋“ค์–ด๋‘” ui๋กœ ๋ณ€๊ฒฝ๋˜๊ฒŒ ํ•ด ์ฃผ์—ˆ๋‹ค.

 

3. ์‹คํ–‰ ํ™”๋ฉด

์‹คํ–‰ ํ™”๋ฉด์„ ๋ณด์ž(์™ผ์ชฝ์ด custom, ์˜ค๋ฅธ์ชฝ์ด basic)

ํ˜น์‹œ ๊ถ๊ธˆํ•ดํ•  ์—ฌ๋Ÿฌ๋ถ„๋“ค์„ ์œ„ํ•ด ์ฝ”๋“œ๋„ ์˜ฌ๋ ค์ฃผ๊ฒ ๋‹ค.

var checkState by remember { mutableStateOf(true) }
var basicCheckState by remember { mutableStateOf(false) }

Row {
    DotoriCheckBox(
        modifier = Modifier.padding(40.dp),
        checked = checkState,
        onCheckedChange = { checkState = it }
    )

    Checkbox(
        modifier = Modifier.padding(top = 40.dp),
        checked = basicCheckState,
        onCheckedChange = { basicCheckState = !basicCheckState }
    )
}

Text(
    modifier = Modifier.fillMaxWidth(),
    text = "checkbox checkState: $checkState",
    style = DotoriTheme.typography.body,
    textAlign = TextAlign.Center,
    color = DotoriTheme.colors.neutral10
)

์ฐธ๊ณ ๋กœ ์ „์ฒด ์ฝ”๋“œ๊ฐ€ ์•„๋‹ˆ๊ณ  checkbox ์ชฝ ์ฝ”๋“œ์ด๋‹ค(์‚ฌ์‹ค ์ „์ฒด๋ผ๊ณ  ํ•ด๋ดค์ž ๊ทธ๋ƒฅ row ๋ฐ–์— column๋ฐ–์— ๋‹ฌ ๊ฒŒ ์—†๋‹ค ใ…‹ใ…‹;;)

 

์ •๋ฆฌ

  • ์ด๋ ‡๊ฒŒ custom checkbox๋ฅผ ๋งŒ๋“ค์–ด ๋ณด์•˜๋Š”๋ฐ ์ƒ๊ฐ๋ณด๋‹ค ์‰ฝ๋‹ค! ์—ฌ๋Ÿฌ๋ถ„๋“ค๋„ ํ•  ์ˆ˜ ์žˆ๋‹ค!
  • ํ˜„์žฌ ํฌ์ŠคํŒ…์€ checkbox์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์œ„ํ•ด ์ฝ”๋“œ ๊ธฐ๋Šฅ์  ๋ถ€๋ถ„์€ ์„ค๋ช…์„ ์•ˆ ํ–ˆ๋Š”๋ฐ ๊ถ๊ธˆํ•œ ๊ฒƒ์ด ์žˆ์œผ๋ฉด ๋Œ“๊ธ€๋กœ ์•Œ๋ ค์ฃผ์‹œ๋ฉด ์ตœ๋Œ€ํ•œ ๋น ๋ฅด๊ฒŒ ๋‹ต๋ณ€ํ•ด ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.(์‚ฌ์‹ค ๊ธฐ๋Šฅ์ ์ธ ๋ถ€๋ถ„๋„ ๋ณ„๋กœ ์—†๋‹ค ใ…‹ใ…‹)
  • ์ด ๋ฐฉ๋ฒ• ๋ง๊ณ ๋„ ๋” ์ข‹์€ ๋ฐฉ๋ฒ•์ด๋‚˜ ๋˜ ๋‹ค๋ฅธ ์ƒ๊ฐ์„ ๊ฐ€์ง€๊ณ  ๊ณ„์‹  ๋ถ„๋“ค๋„ ๋Œ“๊ธ€๋กœ ์•Œ๋ ค์ฃผ์‹œ๋ฉด ํ•„์ž์—๊ฒŒ๋„ ์ข‹์€ ๊ณต๋ถ€๊ฐ€ ๋  ๊ฒƒ ๊ฐ™๋‹ค XD
728x90