[Compose UI] LazyColumn에 item 이 추가될 때마다 마지막 item으로 스크롤하기

LeeEunJae·2023년 1월 6일
1

최근에 시작한 프로젝트를 진행하다가 LazyColumn 을 사용해서 실시간 채팅 기능을 구현했는데, 가장 최근에 받은 메시지 item의 위치로 Scroll 하는 방법에 대해서 삽질을 하다가 알아낸 방법입니다.

🧑‍💻 Code

@Composable
fun ChatList(
    roomId: String,
    currentUser: User,
    viewModel: MainViewModel
){
    val chatList by viewModel.getRoomMessage(roomId).observeAsState()
    var previousChat by remember { mutableStateOf(listOf<Chat>()) }

    val listState = rememberLazyListState()
    val isKeyboardOpen by keyboardAsState()
    val coroutineScope = rememberCoroutineScope()

    Log.d("testt", "isKeyboardOpen: ${isKeyboardOpen}")

    chatList?.let {
        LazyColumn(
            verticalArrangement = Arrangement.spacedBy(8.dp),
            contentPadding = PaddingValues(10.dp),
            state = listState,
        ){
            items(
                items = chatList!!,
                key = { it.chat_key }
            ){ chat->
                if(chat.userInfo.uid == currentUser.uid){
                    // 나의 채팅
                    MyChatItem(chat = chat)
                }else{
                    // 다른 사람의 채팅
                    OtherChatItem(chat = chat)
                }
            }

            if(isKeyboardOpen == Keyboard.Opened || previousChat.size != it.size) {
                coroutineScope.launch {
                    listState.scrollToItem(it.size - 1)
                }
            }
        }
        previousChat = it
    }
    
}

chatList(모든 채팅 메시지)를 observeAsState() 로 관리하기 때문에 어느시점에 새로운 메시지가 왔는지 알아야 합니다.

var previousChat by remember { mutableStateOf(listOf<Chat>()) }

그래서 새로운 메시지가 오기 이전의 메시지들을 저장할 State 를 따로 선언해놓습니다.

if(isKeyboardOpen == Keyboard.Opened || previousChat.size != it.size) {
        coroutineScope.launch {
             listState.scrollToItem(it.size - 1)
        }
}

키보드가 올라오거나, 이전 메시지 개수와 현재 메시지 개수가 일치하지 않으면 현재 리스트의 마지막 아이템 인덱스로 스크롤합니다.

enum class Keyboard {
    Opened, Closed
}

@Composable
fun keyboardAsState(): State<Keyboard> {
    val keyboardState = remember { mutableStateOf(Keyboard.Closed) }
    val view = LocalView.current
    DisposableEffect(view) {
        val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {
            val rect = Rect()
            view.getWindowVisibleDisplayFrame(rect)
            val screenHeight = view.rootView.height
            val keypadHeight = screenHeight - rect.bottom
            keyboardState.value = if (keypadHeight > screenHeight * 0.15) {
                Keyboard.Opened
            } else {
                Keyboard.Closed
            }
        }
        view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener)

        onDispose {
            view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener)
        }
    }

    return keyboardState
}

현재 키보드의 상태를 리턴하는 메서드입니다.

profile
매일 조금씩이라도 성장하자

1개의 댓글

comment-user-thumbnail
2023년 11월 2일

좋은 글 감사합니다~

답글 달기