/* 본 게시물은 'Compose CookBook' 의 내용을 토대로 작성되었습니다. */
참고 자료
[Compose CookBook] : https://github.com/Gurupreet/ComposeCookBook
실행 결과
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 크기, 위치를 정해준다.
실행 결과
반응형