@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
Compose_1Theme {
Greeting("Android")
}
}
preview에 있는 함수가 스플릿하면 디자인이 나온다. 매우 편리하다.

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Compose_1Theme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
setcontent 부분을 변경하는 것이 아닌 그리팅 함수를 변경해야 한다.

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name! Hello $name! Hello $name!",
modifier = Modifier.size(300.dp), // 감싸는 크기, dp도 임포트 필요
color = Color.Red, // 색
fontSize = 30.sp, // 폰트 크기, sp 는 임포트
fontWeight = FontWeight.Bold, // 굵기
fontFamily = FontFamily.Cursive, // 글씨체
letterSpacing = 2.sp, // 글씨는 sp 단위
maxLines = 3, // 최대 줄
textDecoration = TextDecoration.Underline, // 밑 줄
textAlign = TextAlign.Center
)
}

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Compose_1Theme {
ButtonExample(onButtonClicked = {
Toast.makeText(this, "send clicked.", Toast.LENGTH_SHORT).show()
})
}
}
}
}
@Composable
fun ButtonExample(onButtonClicked: () -> Unit) {
Button(
onClick = onButtonClicked,
enabled = true, // 버튼 활성화
shape = CircleShape, // 버튼 모양
contentPadding = PaddingValues(20.dp), // 글자 여백
border = BorderStroke(10.dp, Color.Magenta, // 버튼 경계선
)
) {
Icon(
imageVector = Icons.Filled.Send,
contentDescription = null,
) // icon
Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) // icon 여백 생성
Text(text = "send")
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
Compose_1Theme {
ButtonExample(onButtonClicked = {})
}
}

@Composable
fun ModifierExample() {
Button(
onClick = {},
colors = ButtonDefaults.buttonColors(
contentColor = Color.Cyan,
containerColor = Color.Red
), // 버튼의 컬러는 모디파이어에서 못 바꿈
/*enabled = false,*/
modifier = Modifier
.size(200.dp, 100.dp) // width, height 따로 설정 가능 . 으로
.padding(10.dp) // 버튼을 여백으로 감싸며 채움
) {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = null,
modifier = Modifier.background(Color.Blue)
)
Spacer(
modifier = Modifier.size(ButtonDefaults.IconSpacing).background(Color.Blue)
)
Text(
"Search",
/*modifier = Modifier.clickable { }*/ // 클릭 가능하게 바꿈
modifier = Modifier.offset(x = 10.dp, y = 10.dp).background(Color.Blue)
)
// 하지만 다른 백그라운드 컬러는 모디파이어에서 변경
}
}
머터리얼 디자인의 기본적인 패턴
핵심적인 메타포
높이가 있어 그림자가 있음
컬러를 서피스로 하면 콘텐스 색이 알아서 지정됨

@Composable
fun SurfaceExample() {
Surface(
border = BorderStroke(width = 2.dp, color = Color.Magenta),
shadowElevation = 5.dp, // 그림자
modifier = Modifier.padding(5.dp),
shape = CircleShape,
color = MaterialTheme.colorScheme.error,
) {
Text(
text = "surface",
modifier = Modifier.padding(5.dp)
)
}
}

@Composable
fun BoxExample() {
/*Box(modifier = Modifier.size(100.dp)) {
Text(text = "hello world",
modifier = Modifier.align(Alignment.BottomEnd))
Text(text = "jetpack",
modifier = Modifier.align(Alignment.TopStart))
Text(text = "compose",
modifier = Modifier.align(Alignment.Center))
Box(modifier = Modifier.size(70.dp).background(Color.Cyan).align(Alignment.CenterStart)) {
}
Box(modifier = Modifier.size(70.dp).background(Color.Yellow).align(Alignment.BottomEnd)) {
}
}*/
Box() {
Box(modifier = Modifier.fillMaxSize().background(Color.Cyan).align(Alignment.CenterStart))
Box(modifier = Modifier.size(70.dp).background(Color.Yellow).align(Alignment.Center))
}
}

@Composable
fun RowExample() {
// Row(modifier = Modifier.height(40.dp)){ // 하나 하나 배치
// Text(text = "첫 번째", modifier = Modifier.align(Alignment.Top))
// Text(text = "두 번째", modifier = Modifier.align(Alignment.CenterVertically))
// Text(text = "세 번째", modifier = Modifier.align(Alignment.Bottom))
//
// }
Row(
modifier = Modifier
.height(40.dp)
.width(200.dp),
horizontalArrangement = Arrangement.Center, // 글자 수평 배치
verticalAlignment = Alignment.Bottom
) { // 글자 수직 배치 한 번에 배치
Text(text = "첫 번째", textAlign = TextAlign.Center, modifier = Modifier
.weight(1f)
.background(color = Color.Gray))
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "추가",
modifier = Modifier
.weight(3f)
.background(color = Color.Cyan)
)
Text(text = "두 번째",textAlign = TextAlign.Center, modifier = Modifier.weight(1f))
Text(
text = "세 번째",textAlign = TextAlign.Center,
modifier = Modifier
.align(Alignment.Top)
.weight(3f)
.background(color = Color.Red)
)
}
}

@Composable
fun ColumnExample() {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.size(100.dp)
) {
Text(text = "첫 번째",
modifier = Modifier.align(Alignment.End))
Text(text = "두 번째")
Text(text = "세 번째",
modifier = Modifier.align(Alignment.Start))
}
}
길이가 얼마 이상일 때 어떠한 일을 발생시키겠다.

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Compose_1Theme {
Outer()
}
}
}
}
@Composable
fun Outer() {
Column {
Inner(
modifier = Modifier
.widthIn(min = 100.dp, max = 350.dp)
.heightIn(min = 100.dp, max = 160.dp)
)
Inner(
modifier = Modifier
.widthIn(min = 100.dp, max = 350.dp)
.heightIn(min = 100.dp, max = 60.dp)
)
}
}
@Composable
private fun Inner(modifier: Modifier = Modifier) {
BoxWithConstraints(modifier) {
if (maxHeight > 150.dp) {
Text("여기 꽤 길군요", modifier = Modifier.align(Alignment.BottomCenter))
}
Text(" $maxWidth $maxHeight $minWidth $minHeight")
}
}

@Composable
fun ImageExample() {
Column {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "머시기"
)
Image(
imageVector = Icons.Filled.Settings,
contentDescription = "setting",
)
// Image(
// bitmap = ,
// contentDescription = "",
// )
}
}
dependencies에 다음과 같이 추가해야 한다.
implementation(libs.coil)
implementation(libs.coil.compose)
버전이 바뀌어서 이렇게 추가해줘야 했다.
manifests파일에도 네트워크 허용부분을 추가해준다.
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
preview 모드에서 네트워크 관련은 보이지 않는다.
@Composable
fun coilExample() {
// val painter = rememberImagePainter(data = "https://velog.velcdn.com/images/mraz3068/post/7039993b-b875-4524-b1f8-dfcc0ab3d720/image.png")
//
// Image(painter = painter, contentDescription = "coil")
Column {
AsyncImage(
model = "https://velog.velcdn.com/images/mraz3068/post/7039993b-b875-4524-b1f8-dfcc0ab3d720/image.png",
contentDescription = "coil"
)
AsyncImage(
model = "https://velog.velcdn.com/images/mraz3068/post/7039993b-b875-4524-b1f8-dfcc0ab3d720/image.png",
contentDescription = "coil"
)
}
}
따라서 에뮬레이터로 실행해 주어야 한다.


@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
Compose_1Theme {
CardExample(MainActivity.cardData)
}
}
data class CardData(
val imageUri: String,
val imageDescripsion: String,
val author: String,
val description: String
)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Compose_1Theme {
Column {
CardExample(cardData)
CardExample(cardData)
}
}
}
}
companion object {
val cardData = CardData(
imageUri = "https://velog.velcdn.com/images/mraz3068/post/7039993b-b875-4524-b1f8-dfcc0ab3d720/image.png",
imageDescripsion = "coil",
author = "me",
description = "none"
)
}
}
@Composable
fun CardExample(cardData: CardData) {
val placeHolderColor = Color(0x33000000)
Card(
elevation = CardDefaults.cardElevation(80.dp),
modifier = Modifier.padding(4.dp),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(12.dp)
) {
AsyncImage(
model = cardData.imageUri, // 이미지의 URL
contentScale = ContentScale.Crop, // 이미지의 비율을 유지
contentDescription = cardData.description, // 이미지의 설명
placeholder = ColorPainter(placeHolderColor), // 이미지가 로드되지 않았을 때 보여줄 이미지
error = rememberVectorPainter(Icons.Default.Settings), // 이미지 로드에 실패했을 때 보여줄 이미지
fallback = rememberVectorPainter(Icons.Default.Send), // 이미지가 없을 때 보여줄 이미지
modifier = Modifier.size(100.dp).clip(RoundedCornerShape(4.dp)), // 이미지의 크기와 모서리 둥글기
)
Spacer(Modifier.size(8.dp))
Column {
Text(
text = cardData.author,
fontSize = 10.sp,
fontWeight = FontWeight.Bold
)
Spacer(Modifier.size(4.dp)) // 4의 배수가 좋음
Text(
text = cardData.description,
fontSize = 10.sp,
fontWeight = FontWeight.Bold
)
}
}
}
}

@Composable
fun CheckBoxExample() {
//// var checked = false // 안 됨
// var checked = remember { mutableStateOf(false) }// 이건 됨
// Row(verticalAlignment = Alignment.CenterVertically) {
// Checkbox(checked = checked.value, onCheckedChange = {
// checked.value = !checked.value
// }) // 체크박스의 체크드 변수를 바꿔줘야 함
// // 컴포저블에 있는 상태를 단순히 뮤터블 스테이트라고 적어 주면 날아갈 가능성이 있어서 remembe로 감싸줘야 함
// // 상태가 다시 그려지는 것을 리컴포지션이라고 한다
// Text("hello world")
// }
// var checked by remember { mutableStateOf(false) }// 프로퍼티 변경
// Row(verticalAlignment = Alignment.CenterVertically) {
// Checkbox(checked = checked, onCheckedChange = {
// checked = !checked
// })
// Text("hello world")
// }
var (getter, setter) = remember { mutableStateOf(false) }// destruction으로 상태를 받아서 사용 게터 세터로 받음
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = getter, onCheckedChange = setter)
Text("hello world", modifier = Modifier.clickable {
setter(!getter)
})
}
}

@Composable
fun TextFieldExample() {
var name by remember { mutableStateOf("tom") }
Column (modifier = Modifier.padding(16.dp)){
TextField(
value = name,
onValueChange = {name = it},
label = {
Text(text = "Name")
}
)
Spacer(modifier = Modifier.size(8.dp))
Text(text = "Hello $name")
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopAppBarExample() {
Column {
TopAppBar(
title = { Text("TopAppBar") },
navigationIcon = {
IconButton(onClick = {}) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "back"
)
}
}, actions = {
IconButton(onClick = {}) {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = "search"
)
}
IconButton(onClick = {}) {
Icon(
imageVector = Icons.Filled.Settings,
contentDescription = "settings"
)
}
IconButton(onClick = {}) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = "menu"
)
}
}
)
Text("Hello World")
}
}

@Composable
fun CheckBoxEx(
checked: Boolean,
onCheckedChanged: () -> Unit,
content: @Composable RowScope.() -> Unit
) {
Row(verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable {
onCheckedChanged()
}) {
Checkbox(
checked = checked,
onCheckedChange = { onCheckedChanged() }
)
content()
}
}
// 컴포저블 안에 컴포저블을 넣는 것을 슬롯 api라고 한다.
@Composable
fun SlotEx() {
var checked1 by remember { mutableStateOf(false) }
var checked2 by remember { mutableStateOf(false) }
Column {
CheckBoxEx(checked1, { checked1 = !checked1 } , { Text("체크박스1") })
CheckBoxEx(checked2, { checked2 = !checked2 } , { Text("체크박스2") })
}
}

@Composable
fun CheckBoxWithContent(
checked: Boolean,
toggleState: () -> Unit,
content: @Composable RowScope.() -> Unit
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable { toggleState() }
) {
Checkbox(
checked = checked,
onCheckedChange = { toggleState() },
)
content()
}
}
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ScaffoldEx() {
var checked by remember { mutableStateOf(false) }
Scaffold(topBar = {
// 스텝 1: `topBar`를 `TopAppBar`로 채워 봅시다.
TopAppBar(
navigationIcon = {
IconButton(onClick = { /*TODO*/ }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "뒤로가기"
)
}
},
title = { Text(text = "Scaffold Ex") },
)
},
floatingActionButton = {
FloatingActionButton(onClick = { /*TODO*/ }) {
Icon(imageVector = Icons.Default.Search, contentDescription = "검색")
}
}
) {
Surface(modifier = Modifier.padding(8.dp)) {
// 스텝 2: 아래에 CheckBoxWithContent를 넣어봅시다.
CheckBoxWithContent(
checked = checked,
toggleState = { checked = !checked },
) { Text(text = "스크래치 모드") }
}
}
}

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CatalogTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background
) {
CatalogEx(items)
}
}
}
}
}
@Composable
fun Item(itemData: ItemData) {
// 스텝 1: catalog1.png를 참고해 카드 레이아웃을 완성하세요.
Card(
elevation = 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.height(8.dp))
Text(
text = itemData.title,
style = MaterialTheme.typography.h6
)
Spacer(modifier = Modifier.height(8.dp))
Text(text = itemData.description)
}
}
}
@Preview(showBackground = true)
@Composable
fun ItemPreview() {
CatalogTheme {
Item(
ItemData(
imageId = drawable.a1,
title = "해변 놀이 공원",
description = "해변 놀이 공원 설명입니다. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. "
)
)
}
}
@Composable
fun CatalogEx(itemList: List<ItemData>) {
LazyColumn {
// 스텝 2: `items(itemList)`를 이용해 Item을 반복해서
// 컬럼에 추가하세요.
// 많은 것을 보여줄 때 사용
items(itemList) { itemData ->
Item(itemData)
}
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
CatalogTheme {
CatalogEx(items)
}
}
data class ItemData(
@DrawableRes val imageId: Int,
val title: String,
val description: String,
)
val items = listOf(
ItemData(
imageId = drawable.a1,
title = "해변 놀이 공원",
description = "해변 놀이 공원 설명입니다. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. "
),
ItemData(
imageId = drawable.a2,
title = "캐년",
description = "미국의 캐년입니다. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. "
),
ItemData(
imageId = drawable.a3,
title = "워터월드",
description = "워터월드입니다. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. "
),
ItemData(
imageId = drawable.a4,
title = "미국의 캐년",
description = "미국의 캐년입니다. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. "
),
ItemData(
imageId = drawable.a5,
title = "라스베가스",
description = "라스베가스입니다. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. "
),
ItemData(
imageId = drawable.a6,
title = "호르슈 밴드",
description = "호르슈 밴드입니다. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. "
),
ItemData(
imageId = drawable.a7,
title = "미국의 길",
description = "미국의 길입니다. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. "
),
ItemData(
imageId = drawable.a8,
title = "엔텔로프 캐년",
description = "엔텔로프 캐년입니다. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. "
),
ItemData(
imageId = drawable.a9,
title = "그랜드 캐년",
description = "그랜드 캐년입니다. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. "
),
)