<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dong-hyeok.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 01 Sep 2023 01:29:17 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dong-hyeok.log</title>
            <url>https://velog.velcdn.com/images/dong-hyeok/profile/16287da7-d204-496f-8e7d-89d1c471954f/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dong-hyeok.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dong-hyeok" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - Animation (Material3)]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Animation-Material3</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Animation-Material3</guid>
            <pubDate>Fri, 01 Sep 2023 01:29:17 GMT</pubDate>
            <description><![CDATA[<p><a href="https://developer.android.com/jetpack/compose/animation?hl=ko">애니메이션  |  Jetpack Compose  |  Android Developers</a></p>
<h2 id="예제1">예제[1]</h2>
<h3 id="animatedvisibility---enter"><code>AnimatedVisibility</code> - <code>enter</code></h3>
<ul>
<li>fadeIn</li>
<li>scaleIn</li>
<li>slideIn</li>
<li>slideInHorizontally</li>
<li>slideInVertically</li>
<li>expandIn</li>
<li>expandHorizontally</li>
<li>expandVertically</li>
<li>AnimatedVisibility</li>
</ul>
<h3 id="animatedvisibility---exit"><code>AnimatedVisibility</code> - <code>exit</code></h3>
<ul>
<li>fadeOut</li>
<li>scaleOut</li>
<li>slideOut</li>
<li>slideOutHorizontally</li>
<li>slideOutVertically</li>
<li>shrinkOut</li>
<li>shrinkHorizontally</li>
<li>shrinkVertically</li>
<li>AnimatedVisibility</li>
</ul>
<pre><code class="language-kotlin">@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AnimationEx() {
    var helloWorldVisible by remember { mutableStateOf(true) }
    var isRed by remember { mutableStateOf(false) }


    // Red -&gt; White로 애니메이션이 적용되어 색상 변경
    val backgroundColor by animateColorAsState(
        targetValue = if(isRed) Color.Red else Color.White
    )

    // alpha값을 애니메이션 적용
    val alpha by animateFloatAsState(
        targetValue = if(isRed) 1.0f else 0.5f
    )

    Column(
        modifier = Modifier
            .padding(16.dp)
            .background(backgroundColor)  // animation - background
            .alpha(alpha)  // animation - alpha
            .fillMaxSize()
    ) {

        // 1. AnimatedVisibility
        // helloWorldVisible의 값에 따라 보이기 또는 사라지게 애니메이션 적용
        AnimatedVisibility(
            visible = helloWorldVisible,
            enter = fadeIn() + expandHorizontally(), // 넓이를 0에서 ~ 보이게 하기 (옆으로 나타남 Horizon이라서)
            exit = slideOutHorizontally() // 사라지게 하기
        ) {
            Text(text = &quot;Hello World!&quot;)
        }

        Row(
            Modifier.selectable(
                selected = helloWorldVisible,
                onClick = {
                    helloWorldVisible = true
                }
            ),
            verticalAlignment = Alignment.CenterVertically
        ) {
            RadioButton(
                selected = helloWorldVisible,
                onClick = { helloWorldVisible = true }
            )
            Text(
                text = &quot;Hello World 보이기&quot;
            )
        }

        Row(
            Modifier.selectable(
                selected = !helloWorldVisible,
                onClick = {
                    helloWorldVisible = false
                }
            ),
            verticalAlignment = Alignment.CenterVertically
        ) {
            RadioButton(
                selected = !helloWorldVisible,
                onClick = { helloWorldVisible = false }
            )
            Text(
                text = &quot;Hello World 감추기&quot;
            )
        }

        Text(text = &quot;배경 색을 바꾸어봅시다.&quot;)

        Row(
            Modifier.selectable(
                selected = !isRed,
                onClick = {
                    isRed = false
                }
            ),
            verticalAlignment = Alignment.CenterVertically
        ) {
            RadioButton(
                selected = !isRed,
                onClick = { isRed = false }
            )
            Text(
                text = &quot;흰색&quot;
            )
        }

        Row(
            Modifier.selectable(
                selected = isRed,
                onClick = {
                    isRed = true
                }
            ),
            verticalAlignment = Alignment.CenterVertically
        ) {
            RadioButton(
                selected = isRed,
                onClick = { isRed = true }
            )
            Text(
                text = &quot;빨간색&quot;
            )
        }
    }
}

@Preview(showBackground = true)
@Composable
fun AnimationPreview() {
    Compose_exampleTheme {
        AnimationEx()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/ec720f3f-66dc-463a-822b-10bf513095c6/image.png" alt=""></p>
<br>
<br>

<h2 id="예제2">예제[2]</h2>
<ul>
<li>updateTransition</li>
<li>Crossfade</li>
</ul>
<pre><code class="language-kotlin">@Composable
fun Animation2Ex() {
    var isDarkMode by remember { mutableStateOf(false) }

    // backGroundStatus, textColorStatus, alpha 와 같이 여러가지 애니메이션을 한 단위로 묶어 관리가 가능
    val trasition = updateTransition(targetState = isDarkMode, label = &quot;darkMode Ani&quot;)

    // 아래 state -&gt; 는 trasition의 isDarkMode
    val backGroundStatus by trasition.animateColor(label = &quot;darkmode backGroundColor&quot;) { state -&gt;
        when(state) {
            false -&gt; { Color.White }
            true -&gt; { Color.Black }
        }
    }

    val textColorStatus by trasition.animateColor(label = &quot;darkmode textColorStatus&quot;) { state -&gt;
        when(state) {
            false -&gt; { Color.Black }
            true -&gt; { Color.White }
        }
    }

    val alpha by trasition.animateFloat (label = &quot;darkmode alpha&quot;){ state -&gt;
        when(state) {
            false -&gt; 0.7f
            true -&gt; 1.0f
        }
    }

    Column (
      modifier = Modifier
          .background(backGroundStatus)
          .alpha(alpha)
          .fillMaxSize()
    ) {
        // 일반모드, 다크모드
        RadioButtonWithText(text = &quot;일반 모드&quot;, color=textColorStatus, selected = !isDarkMode) {
            isDarkMode = false
        }
        RadioButtonWithText(text = &quot;다크 모드&quot;, color=textColorStatus, selected = isDarkMode) {
            isDarkMode = true
        }

        // darkmode 상태에 따라 박스 위치 변경하는 애니메이션
        // Crossfade는 컨텐츠에 애니메이션을 적용할 때 주로 사용됨. 
        Crossfade(targetState = isDarkMode, label = &quot;&quot;) { state -&gt;
            when(state) {
                false -&gt; {
                    Column {
                        Box(modifier = Modifier
                            .background(Color.Red)
                            .size(20.dp)) {
                            Text(&quot;1&quot;)
                        }
                        Box(modifier = Modifier
                            .background(Color.Magenta)
                            .size(20.dp)) {
                            Text(&quot;2&quot;)
                        }
                        Box(modifier = Modifier
                            .background(Color.Blue)
                            .size(20.dp)) {
                            Text(&quot;3&quot;)
                        }
                    }
                }
                true -&gt; {
                    Row {
                        Box(modifier = Modifier
                            .background(Color.Red)
                            .size(20.dp)) {
                            Text(&quot;1&quot;)
                        }
                        Box(modifier = Modifier
                            .background(Color.Magenta)
                            .size(20.dp)) {
                            Text(&quot;2&quot;)
                        }
                        Box(modifier = Modifier
                            .background(Color.Blue)
                            .size(20.dp)) {
                            Text(&quot;3&quot;)
                        }
                    }
                }
            }

        }
    }
}

@Preview(showBackground = true)
@Composable
fun AnimationPreview2() {
    Compose_exampleTheme {
        RadioButtonWithText(
            text = &quot;라디오 버튼&quot;,
            color = Color.Red,
            selected = true,
            onClick = {}
        )
    }
}

@Composable
fun RadioButtonWithText(
    text: String,
    color: Color = Color.Black,
    selected: Boolean,
    onClick: () -&gt; Unit
) {
    Row(
        modifier = Modifier.selectable(
            selected = selected,
            onClick = onClick
        ),
        verticalAlignment = Alignment.CenterVertically
    ) {
        RadioButton(selected = selected, onClick = onClick)
        Text(text = text, color = color)
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - State & Hoisting]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-State-Hoisting</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-State-Hoisting</guid>
            <pubDate>Fri, 01 Sep 2023 01:27:30 GMT</pubDate>
            <description><![CDATA[<h3 id="remember"><strong>remember</strong></h3>
<p>Composable 함수 내에서 상태를 유지하기 위해 사용됩니다. 이 함수로 생성된 상태는 Composable 함수가 재호출되더라도 변경되지 않습니다. 즉, 상태가 Composable 함수가 호출될 때 한 번 생성되고 그 상태가 유지됩니다. 이는 재호출이 발생하더라도 상태를 유지하고 싶을 때 사용됩니다.</p>
<h3 id="remembersaveable"><strong>rememberSaveable</strong></h3>
<p>주로 화면 회전과 같은 상태 변경으로 인해 Composable 함수가 다시 생성될 때 상태를 보존하고 복원하기 위해 사용됩니다. 이 함수를 사용하면 Composable 함수가 재생성되더라도 상태를 유지하며, 이전에 입력된 데이터나 상태를 회복하는 데에 특히 유용합니다.</p>
<h3 id="실습-1">실습 [1]</h3>
<ul>
<li>첫 번째 실습은 OutlinedTextField 내부까지 값이 도달하게 했습니다. 
이러한 방식은 권장하지 않은 방식입니다.</li>
<li>가능한 상태가 전달되는 범위를 좁히는 것이 중요합니다.
두 번째 실습에서는 State Hoisting을 활용하여 상태 전달 범위를 좁히는 실습을 진행하겠습니다.</li>
</ul>
<pre><code class="language-java">@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun StateHoistingEx() {
    /*
        rememberSaveable은 Configuration이 변경시 값을 저장할 수 있다.
        그러나 캐시를 사용하기 때문에 많은 데이터에 사용하는 것은 권장하지 않는다.
     */

    var num by rememberSaveable {
        mutableStateOf(&quot;0&quot;)
    }
    var result by remember {
        mutableStateOf((0.0).toString())
    }

    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = num,
            onValueChange = {
                if(it.isBlank()) {
                    num = &quot;&quot;
                    result = &quot;&quot;
                    return@OutlinedTextField
                }
                val inputNum = it.toFloatOrNull() ?: return@OutlinedTextField // toFloatOrNull() : Float형으로 변경 불가능한 것은 null 리턴
                result = (inputNum * inputNum * 3.14).toString()
                num = it

            }, label = {
                Text(&quot;반지름&quot;)
            }
        )
        OutlinedTextField(
            value = result,
            onValueChange = {
                result = it
            },
            label = {
                Text(&quot;원 넓이&quot;)
            }
        )
    }
}

@Preview(showBackground = true)
@Composable
fun StateHoistingPreview() {
    Compose_exampleTheme {
        StateHoistingEx()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/8dc63cb7-dd47-4413-ab9f-5e0b9d0c1d37/image.png" alt=""></p>
<br>
<br>

<h3 id="실습-2---state-hoisting">실습 [2] - State Hoisting</h3>
<ul>
<li>State Hoisting을 사용하여 상태를 관리합니다.</li>
<li>가능한 상태를 별도로 빼서 관리하는 것이 좋습니다. 
( == UI 부분은 Stateless하게 관리하는 것이 좋다.)</li>
<li>자세한 설명은 주석을 참고</li>
</ul>
<pre><code class="language-java">@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun StateHoistingEx() {
    /*
        rememberSaveable은 Configuration이 변경시 값을 저장할 수 있다.
        그러나 캐시를 사용하기 때문에 많은 데이터에 사용하는 것은 권장하지 않는다.
     */

    var num by rememberSaveable {
        mutableStateOf(&quot;0&quot;)
    }
    var resultNum by remember {
        mutableStateOf((0.0).toString())
    }

    numToResultStateless(
        num,
        resultNum
    ) {
        if (it.isBlank()) {
            num = &quot;&quot;
            resultNum = &quot;&quot;
            return@numToResultStateless  // 입력 값이 numToResultStateless에서 리턴되기 때문
        }
        val inputNum = it.toFloatOrNull()
            ?: return@numToResultStateless // toFloatOrNull() : Float형으로 변경 불가능한 것은 null 리턴
        resultNum = (inputNum * inputNum * 3.14).toString()
        num = it
    }

}

/*
    numToResultStateless는 상태를 가지고 있지 않아서 상태를 변경하지 않는다.
    onNumChange: (String) -&gt; Unit 은 num이 변경됬을때 호출할 콜백을 사용해 상위 계층에서 상태를 변경하도록 한다.
    이 방식을 통해 numToResultStateless는 상태에 개입하지 않도록 한다.
    State관리의 책임은 StateHoistingEx()에서 맡도록 한다.
 */
@Composable
fun numToResultStateless(
    num: String,
    resultNum: String,
    onNumChange: (String) -&gt; Unit
) {
    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = num,
            onValueChange = onNumChange, // 상태 변경을 onNumChange로 위임
            label = {
                Text(&quot;반지름&quot;)
            }
        )
        OutlinedTextField(
            value = resultNum,
            onValueChange = {},
            label = {
                Text(&quot;원 넓이&quot;)
            }
        )
    }
}

@Preview(showBackground = true)
@Composable
fun StateHoistingPreview() {
    Compose_exampleTheme {
        StateHoistingEx()
    }
}</code></pre>
<ul>
<li><p>왜 State Hoisting을 해서 상태가 없도록 하는가?</p>
<ul>
<li>테스트시 위 예시 코드와 같이  <code>onNumChange: (String) -&gt; Unit</code>를 변형시켜 손 쉽게 테스트가 가능</li>
<li>첫 번째 예시에서 한 곳에 <code>num</code>과 <code>resultNum</code>를 연동할 필요가 사라짐</li>
<li>두 번째 예시 코드에서 fun numToResultStateless()는 상태가 없다. 
⇒ 여러개의 numToResultStateless를 같이 호출할 수 있다. 즉, 상태를 한 곳에 모아둘 수 있다.</li>
</ul>
</li>
<li><p>두 번째 예시 코드는 상태를 <code>StateHoistingEx()</code>서 관리하도록 했습니다. 
이것을 ViewModel 계층으로 옮길 수 있습니다. 
(<code>numToResultStateless</code>는 변경이 필요 X)</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - BottomAppBar (Material3)]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-BottomAppBar-Material3</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-BottomAppBar-Material3</guid>
            <pubDate>Fri, 01 Sep 2023 01:25:21 GMT</pubDate>
            <description><![CDATA[<h2 id="bottomappbar">BottomAppBar</h2>
<pre><code class="language-java">@Composable
fun BottomAppBarEx() {
    val snackbarHostState = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()

    var counter by remember {
        mutableStateOf(0)
    }

    Scaffold (
        snackbarHost = { SnackbarHost(snackbarHostState) },

        bottomBar = {
            BottomAppBar() {
                Text(&quot;study&quot;)
                Button(onClick = {
                    scope.launch {
                        snackbarHostState.showSnackbar( // 스낵바 결과 받기 가능
                            message = &quot;snackBar 보인다~~!!&quot;,
                            actionLabel = &quot;close&quot;,
                            duration = SnackbarDuration.Short
                        )
                    }
                }) {
                    Text(&quot;show SnackBar&quot;)
                }
                Button(onClick = {
                    counter++
                }) {
                    Text(text = &quot;+&quot;)
                }
                Button(onClick = {
                    counter--
                }) {
                    Text(text = &quot;-&quot;)
                }
            }
        }
    ) {
                Surface(
            modifier = Modifier.padding(it),
        ) {
            Text(text = &quot; &quot;)
        }
    }
}

@Preview(showBackground = true)
@Composable
fun BottomAppBarPreview() {
    Compose_exampleTheme {
        BottomAppBarEx()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/704646c2-b906-4aef-b418-60143749b14b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - SnackBar (Material3)]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-SnackBar-Material3</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-SnackBar-Material3</guid>
            <pubDate>Fri, 01 Sep 2023 01:23:52 GMT</pubDate>
            <description><![CDATA[<p><a href="https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#Snackbar(androidx.compose.material3.SnackbarData,androidx.compose.ui.Modifier,kotlin.Boolean,androidx.compose.ui.graphics.Shape,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color)">androidx.compose.material3  |  Android Developers</a></p>
<pre><code class="language-java">@Composable
fun SnackBarEx() {
    var counter by remember {
        mutableStateOf(0)
    }

    // SnackbarHostState 생성
    val snackbarHostState = remember { SnackbarHostState() }

    // coroutineScope 사용해야 함 
    val scope = rememberCoroutineScope()

    Scaffold(
        snackbarHost = { SnackbarHost(snackbarHostState) },
        floatingActionButton = {

            ExtendedFloatingActionButton(
                onClick = {
                    // 스낵바 실행 부분
                    scope.launch {
                        val result = snackbarHostState.showSnackbar( // 스낵바 결과 받기 가능
                            message = &quot;Snackbar # ${++counter}&quot;,
                            actionLabel = &quot;close&quot;,
                            duration = SnackbarDuration.Short
                        )

                        // 스낵바 결과
                        when(result) {
                            SnackbarResult.Dismissed -&gt; {
                                // 스낵바 닫기
                            }
                            SnackbarResult.ActionPerformed -&gt; {
                                // 동작 수행
                            }
                        }
                    }
                }
            ) { Text(&quot;Show snackbar&quot;) }
        },
        content = { innerPadding -&gt;
            Text(
                text = &quot;SnackBar 예제&quot;,
                modifier = Modifier
                    .padding(innerPadding)
                    .fillMaxSize()
                    .wrapContentSize()
            )
        }
    )
}

@Preview(showBackground = true)
@Composable
fun SnackBarPreview() {
    Compose_exampleTheme {
        SnackBarEx()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/d48e0070-024f-48b1-bd02-9eab89ab9fdc/image.png" alt=""></p>
<br>
<br>

<h2 id="▪️-snackbar를-처음-한-번-띄우기-launchedeffect">▪️ SnackBar를 처음 한 번 띄우기 (<strong><code>LaunchedEffect</code>)</strong></h2>
<ul>
<li><strong><code>LaunchedEffect</code>를 사용</strong></li>
<li><strong>아래 예제 코드에서 <code>LaunchedEffect</code>는 ( )안 key인 <code>snackbarHostState</code>가 변경되기 전 까지는 다시 호출되지 않는다.</strong></li>
<li>위 코드가 <strong><code>LaunchedEffect</code></strong>를 사용하지 않아도 가능했던 이유는 ****composition(ex 버튼 onClick내부) 내부에서 상용했기 때문이다.</li>
</ul>
<pre><code class="language-java">@Composable
fun SnackBarEx() {
    var counter by remember {
        mutableStateOf(0)
    }

    // SnackbarHostState 생성
    val snackbarHostState = remember { SnackbarHostState() }

    // coroutineScope 사용해야 함
    val scope = rememberCoroutineScope()

    Scaffold(
        snackbarHost = { SnackbarHost(snackbarHostState) },

        content = { innerPadding -&gt;
            // launch는 LaunchedEffect and composition 밖에서 사용 가능
            // snackbarHostState가 변경되기 전 까지는 다시 호출되지 않는다. 
            LaunchedEffect(key1 = snackbarHostState) {
                scope.launch {
                    val result = snackbarHostState.showSnackbar( // 스낵바 결과 받기 가능
                        message = &quot;Snackbar # ${++counter}&quot;,
                        actionLabel = &quot;close&quot;,
                        duration = SnackbarDuration.Short
                    )

                    // 스낵바 결과
                    when(result) {
                        SnackbarResult.Dismissed -&gt; {
                            // 스낵바 닫기
                        }
                        SnackbarResult.ActionPerformed -&gt; {
                            // 동작 수행
                        }
                    }
                }
            }

            Text(
                text = &quot;SnackBar 예제&quot;,
                modifier = Modifier
                    .padding(innerPadding)
                    .fillMaxSize()
                    .wrapContentSize()
            )
        }
    )
}

@Preview(showBackground = true)
@Composable
fun SnackBarPreview() {
    Compose_exampleTheme {
        SnackBarEx()
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - DropdownMenu]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-DropdownMenu</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-DropdownMenu</guid>
            <pubDate>Fri, 01 Sep 2023 01:22:19 GMT</pubDate>
            <description><![CDATA[<h3 id="dropdownmenu">DropdownMenu</h3>
<ul>
<li><code>DropdownMenu</code></li>
</ul>
<pre><code class="language-kotlin">        expanded: Boolean,
    onDismissRequest: () -&gt; Unit,
    modifier: Modifier = Modifier,
    offset: DpOffset = DpOffset(0.dp, 0.dp),
    properties: PopupProperties = PopupProperties(focusable = true),
    content: @Composable ColumnScope.() -&gt; Unit</code></pre>
<ul>
<li><code>DropdownMenuItem</code></li>
</ul>
<pre><code class="language-kotlin">        text: @Composable () -&gt; Unit,
    onClick: () -&gt; Unit,
    modifier: Modifier = Modifier,
    leadingIcon: @Composable (() -&gt; Unit)? = null,
    trailingIcon: @Composable (() -&gt; Unit)? = null,
    enabled: Boolean = true,
    colors: MenuItemColors = MenuDefaults.itemColors(),
    contentPadding: PaddingValues = MenuDefaults.DropdownMenuItemContentPadding,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() </code></pre>
<h3 id="실습">실습</h3>
<pre><code class="language-kotlin">@Composable
fun DropdownMenuEx() {
    var expandStatus by remember { mutableStateOf(false) }
    var counter by remember { mutableStateOf(0) }

    Column {
        Button(onClick = {
            expandStatus = true // 버튼 클릭시 드롭다운 메뉴 열기 
        }) {
            Text(text = &quot;DropdownMenu open&quot;)
        }

        Text(text = &quot;Counter: $counter&quot;)
    }

    DropdownMenu(
        expanded = expandStatus,
        onDismissRequest = { // 사라질때 
            expandStatus = false
        }
    ) {
        // 메뉴 안에 들어갈 값들
        DropdownMenuItem(
            text = { Text(text = &quot;plus&quot;)},
            onClick = {
                counter++
                expandStatus = false
            }
        )

        DropdownMenuItem(
            text = {Text(text = &quot;minus&quot;)},
            onClick = {
                counter--
                expandStatus = false
            }
        )
    }
}

@Preview(showBackground = true)
@Composable
fun DropdownMenuPreview() {
    Compose_exampleTheme {
        DropdownMenuEx()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/ae93fb2c-e80f-4f8d-a958-6885e0443001/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - Dialog]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Dialog</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Dialog</guid>
            <pubDate>Fri, 01 Sep 2023 01:21:10 GMT</pubDate>
            <description><![CDATA[<h2 id="dialog">Dialog</h2>
<pre><code class="language-kotlin">/*
    onDismissRequest: () -&gt; Unit,
    confirmButton: @Composable () -&gt; Unit,
    modifier: Modifier = Modifier,
    dismissButton: @Composable (() -&gt; Unit)? = null,
    icon: @Composable (() -&gt; Unit)? = null,
    title: @Composable (() -&gt; Unit)? = null,
    text: @Composable (() -&gt; Unit)? = null,
    shape: Shape = AlertDialogDefaults.shape,
    containerColor: Color = AlertDialogDefaults.containerColor,
    iconContentColor: Color = AlertDialogDefaults.iconContentColor,
    titleContentColor: Color = AlertDialogDefaults.titleContentColor,
    textContentColor: Color = AlertDialogDefaults.textContentColor,
    tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
    properties: DialogProperties = DialogProperties()
 */
@Composable
fun DialogEx() {
    var openDialog by remember { mutableStateOf(false) }
    var counter by remember { mutableStateOf(0) }

    Column {
        Button(onClick = { openDialog = true }) {
            Text(&quot;다이얼로그 열기&quot;)
        }
        Text(&quot;카운터: $counter&quot;)
    }

    if (openDialog) {
        AlertDialog(onDismissRequest = {
            // 다이얼로그를 종료
            openDialog = false
        }, confirmButton = {
            // &quot;더하기&quot; 버튼을 만들고 `counter`를 증가시키기
            Button(onClick = {
                counter++
                openDialog = false
            }) {
                Text(&quot;증가(+)&quot;)
            }
        }, dismissButton = {
            // 다이얼로그 끄기
            Button(onClick = {
                openDialog = false
            }) {
                Text(&quot;취소&quot;)
            }
        }, title = { 
            // 타이틀
            Text(text = &quot;title&quot;)
        }, text = {
            // 단계 5: 다이얼로그에서 설명할 문구를 출력합니다.
            Text(text = &quot;다이얼로그 예제 (버튼 누르면 카운터 증가)&quot;)
        })
    }
}

@Preview(showBackground = true)
@Composable
fun DialogPreView() {
    Compose_exampleTheme {
        DialogEx()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/e604d7a6-150d-4134-b20a-d3a78fc50018/image.png" alt=""></p>
<h3 id="customdialog">CustomDialog</h3>
<pre><code class="language-kotlin">@Composable
fun CustomDialogEx() {
    var openDialog by remember { mutableStateOf(false) }
    var counter by remember { mutableStateOf(0) }

    Column {
        Button(onClick = { openDialog = true }) {
            Text(&quot;다이얼로그 열기&quot;)
        }
        Text(&quot;카운터: $counter&quot;)
    }

    if (openDialog) {
        Dialog(onDismissRequest = { // onDismiss를 처리해야 다이얼로그 밖을 눌렀을때 다얼로그가 사라짐
            // Dismiss 처리
            openDialog = false
        }) {
            // Surface를 통해 다이얼로그에 들어갈 요소들을 잘 보이도록함
            Surface(shape = RoundedCornerShape(8.dp)) {
                Column (modifier = Modifier.padding(8.dp)){
                    Text(&quot;버튼 클릭 +1 / -1&quot;)
                    Row {
                        Button(onClick = {
                            counter++
                            openDialog = false
                        }) {
                            Text(text = &quot;+1&quot;)
                        }
                        Button(onClick = {
                            counter--
                            openDialog = false
                        }) {
                            Text(text = &quot;-1&quot;)
                        }
                    }
                    Button(onClick = {
                        openDialog = false
                    }) {
                        Text(text = &quot;취소&quot;)
                    }
                }
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun CustomDialogPreView() {
    Compose_exampleTheme {
        CustomDialogEx()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/39ba5253-6540-4ee4-a1b7-8cd3d919782c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - Canvas]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Canvas</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Canvas</guid>
            <pubDate>Fri, 01 Sep 2023 01:19:24 GMT</pubDate>
            <description><![CDATA[<h3 id="canvas">Canvas</h3>
<ul>
<li>Canvas는 modifier 기본값이 없다!!</li>
</ul>
<h3 id="실습">실습</h3>
<pre><code class="language-kotlin">@Composable
fun CanvasEx() {
    Canvas(modifier = Modifier.fillMaxSize()) {
        // drawLine (색, 시작위치, 끝 위치)
        drawLine(Color.Red, Offset(10f, 10f), Offset(300f, 300f), strokeWidth = 10f)

        // drawCircle(색, 반지름, 중앙 위치)
        drawCircle(Color.Blue, 300f, Offset(400f, 400f))

        // drawRect
        drawRect(Color.Magenta, Offset(250f, 250f), Size(500f, 500f))

        // 연속해서 그리기
        drawLine(Color.Black, Offset(600f, 600f), Offset(650f, 650f), strokeWidth = 10f)
        drawLine(Color.Black, Offset(650f, 650f), Offset(600f, 650f), strokeWidth = 10f)
        drawLine(Color.Black, Offset(600f, 650f), Offset(650f, 700f), strokeWidth = 10f)
    }

}

@Preview(showBackground = true)
@Composable
fun CanvasExPreview() {
    Compose_exampleTheme {
        CanvasEx()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/33a9c0db-2ac1-49b3-b8d4-c2c5a9476d59/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - ConstraintLayout으로 프로필카드 만들기]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-ConstraintLayout%EC%9C%BC%EB%A1%9C-%ED%94%84%EB%A1%9C%ED%95%84%EC%B9%B4%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-ConstraintLayout%EC%9C%BC%EB%A1%9C-%ED%94%84%EB%A1%9C%ED%95%84%EC%B9%B4%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Wed, 23 Aug 2023 16:38:13 GMT</pubDate>
            <description><![CDATA[<h3 id="실습">실습</h3>
<pre><code class="language-kotlin">@Composable
fun CardWithConstraintEx() {

    val cardData = CardData(
        imageUri = &quot;https://cdn.gjdream.com/news/photo/202308/631816_233764_323.jpg&quot;,
        imageDescription = &quot;sleeping cat&quot;,
        name = &quot;pimo&quot;,
        description = &quot;이 고양이는 꿀잠자는 중 입니다.&quot;,
    )
    Column {
        CardItem(cardData)
        CardItem(cardData)
    }
}

@Preview(showBackground = true)
@Composable
fun CardWithConstraintExPreview() {
    Compose_exampleTheme {
        CardWithConstraintEx()
    }
}

@Composable
fun CardItem(cardData: CardData) {
    val color = Color(0x33000000)

    Card(
        elevation = CardDefaults.elevatedCardElevation(8.dp),
        modifier = Modifier.padding(4.dp),
    ) {
        ConstraintLayout(
            modifier = Modifier.fillMaxWidth()
        ) {
            val (profileImg, name, description) = createRefs()

            AsyncImage(
                model = cardData.imageUri,
                placeholder = ColorPainter(color), // 이미지가 없을때 넣을 것
                contentScale = ContentScale.Crop, // 사이즈에 맞지 않은 것은 잘라냄
                contentDescription = &quot;sleeping cat&quot;,
                modifier = Modifier
                    .size(30.dp)
                    .clip(CircleShape) // 둥굴게,
                    .constrainAs(profileImg) {
                        start.linkTo(parent.start, margin = 10.dp)
                        top.linkTo(parent.top)
                        bottom.linkTo(parent.bottom)
                    }
            )

            Text(
                text = cardData.name,
                modifier = Modifier.constrainAs(name) {
                    start.linkTo(profileImg.end, margin = 10.dp)
                }
            )

            Text(
                text = cardData.description,
                modifier = Modifier.constrainAs(description) {
                    start.linkTo(profileImg.end, margin = 10.dp)
                    end.linkTo(parent.end)
                    width = Dimension.fillToConstraints // 제약만큼만
                }
            )

            val chain = createVerticalChain(
                name,
                description,
                chainStyle = ChainStyle.Packed
            )

            // 위 아래 공백 만들기 위함
            // 각 Text 위 아래 margin을 주지 못한다. chain을 만드는 순간 포함된 애들은 margin이 적용 불가
            // 그러므로 아래와 같은 방식으로 margin을 주어야 한다.
            constrain(chain) {
                top.linkTo(parent.top, margin = 8.dp)
                bottom.linkTo(parent.bottom, margin = 8.dp)
            }

        }
    }
}

data class CardData(
    val imageUri: String,
    val imageDescription: String,
    val name: String,
    val description: String,
)</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/0ac95ef5-7695-43cc-b5a7-e741919334a7/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose -  Navigation]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Navigation</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Navigation</guid>
            <pubDate>Wed, 23 Aug 2023 16:36:59 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>💡 Navigation Compose는 Compose 배포 버전과 동일하게 업데이트 되지 않습니다. 
때문에, <a href="https://developer.android.com/jetpack/compose/navigation?hl=ko">https://developer.android.com/jetpack/compose/navigation?hl=ko</a> 에서 Navigation 버전을 확인하시고 설정을 진행해야 합니다.</p>
</blockquote>
<h3 id="설정">설정</h3>
<ul>
<li>버전확인!</li>
</ul>
<pre><code class="language-kotlin">dependencies {
    val nav_version = &quot;2.5.3&quot;

    implementation(&quot;androidx.navigation:navigation-compose:$nav_version&quot;)
}</code></pre>
<pre><code class="language-kotlin">dependencies {
    implementation(&quot;androidx.navigation:navigation-compose:2.5.3&quot;)
}</code></pre>
<h3 id="▪️popupto">▪️<code>popUpTo</code></h3>
<pre><code class="language-kotlin">// room1으로 이동하기 전 home을 제외한 나머지 스택을 모두 제거 후 room1으로 이동 
navController.navigate(&quot;room1&quot;) {
    popUpTo(&quot;home&quot;) 
}</code></pre>
<ul>
<li><p>Home, Room1, Room2가 있다고 가정</p>
</li>
<li><p>위 코드를 보면 홈으로 이동시 <code>popUpTo</code>를 지정</p>
</li>
<li><p><code>popUpTo</code>는 <code>Home → Room1</code>으로 이동후  <code>Room1 → Room2</code>로 이동하면 <code>Room1</code>은 사라짐
(상태 <code>Home → Room2</code>)</p>
</li>
<li><p>즉, Home과 이동하려는 곳 사이를 제거한다.</p>
</li>
<li><p><code>inclusive</code></p>
<pre><code class="language-kotlin">  navController.navigate(&quot;Home&quot;) {
                          popUpTo(&quot;Home&quot;) {
                              inclusive = true
                          }
                      }</code></pre>
<ul>
<li><code>inclusive = ture</code>를 사용하면 Home까지 제거를 한다.</li>
<li><strong>ex) 동작 방식</strong></li>
</ul>
<ol>
<li>처음 <code>Home → Room1</code> 으로 이동시 Home이 제거된다. (상태 : Room1) </li>
<li><code>Room1 → Room2</code>로 이동시에는 popUpto가 동작하지 않는다.  (상태 <code>Room1 → Room2</code>)</li>
<li>그리고 다시 Home으로 이동하면 <code>Room1 → Room2 → Home</code>으로 앞에 Home이 없어 아무것도 제거되지 않는다. </li>
<li>여기서 Room1으로 이동하면 <code>Room1 → Room2 → Room1</code>으로 Home이 제거된다.</li>
</ol>
</li>
</ul>
<aside>
💡 `inclusive`는 언제 주로 사용할까?
만약 Home에서 Login이 성공하면 광고 화면으로 이동한다고 가정하자. 
이때 Login으로 이동시 `inclusive를 적용한 popUpTo`를 사용하면 로그인 성공 후 광고화면에서 뒤로가기를 하면 Home 화면으로 이동할 것이다.

</aside>

<h3 id="▪️launchsingletop">▪️<code>launchSingleTop</code></h3>
<ul>
<li><code>launchSingleTop</code>: 스택 최상단에 올라가 있으면, 스택에 쌓지 않음</li>
</ul>
<pre><code class="language-kotlin">navController.navigate(&quot;Home&quot;) {
                        launchSingleTop = true
                    }</code></pre>
<ul>
<li>Home에서 Home으로 이동한다고 가정</li>
<li><code>launchSingleTop</code>을 사용하지 않으면 Home에서 Home으로 이동하는 버튼을 클릭시 스택에 Home이 여러개 쌓임</li>
<li><code>launchSingleTop</code>을 사용하면 Home을 여러번 눌러도 이미 Home이 있기 때문에 더이상 쌓이지 않음</li>
</ul>
<h3 id="▪️-argument-nav">▪️ Argument Nav</h3>
<ul>
<li>버튼 클리깃 <code>Argument/dong</code>을 전달</li>
<li>해당 composable은 전달받은 {userId}로 이동</li>
</ul>
<pre><code class="language-kotlin">@Composable
fun Navigation(
    modifier: Modifier = Modifier,
    navController: NavHostController = rememberNavController(),
) {
    // NavHost 생성
    // 컨트롤러, 목적지, modifier
    NavHost(navController, &quot;Home&quot;, modifier = modifier) {
        // 라우터 설정
        composable(&quot;Home&quot;) {
            Column {
                // argument nav
                Button(onClick = {
                    navController.navigate(&quot;Argument/dong&quot;) {
                        launchSingleTop = true
                    }
                }) {
                    Text(&quot;userId 입력값 으로 연결 이동&quot;)
                }
            }
        }

        composable(&quot;Argument/{userId}&quot;) {
            val userId = it.arguments?.get(&quot;userId&quot;)
            Text(text = &quot;userId: $userId&quot;)
        }
    }
}</code></pre>
<h3 id="전체-코드">전체 코드</h3>
<pre><code class="language-kotlin">import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.compose_example.ui.theme.Compose_exampleTheme

class NavActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Compose_exampleTheme {
                // A surface container using the &#39;background&#39; color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background,
                ) {
                    Navigation()
//                    Greeting(&quot;Android&quot;)
                }
            }
        }
    }
}

/**
 * Navigation 이동은 stack이 쌓인다.
 */
@Composable
fun Navigation(
    modifier: Modifier = Modifier,
    navController: NavHostController = rememberNavController(),
) {
    // NavHost 생성
    // 컨트롤러, 목적지, modifier
    NavHost(navController, &quot;Home&quot;, modifier = modifier) {
        // 라우터 설정
        composable(&quot;Home&quot;) {
            Column {
                Text(text = &quot;Home&quot;)
                Button(onClick = {
                    navController.navigate(&quot;room1&quot;)
                }) {
                    Text(&quot;room1 이동&quot;)
                }
                Button(onClick = {
                    navController.navigate(&quot;room2&quot;)
                }) {
                    Text(&quot;room2 이동&quot;)
                }
                Button(onClick = {
                    navController.navigate(&quot;Home&quot;) {
                        launchSingleTop = true
                    }
                }) {
                    Text(&quot;Home으로 이동&quot;)
                }

                // argument nav
                Button(onClick = {
                    navController.navigate(&quot;Argument/dong&quot;) {
                        launchSingleTop = true
                    }
                }) {
                    Text(&quot;userId 입력값 으로 연결 이동&quot;)
                }
            }
        }

        composable(&quot;room1&quot;) {
            Column {
                Text(text = &quot;room1&quot;)
                Button(onClick = {
                    navController.navigate(&quot;Home&quot;)
                }) {
                    Text(&quot;Home으로 이동&quot;)
                }
                Button(onClick = {
                    navController.navigate(&quot;room2&quot;)
                }) {
                    Text(&quot;room2 이동&quot;)
                }
            }
        }

        composable(&quot;room2&quot;) {
            Column {
                Text(text = &quot;room2&quot;)
                Button(onClick = {
                    navController.navigate(&quot;Home&quot;)
                }) {
                    Text(&quot;Home으로 이동&quot;)
                }
                Button(onClick = {
                    navController.navigate(&quot;room1&quot;)
                }) {
                    Text(&quot;room1 이동&quot;)
                }
            }
        }

        composable(&quot;Argument/{userId}&quot;) {
            val userId = it.arguments?.get(&quot;userId&quot;)
            Text(text = &quot;userId: $userId&quot;)
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - ConstraintSet]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-ConstraintSet</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-ConstraintSet</guid>
            <pubDate>Wed, 23 Aug 2023 16:35:45 GMT</pubDate>
            <description><![CDATA[<h3 id="constraintset">ConstraintSet</h3>
<p>이 전 ConstraintLayout편은 뷰들 각각을 직접 modifier에 제약을 지정했지만, ConstraintSet을 사용하여 Constraint을 밖으로 빼내 별도로 지정하도록 할 수 있다. </p>
<ol>
<li>이 전에는 ConstraintLayout에서 만들었던 레퍼런스들을 ConstraintSet에서 만든다. </li>
<li>ConstraintSet에 각 레퍼런스에 대한 제약조건을 지정한다. </li>
<li>ConstraintLayout의 첫 번째 인자로 constraintSet을 지정하고, 각각의 뷰에 ConstraintSet에서 만든 각 레퍼런스를 .layoutId()로 지정한다. </li>
</ol>
<pre><code class="language-kotlin">@Composable
fun ConstraintSetEx() {
    val constraintSet = ConstraintSet {
        // 레퍼런스 생성
        val redBox = createRefFor(&quot;redBox&quot;)
        val magentaBox = createRefFor(&quot;magentaBox&quot;)
        val greenBox = createRefFor(&quot;greenBox&quot;)
        val yellowBox = createRefFor(&quot;yellowBox&quot;)

        // 제약 지정
        constrain(redBox) {
            bottom.linkTo(parent.bottom, margin = 8.dp)
            end.linkTo(parent.end, margin = 4.dp)
        }

        constrain(magentaBox) {
            start.linkTo(parent.start)
            end.linkTo(parent.end)
        }

        constrain(greenBox) {
            centerTo(parent) // 정 중앙
        }

        constrain(yellowBox) {
            top.linkTo(magentaBox.bottom)
            start.linkTo(magentaBox.end)
        }
    }

    ConstraintLayout(
        constraintSet,
        modifier = Modifier.fillMaxSize(),
    ) {
        Box(
            modifier = Modifier.size(40.dp)
                .background(Color.Red)
                .layoutId(&quot;redBox&quot;),
        )

        Box(
            modifier = Modifier.size(40.dp)
                .background(Color.Magenta)
                .layoutId(&quot;magentaBox&quot;),
        )

        Box(
            modifier = Modifier.size(40.dp)
                .background(Color.Green)
                .layoutId(&quot;greenBox&quot;),
        )

        Box(
            modifier = Modifier.size(40.dp)
                .background(Color.Yellow)
                .layoutId(&quot;yellowBox&quot;),
        )
    }
}

@Preview(showBackground = true)
@Composable
fun ConstraintSetPreview() {
    Compose_exampleTheme {
        ConstraintLayoutEx()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/b95e81e4-2583-499d-b6d6-111f5c8848bd/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - ConstraintLayout]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-ConstraintLayout</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-ConstraintLayout</guid>
            <pubDate>Wed, 23 Aug 2023 16:34:33 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://developer.android.com/jetpack/compose/layouts/constraintlayout?hl=ko">Compose의 ConstraintLayout  |  Jetpack Compose  |  Android Developers</a></p>
</blockquote>
<h3 id="설정">설정</h3>
<ul>
<li>Build.gradle(app)</li>
</ul>
<pre><code class="language-kotlin">implementation(&quot;androidx.constraintlayout:constraintlayout-compose:1.0.1&quot;)</code></pre>
<h3 id="실습">실습</h3>
<pre><code class="language-kotlin">import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.navigation.NavController
import com.example.compose_example.ui.theme.Compose_exampleTheme

@Composable
fun ConstraintLayoutScreen(
    navController: NavController,
) {
    ConstraintLayoutEx()
}

@Composable
fun ConstraintLayoutEx() {
    ConstraintLayout(
        modifier = Modifier.fillMaxSize(),
    ) {
        // createRefs는 하나에 최대 16개의 컴포넌트
        // 만약 17개 이상 사용해야 한다면 createRefs를 추가로 생성하면 됨
        // xml로 비유하면 각 뷰들의 id라고 볼 수 있다.
        var (redBox, magentaBox, greenBox, yellowBox) = createRefs()

        /*
            linkTo
            (bottom, start, end, top).linkTo
            margin: Dp = 0.dp,
            goneMargin: Dp = 0.dp

            centerTo
            centerVerticallyTo
            centerHorizontallyTo
         */

        Box(
            modifier = Modifier.size(40.dp)
                .background(Color.Red)
                .constrainAs(redBox) {
                    // 박스 위치시키기
                    bottom.linkTo(parent.bottom, margin = 8.dp)
                    end.linkTo(parent.end, margin = 4.dp)
                },
        )

        Box(
            modifier = Modifier.size(40.dp)
                .background(Color.Magenta)
                .constrainAs(magentaBox) {
                    start.linkTo(parent.start)
                    end.linkTo(parent.end)
                },
        )

        Box(
            modifier = Modifier.size(40.dp)
                .background(Color.Green)
                .constrainAs(greenBox) {
//                    start.linkTo(parent.start)
//                    end.linkTo(parent.end)
//                    top.linkTo(parent.top)
//                    bottom.linkTo(parent.bottom)
                                        // 위 4줄 주석과 동일한 방법 
                    centerTo(parent) // 정 중앙
                },
        )

        Box(
            modifier = Modifier.size(40.dp)
                .background(Color.Yellow)
                .constrainAs(yellowBox) {
                    top.linkTo(magentaBox.bottom)
                    start.linkTo(magentaBox.end)
                },
        )
    }
}

@Preview(showBackground = true)
@Composable
fun ConstraintLayoutExPreview() {
    Compose_exampleTheme {
        ConstraintLayoutEx()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/6a78bcab-5650-44b7-a995-56316e35c442/image.png" alt=""></p>
<br/>
<br/>


<h2 id="chain--barrier">Chain &amp; Barrier</h2>
<pre><code class="language-kotlin">@Composable
fun ConstraintLayoutChainWithBarrierEx() {
    ConstraintLayout(
        modifier = Modifier.fillMaxSize(),
    ) {
        var (redBox, magentaBox, greenBox, testText) = createRefs()

        Box(
            modifier = Modifier.size(40.dp)
                .background(Color.Red)
                .constrainAs(redBox) {
                    top.linkTo(parent.top, margin = 20.dp)
                },
        )

        Box(
            modifier = Modifier.size(40.dp)
                .background(Color.Magenta)
                .constrainAs(magentaBox) {
                    top.linkTo(parent.top, margin = 30.dp)
                },
        )

        Box(
            modifier = Modifier.size(40.dp)
                .background(Color.Green)
                .constrainAs(greenBox) {
                    top.linkTo(parent.top, margin = 40.dp)
                },
        )

        /**
         * Chain
         */
        // 1. createVerticalChain
//        createVerticalChain(redBox, magentaBox, greenBox)

        // 2. createHorizontalChain
//        createHorizontalChain(redBox, magentaBox, greenBox)

        // 3. chainStyle
        // packed: 뭉침
        // spread: 펼치기
        // SpreadInside: 레퍼런스 사이만 펼치기
//        createVerticalChain(redBox, magentaBox, greenBox, chainStyle = ChainStyle.SpreadInside)
        createHorizontalChain(redBox, magentaBox, greenBox, chainStyle = ChainStyle.SpreadInside)

        /**
         * Barrier
         * create(Bottom, Start, End, Top)Barrier
         *
         * 주의!
         * ex)
         * barrier를 end로 지정하고 linkTo를 top으로 할 수 없다.
         */
        // 1.createBottomBarrier
        // 레퍼런스들의 아래쪽에 Barrier 만들기
        val bottomBarrier = createBottomBarrier(redBox, magentaBox, greenBox)

        Text(
            text = &quot;레퍼런스들의 아래쪽에 Barrier 만들기 / 레퍼런스들의 아래쪽에 Barrier 만들기 / 레퍼런스들의 아래쪽에 Barrier 만들기&quot;,
            modifier = Modifier.constrainAs(testText) {
                top.linkTo(bottomBarrier)
            }
        )
    }
}

@Preview(showBackground = true)
@Composable
fun ConstraintLayoutChainWithBarrierExPreview() {
    Compose_exampleTheme {
        ConstraintLayoutChainWithBarrierEx()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/c4fd60b1-ca6e-4b41-bcda-5db50aaeaba5/image.png" alt="">
<img src="https://velog.velcdn.com/images/dong-hyeok/post/b1c2a724-2b20-416f-a238-4f9fc12e1afe/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - Simple 카탈로그 만들기]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Simple-%EC%B9%B4%ED%83%88%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Simple-%EC%B9%B4%ED%83%88%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Wed, 23 Aug 2023 16:31:48 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-kotlin">import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.compose_example.ui.theme.Compose_exampleTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Compose_exampleTheme {
                // A surface container using the &#39;background&#39; color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Catalog(items)
                }
            }
        }
    }
}

@Composable
fun CatalogItem(itemData: ItemData) {
    // 스텝 1: catalog1.png를 참고해 카드 레이아웃을 완성하세요.
    Card(
        elevation =  CardDefaults.cardElevation(8.dp),
        modifier = Modifier.padding(16.dp),
    ) {
        Column (
          modifier = Modifier.padding(8.dp)
        ) {
            Image(
                painter = painterResource(id = itemData.imageId),
                contentDescription = itemData.title
            )
            Spacer(modifier = Modifier.size(8.dp))

            Text(text = itemData.title, style = MaterialTheme.typography.headlineMedium)

            Spacer(modifier = Modifier.size(8.dp))

            Text(text = itemData.description)
        }
    }
}

@Preview(showBackground = true)
@Composable
fun ItemPreview() {
    Compose_exampleTheme {
        CatalogItem(
            ItemData(
                imageId = R.drawable.a1,
                title = &quot;스테이크&quot;,
                description = &quot;고기를 고열로 구워내어 외부는 바삭하고 내부는 부드러운 고기 요리입니다. 소금과 후추로 간을 해서 고기의 풍미를 살려보세요.&quot;
            ),
        )
    }
}

@Composable
fun Catalog(itemList: List&lt;ItemData&gt;) {
    LazyColumn {
        // LazyItemScope는 각 항목(아이템)에 대한 Scopoe
        items(itemList) {item -&gt;
            CatalogItem(item)
        }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    Compose_exampleTheme {
        Catalog(items)
    }
}

data class ItemData(
    @DrawableRes val imageId: Int,
    val title: String,
    val description: String,
)

val items = listOf(
    ItemData(
        imageId = R.drawable.a1,
        title = &quot;스테이크&quot;,
        description = &quot;고기를 고열로 구워내어 외부는 바삭하고 내부는 부드러운 고기 요리입니다. 소금과 후추로 간을 해서 고기의 풍미를 살려보세요.&quot;
    ),
    ItemData(
        imageId = R.drawable.a2,
        title = &quot;스시&quot;,
        description = &quot;신선한 생선과 쌀로 만든 밥을 함께 먹는 일본의 대표적인 음식입니다. 간장과 와사비, 생강을 곁들여 드세요.&quot;
    ),
    ItemData(
        imageId = R.drawable.a3,
        title = &quot;파스타&quot;,
        description = &quot;면과 소스를 함께 끓여내어 어우러진 이탈리아의 대표 음식입니다. 알 따라서 다양한 종류의 파스타와 소스를 시도해보세요.&quot;
    ),
    ItemData(
        imageId = R.drawable.a4,
        title = &quot;피자&quot;,
        description = &quot;바삭한 반죽 위에 토마토 소스와 치즈, 다양한 토핑을 얹어 구워내는 이탈리아의 대표 음식입니다. 오븐에서 구워내어 즐겨보세요.&quot;
    ),
    ItemData(
        imageId = R.drawable.a5,
        title = &quot;초밥&quot;,
        description = &quot;생선과 다양한 재료를 살짝 얹은 쌀밥 위에 싸먹는 일본의 전통 음식입니다. 신선한 재료와 간장을 묻혀 드세요.&quot;
    )
)</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/2bd52e5c-3798-41dd-a0ec-43073349151d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - Slot API]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Slot-API</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Slot-API</guid>
            <pubDate>Wed, 23 Aug 2023 16:30:46 GMT</pubDate>
            <description><![CDATA[<h3 id="slot-api란">Slot API란?</h3>
<p>어떠한 Composable 함수가 다른 Composable 함수나 Component를 포함하는 것을 Slot API라고 한다. (ex Raw, Column 등등)</p>
<aside>
💡 Composable 함수는 대문자로 시작!!

</aside>

<h3 id="slot-api-예시">Slot API 예시</h3>
<ul>
<li><p>Slot API를 만들때 마지막 파라미터에 content를 만드는게 편하다. 
→ 마지막 인자를 { }에 옮겨 쓸 수 있기 때문.</p>
</li>
<li><p>아래 코드를 보면 CheckboxItemSolt에 인자로 content를 전달 받는다. 
이 content는 RowScope로 지정했다. 
따라서 CheckboxItemSolt에 보낼 content는 RowScope내 속성을 추가하여 사용할 수 있다.</p>
</li>
<li><p>RowScope로 지정했기 때문에 Text의 align을 CenterVertically로 지정했다.</p>
</li>
</ul>
<pre><code class="language-kotlin">CheckboxItemSolt(checked1) {
     // RowScopoe 범위에 있기 때문에 align을 CenterVertically로 지정할 수 있다.
     Text(&quot;체크박스 1&quot;, modifier = Modifier.align(Alignment.CenterVertically))
}</code></pre>
<pre><code class="language-kotlin">@Composable
fun CheckboxItemSolt(
    checked: MutableState&lt;Boolean&gt;,
    content: @Composable RowScope.() -&gt; Unit, // 람다를 RowScope로 세팅하여 RowScope내에서 사용하는 것으로 지정할 수 있음
) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        modifier = Modifier.clickable {
            checked.value = !checked.value
        },
    ) {
        Checkbox(
            checked = checked.value,
            onCheckedChange = { checked.value = it },
        )
        content()
    }
}

@Composable
fun SlotEx() {
    val checked1 = remember { mutableStateOf(false) }
    val checked2 = remember { mutableStateOf(false) }

    Column {
        CheckboxItemSolt(checked1) {
            // RowScopoe 범위에 있기 때문에 align을 CenterVertically로 지정할 수 있다.
            Text(&quot;체크박스 1&quot;, modifier = Modifier.align(Alignment.CenterVertically))
        }
        CheckboxItemSolt(checked2) {
            Text(&quot;체크박스 2&quot;)
        }
    }
}</code></pre>
<h3 id="checkboxitemsolt안에서-체크박스-상태를-변경을-외부로-옮기기">CheckboxItemSolt안에서 체크박스 상태를 변경을 외부로 옮기기</h3>
<ul>
<li><strong>위임할 수 있는 것은 밖으로 위임하는 것이 좋다!!</strong></li>
<li>아래 코드를 살펴보면 onCheckedChanged를 외부로 위임했다.</li>
</ul>
<pre><code class="language-kotlin">@Composable
fun CheckboxItemSolt(
    checked: Boolean, // 변경하는 용도로만 사용
    onCheckedChanged: () -&gt; Unit, // 체크박스의 상태
    content: @Composable RowScope.() -&gt; Unit, // 람다를 RowScope로 세팅하여 RowScope내에서 사용하는 것으로 지정할 수 있음
) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        modifier = Modifier.clickable {
            onCheckedChanged()
        },
    ) {
        Checkbox(
            checked = checked,
            onCheckedChange = { onCheckedChanged() },
        )
        content()
    }
}

@Composable
fun SlotEx() {
    var checked1 by remember { mutableStateOf(false) }
    var checked2 by remember { mutableStateOf(false) }

    Column {
        CheckboxItemSolt(
            checked = checked1,
            onCheckedChanged = { checked1 = !checked1 },
        ) {
            // RowScopoe 범위에 있기 때문에 align을 CenterVertically로 지정할 수 있다.
            Text(&quot;체크박스 1&quot;, modifier = Modifier.align(Alignment.CenterVertically))
        }
        CheckboxItemSolt(
            checked = checked2,
            onCheckedChanged = { checked2 = !checked2 },
        ) {
            // RowScopoe 범위에 있기 때문에 align을 CenterVertically로 지정할 수 있다.
            Text(&quot;체크박스 2&quot;, modifier = Modifier.align(Alignment.CenterVertically))
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - TopAppBar]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-TopAppBar-1gjsezrz</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-TopAppBar-1gjsezrz</guid>
            <pubDate>Wed, 23 Aug 2023 16:29:48 GMT</pubDate>
            <description><![CDATA[<h3 id="일반적인-topappbar">일반적인 TopAppBar</h3>
<ul>
<li>navigationIcon 부분이 왼쪽 아이콘 배치</li>
<li>actions 부분이 오른쪽 아이콘 배치</li>
</ul>
<pre><code class="language-kotlin">TopAppBar(
            title = { Text(&quot;TopAppBar&quot;) },
            navigationIcon = {
                IconButton(onClick = { /*TODO*/ }) {
                    Icon(
                        imageVector = Icons.Filled.ArrowBack,
                        contentDescription = &quot;뒤로가기&quot;
                    )
                }
            },
            actions = {
                IconButton(onClick = { /*TODO*/ }) {
                    Icon(
                        imageVector = Icons.Filled.Search,
                        contentDescription = &quot;검색&quot;
                    )
                }
                IconButton(onClick = { /*TODO*/ }) {
                    Icon(
                        imageVector = Icons.Filled.Settings,
                        contentDescription = &quot;설정&quot;
                    )
                }
                IconButton(onClick = { /*TODO*/ }) {
                    Icon(
                        imageVector = Icons.Filled.AccountCircle,
                        contentDescription = &quot;계정&quot;
                    )
                }
            },
        )</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/b5554337-3fed-4333-b803-3a27578283ea/image.png" alt=""></p>
<h3 id="직접-커스텀-하는-topappbar">직접 커스텀 하는 TopAppBar</h3>
<ul>
<li>직접 간격과 같은 요소들을 지정해 주어야 한다.</li>
</ul>
<pre><code class="language-kotlin">TopAppBar {
            IconButton(onClick = { /*TODO*/ }) {
                Icon(
                    imageVector = Icons.Filled.ArrowBack,
                    contentDescription = &quot;뒤로가기&quot;
                )
            }

            Text(text = &quot;TopAppBar&quot;, modifier = Modifier.weight(1f))

            IconButton(onClick = { /*TODO*/ }) {
                Icon(
                    imageVector = Icons.Filled.Search,
                    contentDescription = &quot;검색&quot;
                )
            }
            IconButton(onClick = { /*TODO*/ }) {
                Icon(
                    imageVector = Icons.Filled.Settings,
                    contentDescription = &quot;설정&quot;
                )
            }
            IconButton(onClick = { /*TODO*/ }) {
                Icon(
                    imageVector = Icons.Filled.AccountCircle,
                    contentDescription = &quot;계정&quot;
                )
            }
        }</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/ec4f09f2-73c8-4529-b770-e4c0b94b3081/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - 프로필카드 만들기 ]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-%ED%94%84%EB%A1%9C%ED%95%84%EC%B9%B4%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-%ED%94%84%EB%A1%9C%ED%95%84%EC%B9%B4%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Tue, 22 Aug 2023 13:24:01 GMT</pubDate>
            <description><![CDATA[<h3 id="설정">설정</h3>
<ul>
<li>설정은 Image편을 참고</li>
</ul>
<h3 id="구현">구현</h3>
<pre><code class="language-kotlin">import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import kr.co.fastcampus.part1.chapter3_12.ui.theme.CardTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CardTheme {
                // A surface container using the &#39;background&#39; color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background,
                ) {
                    Column {
                        CardItem(cardData)
                        CardItem(cardData)
                    }
                }
            }
        }
    }

    companion object {
        val cardData = CardData(
            imageUri = &quot;https://cdn.gjdream.com/news/photo/202308/631816_233764_323.jpg&quot;,
            imageDescription = &quot;sleeping cat&quot;,
            name = &quot;pimo&quot;,
            description = &quot;이 고양이는 꿀잠자는 중 입니다.&quot;,
        )
    }
}

@Composable
fun CardItem(cardData: CardData) {
    val color = Color(0x33000000)

    Card(
        elevation = 8.dp,
        modifier = Modifier.padding(4.dp),
    ) {
        Row(
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier.padding(8.dp).fillMaxWidth(),

        ) {
            AsyncImage(
                model = cardData.imageUri,
                placeholder = ColorPainter(color), // 이미지가 없을때 넣을 것
                contentScale = ContentScale.Crop, // 사이즈에 맞지 않은 것은 잘라냄
                contentDescription = &quot;sleeping cat&quot;,
                modifier = Modifier.size(30.dp).clip(CircleShape), // 둥굴게,
            )

            Spacer(modifier = Modifier.size(8.dp))

            Column {
                Text(
                    text = cardData.name,
                )

                Spacer(modifier = Modifier.size(4.dp))

                Text(
                    text = cardData.description,
                )
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    CardTheme {
        Row {
            CardItem(MainActivity.cardData)
        }
    }
}

data class CardData(
    val imageUri: String,
    val imageDescription: String,
    val name: String,
    val description: String,
)</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/fb88c1e6-40fc-4d23-9945-560cd0669fb9/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - Image]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Image</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Image</guid>
            <pubDate>Tue, 22 Aug 2023 02:10:15 GMT</pubDate>
            <description><![CDATA[<h3 id="image의-속성"><strong>Image의 속성</strong></h3>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/b5e4148e-68fd-427f-b933-300a045001bb/image.png" alt=""></p>
<h3 id="예제">예제</h3>
<pre><code class="language-kotlin">@Composable
fun Greeting() {
    Column {
        // painter
        Image(
            painter = painterResource(id = R.drawable.cat1),
            contentDescription = &quot;image1&quot;,
        )

        // imageVector
        Image(
            imageVector = Icons.Filled.Search,
            contentDescription = &quot;image2&quot;)

        // bitmap은 컴포저블 함수 내에서 진행할 수 없다. 
        // 추후에 학습할 예정
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/d5eb49eb-a316-4c36-9148-4b40ea24e09e/image.png" alt=""></p>
<hr>
<br/>
<br/>

<h2 id="network-image">Network Image</h2>
<h3 id="의존성-추가-coil-라이브러리-사용">의존성 추가 (coil 라이브러리 사용)</h3>
<ul>
<li>build.gradle(app)</li>
</ul>
<pre><code class="language-kotlin">dependencies {

    // coil 의존성을 추가
    implementation &#39;io.coil-kt:coil:2.2.2&#39; 
    implementation &#39;io.coil-kt:coil-compose:2.2.2&#39; 
}</code></pre>
<ul>
<li>manifest</li>
</ul>
<pre><code class="language-kotlin">&lt;uses-permission android:name=&quot;android.permission.INTERNET&quot;/&gt;</code></pre>
<ul>
<li><strong>방법 1</strong></li>
</ul>
<pre><code class="language-kotlin">        // rememberImagePainter (Compose 한국어 문서의 추천, but Deprecated)
    val painter = rememberImagePainter(
        data = &quot;https://img.freepik.com/free-photo/adorable-kitty-looking-like-it-want-to-hunt_23-2149167099.jpg?w=2000&quot;,
    )

    Image(
        painter = painter,
        contentDescription = &quot;cat image&quot;,
    )</code></pre>
<ul>
<li><strong>방법 2</strong></li>
</ul>
<pre><code class="language-kotlin">        // AsyncImage
    AsyncImage(
        model = &quot;https://img.freepik.com/free-photo/adorable-kitty-looking-like-it-want-to-hunt_23-2149167099.jpg?w=2000&quot;,
        contentDescription = &quot;cat image&quot;
    )</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/f1a28f73-8767-4f94-9dbb-f5fe56ba7f91/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - Surface]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Surface</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-Surface</guid>
            <pubDate>Tue, 22 Aug 2023 01:58:31 GMT</pubDate>
            <description><![CDATA[<h3 id="surface">Surface</h3>
<p>Surface는 안드로이드 컴포즈에서 화면에 그리는 기본적인 단위입니다. Surface는 렌더링 가능한 영역을 나타내며, 여기에 다양한 컴포즈 요소들을 배치하고 그릴 수 있습니다. 간단하게 말해, 화면의 일부분을 나타내는 컴포즈 요소라고 생각할 수 있습니다.</p>
<h3 id="속성">속성</h3>
<pre><code class="language-kotlin">        modifier: Modifier = Modifier,
    shape: Shape = RectangleShape,
    color: Color = MaterialTheme.colors.surface,
    contentColor: Color = contentColorFor(color),
    border: BorderStroke? = null,
    elevation: Dp = 0.dp,
    content: @Composable () -&gt; Unit</code></pre>
<h3 id="1-text가-margine을-가져야-한다면">1. Text가 Margine을 가져야 한다면?</h3>
<ul>
<li>text에 Margine을 주는 것이 아닌 Surface에 text를 넣고 Surface에 padding을 부여하는 것이 성능적으로 더 좋다고 한다. 
(Text에도 padding을 같이 부여해서 사용할 수 있다.)</li>
</ul>
<pre><code class="language-kotlin">Surface(
        modifier = Modifier.padding(5.dp)
    ) {
        Text(
            text = &quot;Hello $name!&quot;,
            modifier = Modifier.padding(8.dp)
        )
    }</code></pre>
<h3 id="2-간단한-예제">2. 간단한 예제</h3>
<pre><code class="language-kotlin">Surface(
        border = BorderStroke(width = 2.dp, color = Color.Blue), // 테두리
        modifier = Modifier.padding(5.dp),
        elevation = 10.dp,  // 그림자
        shape = CircleShape,
        color = MaterialTheme.colors.secondary
    ) {
        Text(
            text = &quot;Hello $name!&quot;,
            modifier = Modifier.padding(8.dp)
        )
    }</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/a3369603-746d-456d-84e7-7833ca7ce13f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - TextField]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-TextField</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-TextField</guid>
            <pubDate>Mon, 21 Aug 2023 17:11:01 GMT</pubDate>
            <description><![CDATA[<h2 id="textfield">TextField</h2>
<pre><code class="language-kotlin">import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Email
import androidx.compose.material.icons.filled.Person

import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.dong.ui.theme.MyComposeStudyTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyComposeStudyTheme {
                // A surface container using the &#39;background&#39; color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    textFieldContainer()
                }
            }
        }
    }
}
/*
    value: TextFieldValue,
    onValueChange: (TextFieldValue) -&gt; Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    readOnly: Boolean = false,
    textStyle: TextStyle = LocalTextStyle.current,
    label: @Composable (() -&gt; Unit)? = null,(입력란 위에 표시되는 레이블)
    placeholder: @Composable (() -&gt; Unit)? = null, (입력란에 힌트로 표시되는 텍스트)
    leadingIcon: @Composable (() -&gt; Unit)? = null,  (왼쪽에 아이콘 지정)
    trailingIcon: @Composable (() -&gt; Unit)? = null,  (오른쪽에 아이콘 지정)
    isError: Boolean = false,
    visualTransformation: VisualTransformation = VisualTransformation.None,
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
    keyboardActions: KeyboardActions = KeyboardActions(),
    singleLine: Boolean = false,
    maxLines: Int = Int.MAX_VALUE,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    shape: Shape = TextFieldDefaults.TextFieldShape,
    colors: TextFieldColors = TextFieldDefaults.textFieldColors()
 */

@Composable
fun textFieldContainer(){

    // 글자의 상태를 저장
    var userInput by remember {
        mutableStateOf(TextFieldValue())
    }

    var phoneNumberInput by remember {
        mutableStateOf(TextFieldValue())
    }

    var passwordInput by remember {
        mutableStateOf(TextFieldValue())
    }

    // 비밀번호 입력 textField에 우측 버튼을 누르면 입력한 내용이 보이고 안보이게 하기 위해
    var shouldShowPassword by remember {
        mutableStateOf(false)
    }

    // shouldShowPassword의 값에 따라 이미지를 지정
    val passwordResource : (Boolean) -&gt; Int = {
        if(it) { // true
            R.drawable.baseline_visibility_24
        }else{
            R.drawable.baseline_visibility_off_24
        }
    }

    Column(
        Modifier
            .padding(16.dp)
            .fillMaxWidth()
        , verticalArrangement = Arrangement.SpaceEvenly
    ){
        // singleLine 지정ㅇㅇㅇㅇㅇ
        TextField(
            modifier = Modifier.fillMaxWidth(),
            value = userInput,
            singleLine = true, // 한 줄만 작성할 수 있도록
            onValueChange = { userInput = it }, // 유저가 입력한 값(it)을 remember에 저장
            label = { Text(&quot;singleLine&quot;) },
            placeholder = { Text(&quot;텍스트를 작성해주세요.&quot;)}
        )

        // maxLine 지정 
        TextField(
            modifier = Modifier.fillMaxWidth(),
            value = userInput,
            maxLines = 3,  // 최대 line수 지정
            onValueChange = { userInput = it },
            label = { Text(&quot;maxLine&quot;) },
            placeholder = { Text(&quot;텍스트를 작성해주세요.&quot;)}
        )

        // outLinedTextField
        // keyboard 옵션 지정 - number
        OutlinedTextField(
            modifier = Modifier.fillMaxWidth(),
            value = phoneNumberInput,
            singleLine = true,
            keyboardOptions = KeyboardOptions(keyboardType =  KeyboardType.Phone), // 키보드 타입 지정
            onValueChange = { phoneNumberInput = it },
            label = { Text(&quot;OutlinedTextField(Number)&quot;) },
            placeholder = { Text(&quot;010-****-****&quot;)}
        )

        // outLinedTextField
        // keyboard 옵션 지정 - email
        // leadingIcon 적용 (TextField 왼쪽 아이콘)
        // trailingIcon 적용 (TextField 오른쪽 아이콘)
        OutlinedTextField(
            modifier = Modifier.fillMaxWidth(),
            value = phoneNumberInput,
            singleLine = true,
            leadingIcon = { Icon(  // 왼쪽 Icon 지정
                imageVector = Icons.Default.Email
                , contentDescription = null
            )},
            trailingIcon = { Icon( // 오른쪽에 Icon 지정
                imageVector = Icons.Default.CheckCircle
                , contentDescription = null
            )},
            keyboardOptions = KeyboardOptions(keyboardType =  KeyboardType.Email), // 키보드 타입 지정
            onValueChange = { phoneNumberInput = it },
            label = { Text(&quot;OutlinedTextFiel(email))&quot;) },
            placeholder = { Text(&quot; @ .com&quot;)}
        )

        // Button 추가
        OutlinedTextField(
            modifier = Modifier.fillMaxWidth()
            ,value = phoneNumberInput
            ,singleLine = true
            ,leadingIcon = { Icon(  // 왼쪽 Icon 지정
                imageVector = Icons.Default.Email
                , contentDescription = null
            )}
            ,trailingIcon = {
                IconButton(onClick = {

                }) {
                    Icon( // 오른쪽에 Icon 지정
                        imageVector = Icons.Default.CheckCircle
                        , contentDescription = null
                    )
                }
            }
            ,keyboardOptions = KeyboardOptions(keyboardType =  KeyboardType.Email) // 키보드 타입 지정
            ,onValueChange = { phoneNumberInput = it }
            ,label = { Text(&quot;buttonIcon&quot;) }
            ,placeholder = { Text(&quot; @ .com&quot;)}
        )

        // password
        OutlinedTextField(
            modifier = Modifier.fillMaxWidth()
            ,value = passwordInput
            ,singleLine = true
            ,leadingIcon = { Icon(  // 왼쪽 Icon 지정
                imageVector = Icons.Default.Person
                , contentDescription = null
            )}
            ,trailingIcon = {
                IconButton(onClick = {
                    // 버튼이 눌려지면 비밀번호가 보이도록
                    shouldShowPassword = !shouldShowPassword
                }) {
                    Icon(painter = painterResource(
                        id = passwordResource(shouldShowPassword))
                        , contentDescription = null
                    )
                }
            }
            // 비밀번호가
            , visualTransformation = if(shouldShowPassword) VisualTransformation.None
                                     else PasswordVisualTransformation()
            ,keyboardOptions = KeyboardOptions(keyboardType =  KeyboardType.Password) // 키보드 타입 지정
            ,onValueChange = { passwordInput = it }
            ,label = { Text(&quot;password&quot;) }
            ,placeholder = { Text(&quot;password&quot;)}
        )

    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyComposeStudyTheme {
        textFieldContainer()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/41ed1f4a-f6a1-4ec7-a825-151fc51188d3/image.png" alt=""></p>
<ul>
<li>singleLine과 maxLine에 같은 입력을 넣었을 때 singleLine속성은 한 줄로 유지 되는 것을 확인할 수 있습니다. 반면 maxLine=3을 지정해 두었기에 최대 3줄 까지만 줄이 생성됩니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/ac528dd9-5111-4f7f-a050-49654356b5a4/image.png" alt=""></p>
<ul>
<li>우측 눈 모양 버튼을 누르면 입력한 비밀번호가 보이는 것을 확인할 수 있습니다.</li>
<li><strong><code>VisualTransformation</code>: <code>shouldShowPassword</code></strong>가 <strong><code>true</code></strong>이면 <strong><code>VisualTransformation.None</code></strong>으로 설정하여 비밀번호를 보여주고, 그렇지 않으면 <strong><code>PasswordVisualTransformation()</code></strong>을 사용하여 비밀번호를 가려줍니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - snackBar]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-snackBar</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-snackBar</guid>
            <pubDate>Mon, 21 Aug 2023 17:09:23 GMT</pubDate>
            <description><![CDATA[<h2 id="snackbar">snackBar</h2>
<h3 id="snackbar의-결과를-변수에-받아-처리하기">snackBar의 결과를 <code>변수</code>에 받아 처리하기</h3>
<pre><code class="language-kotlin">// 버튼 클릭시 코루틴 스코프 안에서 실행
        Button(onClick = {
            Log.d(&quot;snackBar&quot;, &quot;snackBar: 스낵바 버튼 클릭&quot;)

            corutineScope.launch {
                // snackBar의 결과를 받을 수 있음
                // 이 결과는 스낵바가 닫아졌는지? 스낵바의 버튼이 눌러졌는지?
                val result = snackbarHostState.showSnackbar(
                    &quot;snackBar show!!&quot;
                    ,&quot;확인&quot;
                    , SnackbarDuration.Short // 스낵바 보여주는 시간
                )

                when(result){
                    SnackbarResult.Dismissed -&gt; {
                        Log.d(&quot;snackBar&quot;, &quot;snackBar: 스낵바 닫아짐&quot;)
                    }
                    SnackbarResult.ActionPerformed -&gt; {
                        Log.d(&quot;snackBar&quot;, &quot;snackBar: 확인 버튼 눌러짐&quot;)
                    }
                }
            }
        }) {
            Text(text = &quot;스낵바 버튼&quot;)
        }</code></pre>
<h3 id="snackbar의-결과를-let-으로-처리하기">snackBar의 결과를 <code>.let{ }</code>으로 처리하기</h3>
<pre><code class="language-kotlin">// 버튼 클릭시 코루틴 스코프 안에서 실행
        Button(onClick = {
            Log.d(&quot;snackBar&quot;, &quot;snackBar: 스낵바 버튼 클릭&quot;)

            corutineScope.launch {
                // snackBar의 결과를 받을 수 있음
                // 이 결과는 스낵바가 닫아졌는지? 스낵바의 버튼이 눌러졌는지?
                snackbarHostState.showSnackbar(
                    &quot;snackBar show!!&quot;
                    ,&quot;확인&quot;
                    , SnackbarDuration.Short // 스낵바 보여주는 시간
                ).let {
                    when(it){
                        SnackbarResult.Dismissed -&gt; {
                            Log.d(&quot;snackBar&quot;, &quot;snackBar: 스낵바 닫아짐&quot;)
                        }
                        SnackbarResult.ActionPerformed -&gt; {
                            Log.d(&quot;snackBar&quot;, &quot;snackBar: 확인 버튼 눌러짐&quot;)
                        }
                    }
                }
            }
        }) {
            Text(text = &quot;스낵바 버튼&quot;)
        }</code></pre>
<h2 id="실습-코드">실습 코드</h2>
<pre><code class="language-kotlin">import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import com.example.dong.ui.theme.MyComposeStudyTheme
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyComposeStudyTheme {
                // A surface container using the &#39;background&#39; color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    snackBar()
                }
            }
        }
    }
}

@Composable
fun snackBar(){

    // var currentSnackbarData by mutableStateOf&lt;SnackbarData?&gt;(null)
    // 스낵바호스트를 클릭해 보면 위 와 같은 코드가 있다.
    // currentSnackbarData는 현재 스낵바가 떠 있으면 값이 들어오고 없으면 null이다.
    val snackbarHostState = remember {
        SnackbarHostState()
    }

    val corutineScope = rememberCoroutineScope()

    // 스낵바의 상태에 따라 버튼의 text를 변경하는 메서드이다.
    // SnackbarData에 접근하여 스낵바가 올라와 있는지 아닌지에 따라 text를 변경한다.
    val buttonTitle : (SnackbarData?) -&gt; String = { snackbarData -&gt;
        if(snackbarData != null){
            &quot;스낵바 숨기기&quot;
        } else{
            &quot;스낵바 보여주기&quot;
        }
    }

    val buttonColor : (SnackbarData?) -&gt; Color = { snackbarData -&gt;
        if(snackbarData != null){
            Color.Blue
        } else{
            Color.Red
        }
    }

    Box(
        modifier = Modifier.fillMaxSize()
        , contentAlignment = Alignment.Center
    ){

        // 버튼 클릭시 코루틴 스코프 안에서 실행
        Button(
            colors = ButtonDefaults.buttonColors(
                backgroundColor = buttonColor(snackbarHostState.currentSnackbarData)
                ,contentColor = Color.White
            )
            ,onClick = {
                Log.d(&quot;snackBar&quot;, &quot;snackBar: 스낵바 버튼 클릭&quot;)

                // 스낵바가 이미 올라와 있는데 스낵바를 띄우는 버튼을 또 클릭하면 스낵바를 내린다.
                if(snackbarHostState.currentSnackbarData != null){
                    Log.d(&quot;snackBar&quot;, &quot;snackBar: 이미 스낵바가 있음&quot;)
                    snackbarHostState.currentSnackbarData?.dismiss()
                    return@Button
                }

                corutineScope.launch {
                    // snackBar의 결과를 받을 수 있음
                    // 이 결과는 스낵바가 닫아졌는지? 스낵바의 버튼이 눌러졌는지?
                    snackbarHostState.showSnackbar(
                        &quot;snackBar show!!&quot;
                        ,&quot;확인&quot;
                        , SnackbarDuration.Short // 스낵바 보여주는 시간
                    ).let {
                        when(it){
                            SnackbarResult.Dismissed -&gt;
                                Log.d(&quot;snackBar&quot;, &quot;snackBar: 스낵바 닫아짐&quot;)

                            // 스낵바에 있는 버튼이 눌러졌을 때 로직처리 하는 부분
                            SnackbarResult.ActionPerformed -&gt;
                                Log.d(&quot;snackBar&quot;, &quot;snackBar: 확인 버튼 눌러짐&quot;)

                        }
                    }
                }
        }) {
            Text(buttonTitle(snackbarHostState.currentSnackbarData))
        }

        // 스낵바가 보여지는 부분은 따로 지정해 주어야 함
        SnackbarHost(hostState = snackbarHostState
            , modifier = Modifier.align(Alignment.BottomCenter))
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyComposeStudyTheme {
        snackBar()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/b718e36c-95ee-45d5-89c0-63d7c473c313/image.png" alt=""></p>
<ul>
<li>위 예제 코드 주석에 설명 되어 있듯이 스낵바가 나타나 있는 상태에서 스낵바 버튼을 한 번 더 눌러보면 스낵바가 사라지는 것을 확인할 수 있습니다.</li>
<li>코루틴을 사용함으로써 스낵바를 보여주는 비동기 작업을 수행하도록 했습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android(kotlin) - JetPack Compose - CheckBox]]></title>
            <link>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-CheckBox</link>
            <guid>https://velog.io/@dong-hyeok/Androidkotlin-JetPack-Compose-CheckBox</guid>
            <pubDate>Mon, 21 Aug 2023 17:05:57 GMT</pubDate>
            <description><![CDATA[<h2 id="checkbox--remember-활용-방식-3가지">CheckBox  remember 활용 방식 3가지</h2>
<ul>
<li><code>val mutableState = remember { mutableStateOf(default) }</code></li>
<li><code>var value by remember { mutableStateOf(default) }</code></li>
<li><code>val (value, setValue) = remember { mutableStateOf(default) }</code></li>
</ul>
<pre><code class="language-kotlin">    // 체크 박스 상태
    var checkedStatus_1 = remember {
        mutableStateOf(false)
    }

    // by로 지정하면 .value를 붙일 필요 없음
    var checkedStatus_2 by remember {
        mutableStateOf(false)
    }

    // 데이터를 가져올떄는 checkedStatus_3를 사용
    // 데이터를 지정할때 setValue3 사용
    val (checkedStatus_3, setValue3) = remember{
        mutableStateOf(false)
    }</code></pre>
<pre><code class="language-kotlin">        // val mutableState = rember{ }
        Checkbox(
            checked = checkedStatus_1.value
            , onCheckedChange = {
                Log.d(&quot;CheckBoxActivity&quot;, &quot;checkBoxContainer: $it&quot;)
                checkedStatus_1.value = it // check box가 눌려지면 그 상태를 저장
        })                

        // var value by rember{ }
        // 속성을 위임 -&gt; value가 프로퍼티인 것 처럼 사용
        Checkbox(
            checked = checkedStatus_2
            , onCheckedChange = {
                Log.d(&quot;CheckBoxActivity&quot;, &quot;checkBoxContainer: $it&quot;)
                checkedStatus_2 = it
        })

        // val (value, setValue) = remember
        Checkbox(
            checked = checkedStatus_3
            , onCheckedChange = {
                Log.d(&quot;CheckBoxActivity&quot;, &quot;checkBoxContainer: $it&quot;)
                setValue3.invoke(it) // invoke
        })</code></pre>
<ul>
<li>위 3가지 방법 모두 단순 CheckBox의 상태를 저장&amp;기억하기 위한 코드입니다. 
<code>by</code>를 사용하면 <code>.value</code>를 붙이지 안아도 된다는 장점이 있습니다.</li>
</ul>
<aside>
💡 **아래 예제 부터는 remember( )선언 방식을 하나로 통일하여 사용하겠습니다.**

</aside>

<hr>
<h2 id="checkbox-color">CheckBox Color</h2>
<pre><code class="language-kotlin">var checkedStatus_4 by remember {
        mutableStateOf(false)
    }</code></pre>
<pre><code class="language-kotlin">// color 지정
        /**
        checkedColor: Color = MaterialTheme.colors.secondary, (체크 상태의 색)
        uncheckedColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f), (체크 되지 않은 상태 색- 테두리만 색이 적용)
        checkmarkColor: Color = MaterialTheme.colors.surface, (체크 마커 색)
        disabledColor: Color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled),
        disabledIndeterminateColor: Color = checkedColor.copy(alpha = ContentAlpha.disabled)
         */
        Checkbox(
            checked = checkedStatus_4
            , colors = CheckboxDefaults.colors(
                checkedColor = Color.Red
                , uncheckedColor = Color.Blue
                , checkmarkColor = Color.Green
            )
            , onCheckedChange = {
                Log.d(&quot;CheckBoxActivity&quot;, &quot;checkBoxContainer: $it&quot;)
                checkedStatus_4 = it
            })</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/64544da3-8c58-4841-9496-2a231046a755/image.png" alt=""></p>
<hr>
<h2 id="checkbox-with-title">CheckBox With Title</h2>
<pre><code class="language-kotlin">Row(
            modifier = Modifier
                .background(Color.LightGray)
                .fillMaxWidth()
                .padding(horizontal = 30.dp)
            , horizontalArrangement = Arrangement.spacedBy(10.dp)
            , verticalAlignment = Alignment.CenterVertically
        ) {
            // val mutableState = rember{ }
            Checkbox(
                checked = checkedStatus_1.value
                , onCheckedChange = {
                    Log.d(&quot;CheckBoxActivity&quot;, &quot;checkBoxContainer: $it&quot;)
                    checkedStatus_1.value = it // check box가 눌려지면 그 상태를 저장
                })

            Text(text = &quot;1번 CheckBox&quot;)
        }</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/dfb4bdac-91fa-48d2-89d4-4558e4707b16/image.png" alt=""></p>
<ul>
<li>다음 코드는 Row안에 CheckBox와 Text를 함께 넣은 코드입니다. 
하나의 컬럼에 위 코드와 같이 Row( …. )로 만든 CheckBox와 Title을 여러개 만든다면 코드의 가독성이 떨어질 것입니다. 
CheckBox와 Title을 담고 있는 @Composable을 생성하여 사용한다면 위 문제를 해결할 수 있습니다.</li>
</ul>
<hr>
<h2 id="checkbox-실습">CheckBox 실습</h2>
<pre><code class="language-kotlin">import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.dong.study.ui.theme.MyComposeStudyTheme

class CheckBoxActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyComposeStudyTheme {
                // A surface container using the &#39;background&#39; color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    checkBoxContainer()
                }
            }
        }
    }
}

/**
checked: Boolean,  (체크 상태)
onCheckedChange: ((Boolean) -&gt; Unit)?, (체크 상태 변경 콜백 이벤트 발생)
modifier: Modifier = Modifier,
enabled: Boolean = true,  (체크 가능 여부)
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: CheckboxColors = CheckboxDefaults.colors() (체크박스 색)
**/

@Composable
fun checkBoxContainer() {

    var checkedStatus_1 = remember { mutableStateOf(false) }
    var checkedStatus_2 = remember { mutableStateOf(false) }
    var checkedStatus_3 = remember { mutableStateOf(false) }

    // 1,2,3번 check박스 status를 를 배열에 넣어 관리
    val checkedStatesArray = listOf(
        checkedStatus_1
        ,checkedStatus_2
        ,checkedStatus_3
    )

    // checkedStatesArray의 모든 객체(체크박스 1,2,3)이 모두 체크가 된 상태라면? =&gt; 모두 동의 체크박스도 체크
    // all 함수 : 해당 Array의 모든 객체 상태를 살펴 볼 수 있다.
    // val checkedStatusForForth : Boolean = checkedStatesArray.all { it.value == true }
    val checkedStatus_4 : Boolean = checkedStatesArray.all { it.value }

    // 모두 동의 체크박스에 이 이벤트를 등록시킨다.
    // 모두 동의 체크박스의 상태에 따라 나머지 3개의 체크박스의 상태가 동일하게 변경된다.
    val allBoxChecked : (Boolean) -&gt; Unit = { isAllBoxChecked -&gt;
        Log.d(&quot;checkBoxActivity&quot;, &quot;checkBoxContainer: $isAllBoxChecked&quot;)

        checkedStatesArray.forEach {
            it.value = isAllBoxChecked
        }
    }

    Column(
        modifier = Modifier
            .background(Color.White)
            .fillMaxSize()
        , verticalArrangement = Arrangement
            .spacedBy(10.dp, Alignment.CenterVertically)
        , horizontalAlignment = Alignment.CenterHorizontally

    ) {

        CheckBoxWithTitle(title = &quot;1번 CheckBox&quot;, checkedStatus_1)
        CheckBoxWithTitle(title = &quot;2번 CheckBox&quot;, checkedStatus_2)
        CheckBoxWithTitle(title = &quot;3번 CheckBox&quot;, checkedStatus_3)

        Spacer(modifier = Modifier.height(10.dp))

        // 해당 버튼이 눌렸을때 이벤트 동작 : allBoxChecked
        AllAgreeCheckBox(title = &quot;모두 동의 하시겠습니까?&quot;, checkedStatus_4, allBoxChecked)
    }
}

// CheckBox with Title - 모두 동의 CheckBox
@Composable
fun AllAgreeCheckBox(title:String
                     ,shouldChecked: Boolean
                     ,allBoxChecked : (Boolean) -&gt; Unit
) {
    Row(
        modifier = Modifier
            .background(Color.LightGray)
            .fillMaxWidth()
            .padding(horizontal = 30.dp)
        , horizontalArrangement = Arrangement.spacedBy(10.dp)
        , verticalAlignment = Alignment.CenterVertically
    ) {

        Checkbox(
            checked = shouldChecked
            , colors = CheckboxDefaults.colors(
                checkedColor = Color.Red
                , uncheckedColor = Color.Red
                , checkmarkColor = Color.Green
            )
            , onCheckedChange = {
                Log.d(&quot;CheckBoxActivity&quot;, &quot;checkBoxContainer: $it&quot;)
                allBoxChecked(it)
            })

        Text(text = title)
    }
}

// CheckBox with Title 모듈
@Composable
fun CheckBoxWithTitle(title:String, checkedStatus: MutableState&lt;Boolean&gt;){
    Row(
        modifier = Modifier
            .background(Color.LightGray)
            .fillMaxWidth()
            .padding(horizontal = 30.dp)
        , horizontalArrangement = Arrangement.spacedBy(10.dp)
        , verticalAlignment = Alignment.CenterVertically
    ) {

        Checkbox(
            checked = checkedStatus.value
            , onCheckedChange = {
                Log.d(&quot;CheckBoxActivity&quot;, &quot;checkBoxContainer: $it&quot;)
                checkedStatus.value = it // check box가 눌려지면 그 상태를 저장
            })

        Text(text = title)
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview5() {
    MyComposeStudyTheme {
        checkBoxContainer()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/d85fc98e-343a-4524-8908-0ba16b2677b6/image.png" alt=""></p>
<ul>
<li>모두 동의 하시겠습니까? 버튼을 누르면 나머지 1, 2, 3번 체크박스도 상태가 동일하게 변경되는 것을 확인할 수 있습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dong-hyeok/post/963750f5-0913-4676-9d35-b2387023e49c/image.png" alt=""></p>
<ul>
<li>1, 2, 3번 체크박스를 모두 클릭하면 모두 동의 하시겠습니까? 체크 박스가 클릭 상태로 변경되는 것을 확인할 수 있습니다.</li>
<li>1, 2, 3번 체크박스가 모두 체크된 상태에서 하나를 unCheck 상태로 변경한다면 모두 동의 하시겠습니까? 체크 박스도 unCheck 상태로 변경되는 것을 확인할 수 있습니다.</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>