Idealim
article thumbnail

/* 본 게시물은 'Compose CookBook' 의 내용을 토대로 작성되었습니다. */

참고 자료

실행 결과

ListActivity

class ListActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ListViewContent {
                onBackPressed()
            }
        }
    }
}

@Composable
fun ListViewContent(onBack: () -> Unit){
    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Column(modifier = Modifier.padding(4.dp)){
                        Text( text = "ListView", fontSize = 30.sp, fontWeight = FontWeight.Bold )
                    }
                },
                elevation = 10.dp,
                navigationIcon = {
                    IconButton(onClick = onBack) {
                        Icon(Icons.Filled.ArrowBack, null)
                    }
                }
            )
        }
    ) {
        VerticalListView()
    }
}

@Composable
fun VerticalListView(){
    val list = remember { DemoDataProvider.itemList }

    LazyColumn{
        items(
            items = list,
            itemContent = {
                ListItemCardView(item = it)
            }
        )
    }
}

@Composable
fun ListItemCardView(item: Item){
    Card(modifier = Modifier
        .fillMaxWidth()
        .padding(10.dp),
        elevation = 10.dp,
        shape = RoundedCornerShape(12.dp),
        content = {
            ListItemCardContext(item = item)
        }
    )
}

@Composable
fun ListItemCardContext(item: Item){
    Column {
        //image
        Image(
            painter = painterResource(id = item.imageId),
            contentDescription = null,
            modifier = Modifier
                .padding(top = 15.dp, start = 15.dp, end = 15.dp)
                .fillMaxWidth()
                .height(180.dp), contentScale = ContentScale.Crop
            )
        //Title
        Text(text = item.title, fontSize = 25.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(all = 5.dp))
        //drawline
        Divider(color = Color(0xffDDDDDD), thickness = 2.dp)
        //likeButton
        LikeIcon(item.likeCount)
    }
}


@Composable
fun LikeIcon(likeCount: Int){
    val isLike = remember { mutableStateOf(false)}
    var likeCount = remember{ mutableStateOf(likeCount) }
    Row(verticalAlignment = Alignment.CenterVertically){
        IconToggleButton(
            checked = isLike.value,
            onCheckedChange = {
                if (it) likeCount.value += 1 else likeCount.value -= 1
                isLike.value = !isLike.value
            },
            modifier = Modifier.padding(horizontal = 4.dp)
        ) {
            if(isLike.value){
                Icon(
                    imageVector = Icons.Filled.Favorite,
                    contentDescription = null
                )
            } else {
                Icon(
                    imageVector = Icons.Default.FavoriteBorder,
                    contentDescription = null
                )
            }
        }
        Text(text = likeCount.value.toString(), fontSize = 20.sp)
    }
}
  • IconToggleButton 을 이용해 좋아요 버튼을 구현하였다.
  • Divider 을 이용해 Image와 Title 사이에 구분선을 표시했다.

# GridView / HorizontalView 추가

TopAppBar 상단 아이콘으로 ListType을 바꾸는 기능을 추가하겠다.

 

ListMenu 아이콘 클릭 시 메뉴 창을 보여주는 로직

//상단 AppBar 및 Icon 생성
@Composable
fun ListViewContent(onBack: () -> Unit){
    // 상단 리스트 아이콘 클릭 시 값이 바뀜.
    val showListMenu = remember { mutableStateOf(false) }
    // 리스트 타입
    val listViewType = remember { mutableStateOf(ListViewType.VERTICAL.name)}
    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Column(modifier = Modifier.padding(4.dp)){
                        Text( text = "ListView", fontSize = 20.sp, fontWeight = FontWeight.Bold )
                        Text( text = listViewType.value, fontSize = 10.sp)
                    }
                },
                elevation = 10.dp,
                navigationIcon = {
                    IconButton(onClick = onBack) {
                        Icon(Icons.Filled.ArrowBack, null)
                    }
                },
                actions = {
                    //버튼 클릭 시 값 변경 true -> false/ false -> true
                    IconButton(onClick = { showListMenu.value = !showListMenu.value}){
                        Icon(Icons.Filled.Menu, null)
                    }
                }
            )
        }
    ) {
        ListScreen(showListMenu = showListMenu, listViewType = listViewType)
    }
}

// List 보여주는 부분
@Composable
fun ListScreen(showListMenu: MutableState<Boolean>, listViewType: MutableState<String>){
    //Data 가져옴
    val list = remember { DemoDataProvider.itemList }

    Box(modifier = Modifier.fillMaxSize()) {
        when(listViewType.value){
            ListViewType.VERTICAL.name -> VerticalListView(list)
            ListViewType.HORIZONTAL.name -> HorizontalListView(list)
            ListViewType.GRID.name -> GridListView(items = list)
            else -> VerticalListView(list = list)
        }
        // (showListMenu = true 일 때) 리스트 메뉴를 보여주는 부분
        ListMenu(
            showListMenu = showListMenu,
            currentListViewType = listViewType,
            //align은 Box 안에 있을 때 만 사용 가능. 리스트 메뉴창을 왼쪽 상단에 띄운다.
            modifier = Modifier.align(Alignment.TopEnd)
        )
    }
}
// ListMenu 창
@Composable
fun ListMenu(showListMenu: MutableState<Boolean>, currentListViewType: MutableState<String>, modifier: Modifier){
    Card(
        modifier = modifier
            .padding(10.dp)
            .animateContentSize()
            .width(200.dp)
        ,
        elevation = 10.dp,
        shape = RoundedCornerShape(12.dp),

        ){
        Column(
            horizontalAlignment = Alignment.Start
        ){
            // showListMenu 값이 true 일 때 ListMenu를 보여준다.
            if (showListMenu.value) {
                ListMenuItem(currentListViewType = currentListViewType, listType = ListViewType.VERTICAL.name, showListMenu = showListMenu)
                Divider(color = Color(0xffDDDDDD), thickness = 2.dp)
                ListMenuItem(currentListViewType = currentListViewType, listType = ListViewType.HORIZONTAL.name, showListMenu = showListMenu)
                Divider(color = Color(0xffDDDDDD), thickness = 2.dp)
                ListMenuItem(currentListViewType = currentListViewType, listType = ListViewType.GRID.name, showListMenu = showListMenu)
            }
        }
    }
}

// 리스트 메뉴의 옵션을 보여준다.
@Composable
fun ListMenuItem(currentListViewType: MutableState<String>, listType: String, showListMenu: MutableState<Boolean>){
    Row(
        modifier = Modifier
            .padding(10.dp)
            .clickable(
                onClick = {
                    currentListViewType.value = listType
                    // 클릭하고 나면 리스트 메뉴 창 자동으로 닫힘
                    showListMenu.value = false
                }
            ),
        verticalAlignment = Alignment.CenterVertically
    ){
    	//listType 의 맞는 아이콘과 텍스트를 보여준다
        when (listType){
            ListViewType.VERTICAL.name -> {
                Icon(painter = painterResource(id = R.drawable.format_list_checkbox), null, modifier = Modifier.padding(10.dp))
                Text(text = ListViewType.VERTICAL.name, fontSize = 20.sp, fontWeight = FontWeight.Normal)
            }
            ListViewType.HORIZONTAL.name -> {
                Icon(painter = painterResource(id = R.drawable.format_list_text), null, modifier = Modifier.padding(10.dp))
                Text(text = ListViewType.HORIZONTAL.name, fontSize = 20.sp, fontWeight = FontWeight.Normal)
            }
            ListViewType.GRID.name -> {
                Icon(painter = painterResource(id = R.drawable.view_grid_outline), null, modifier = Modifier.padding(10.dp))
                Text(text = ListViewType.GRID.name, fontSize = 20.sp, fontWeight = FontWeight.Normal)
            }
        }
    }
}
  • showListMenu 값이 true가 되면 리스트 메뉴 창을 보여준다.
  • ListMenuItem 을 클릭 시 currentListViewType. value 값을 바꾼다.
  • Box를 사용해야만 Modifier.align 기능을 사용할 수 있어 ListScreen의 content를 Box로 감쌌다.

Vertical / Horizontal / Grid ListView 추가

@Composable
fun VerticalListView(list: List<Item>){
    LazyColumn {
        items(
            items = list,
            itemContent = {
                ListItemCardView(item = it)
            }
        )
    }
}

@Composable
fun HorizontalListView(list: List<Item>){
    LazyRow{
        items(
            items = list,
            itemContent = {
                ListItemCardView(item = it)
            }
        )
    }
}

@Composable 
fun GridListView(items: List<Item>){
    Column(modifier = Modifier.verticalScroll(rememberScrollState())){
        VerticalGrid(columns = 2){
            items.forEach{
                ListItemCardView(item = it)
            }
        }
    }
}
  • Grid List를 구현하기 위해서는 VerticalGrid를 구현해야한다. 

VerticalGrid

@Composable
fun VerticalGrid(
    modifier: Modifier = Modifier,
    columns: Int = 2,
    content: @Composable () -> Unit
) {
    Layout(
        content = content,
        modifier = modifier
    ) { measurables, constraints ->
        val itemWidth = constraints.maxWidth / columns
        // Keep given height constraints, but set an exact width
        val itemConstraints = constraints.copy(
            minWidth = itemWidth,
            maxWidth = itemWidth
        )
        // Measure each item with these constraints
        val placeables = measurables.map { it.measure(itemConstraints) }
        // Track each columns height so we can calculate the overall height
        val columnHeights = Array(columns) { 0 }
        placeables.forEachIndexed { index, placeable ->
            val column = index % columns
            columnHeights[column] += placeable.height
        }
        val height = (columnHeights.maxOrNull() ?: constraints.minHeight)
            .coerceAtMost(constraints.maxHeight)
        layout(
            width = constraints.maxWidth,
            height = height
        ) {
            // Track the Y co-ord per column we have placed up to
            val columnY = Array(columns) { 0 }
            placeables.forEachIndexed { index, placeable ->
                val column = index % columns
                placeable.place(
                    x = column * itemWidth,
                    y = columnY[column]
                )
                columnY[column] += placeable.height
            }
        }
    }
}
  • 이 코드는 ComposeCookBook 의 verticalgrid에서 가져왔다. Grid의 사이즈를 columns 의 개수에 맞추어서 item 크기, 위치를 정해준다.

실행 결과

 

반응형
profile

Idealim

@Idealim

읽어주셔서 감사합니다. 잘못된 내용이 있으면 언제든 댓글로 피드백 부탁드립니다.