본문 바로가기
개발/Android

[Android] RecyclerView 키보드 스크롤 처리 - 카톡과 비슷한 스크롤 처리

by JhDroid 2021. 11. 29.
728x90

RecyclerView 키보드 스크롤 처리

  • 채팅, 메신저 앱을 만들 때 흔히 사용하는 UI 구조는 헤더 - 리스트 - 푸터(EditText)  구조로 하단 EditText를 선택하면 안드로이드의 키보드가 올라오게 되는데 이 때 RecyclerView의 스크롤 처리를 하는 방법을 정리해 봤습니다.
    • 스크롤 처리를 하지 않으면 키보드가 리스트를 가리게 됩니다.
  • 카카오톡과  비슷하게 스크롤 처리가 가능합니다.
    • 방법은 틀렸을지도 ....? ㅠㅠ
  • 바쁜 분들을 위한 샘플 프로젝트
 

GitHub - JhDroid/recyclerview-scroll-sample

Contribute to JhDroid/recyclerview-scroll-sample development by creating an account on GitHub.

github.com

 

스크롤 처리

1. 키보드가 올라온 경우에만 스크롤이 가능한 경우

  • 단, 이경우 키보드가 내려가면 스크롤이 불가능 함

2. 키보드가 올라온 상태에서 데이터를 추가해 키보드가 내려갔을 때에도 스크롤이 가능한 경우

3. 화면 진입 시 데이터를 불러와 처음부터 스크롤이 가능한 경우

 

여기서 1번과 2, 3번의 스크롤 처리 방식이 조금 다릅니다, 아래 예제에서 확인해보겠습니다.

 

예제

  • 예제는 Kotlin + Databinding 으로만 구성되어 있습니다, 전체 코드는 샘플 프로젝트 확인 부탁드립니다!
  • 테스트 UI 구조
    • EditText에 데이터를 입력하고 SEND 버튼을 누르면 아이템을 추가하는 간단한 방식입니다.

 

1번 스크롤 처리

  • 키보드가 올라온 경우에만 스크롤이 가능할 때는 RecyclerView의 OnLayoutChangeListener로 쉽게 처리가 가능합니다.
binding.rvDataList.apply {
    adapter = sampleDataAdapter
    addItemDecoration(SpaceDecoration())

    /**
    * 1. 키보드가 올라온 경우에만 스크롤이 가능한 경우 처리
    * - 키보드가 내려간 경우 스크롤이 불가능하지만 키보드가 올라오면서 스크롤이 가능한 경우
    * */
    addOnLayoutChangeListener(onLayoutChangeListener)
}

-------------
    
private val onLayoutChangeListener =
    View.OnLayoutChangeListener { _, _, _, _, bottom, _, _, _, oldBottom ->
        // 키보드가 올라와 높이가 변함
        if (bottom < oldBottom) {
            binding.rvDataList.scrollBy(0, oldBottom - bottom) // 스크롤 유지를 위해 추가
        }
    }
  • 키보드가 올라오면 RecyclerView의 높이가 변경되는데 이 때 변경된 Bottom 값이 이전 Bottom값 보다 작다면 키보드가 올라온 상태로 이전 Bottom(oldBottom) - 변경된 Bottom(bottom) 를 스크롤 Y값으로 설정해 키보드가 올라왔을 때 리스트를 가리지 않고 키보드를 올릴 수 있습니다.

 

  • 키보드가 올라오지 않은 상태에서는 스크롤이 불가능하지만 여기서 키보드가 올라올 때는 리스트가 살짝 밀려 올라가야 합니다.

 

2, 3번 스크롤 처리

private fun setupView() {
    // 키보드 Open/Close 체크
    binding.clRootContainer.viewTreeObserver.addOnGlobalLayoutListener {
        val rect = Rect()
        binding.clRootContainer.getWindowVisibleDisplayFrame(rect)

        val rootViewHeight = binding.clRootContainer.rootView.height
        val heightDiff = rootViewHeight - rect.height()
        isOpen = heightDiff > rootViewHeight * 0.25 // true == 키보드 올라감
    }
}

 

  • RecyclerView의 스크롤 상태 체크를 위한 함수를 추가해줍니다.
/**
 * 세로 스크롤 가능 여부 확인
 * */
fun RecyclerView.isScrollable(): Boolean {
    return canScrollVertically(1) || canScrollVertically(-1)
}

 

  • RecyclerView는 최상단 부분을 유지하려는 특성(?)이 있어서 키보드가 올라왔을 때 알아서 스크롤되지 않습니다, 그래서 레이아웃에 맞게 알아서 스크롤(밀려 올라가도록) 처리가 되도록 하기 위해 최상단을 하단으로 설정할 필요가 있습니다.
    • 해당 설정은 LinearLayoutManager의 stackFromEnd 설정입니다.
      • 단, stackFromEnd 설정의 경우 데이터가 없을 떄 설정하면 아이템이 하단에 붙어서 나오기 떄문에 처음부터 설정하는것은 추천드리지 않습니다. (UI에 따라 다를 수 있음)
    • reverseLayout 설정도 동일하게 하단을 끝으로 인식하지만 리스트 순서가 반대로 변하니 주의하셔야 합니다.
  • stackFromEnd 설정을 위한 확장 함수를 추가해줍니다.
/**
 * StackFromEnd 설정
 * */
fun RecyclerView.setStackFromEnd() {
    (layoutManager as? LinearLayoutManager)?.stackFromEnd = true
}

 

  • 이제 스크롤의 변경 상태를 감지해 stackFromEnd를 설정하기 위해 RecyclerView에 OnScrollChangedListener 설정을 해줍니다.
binding.rvDataList.apply {
        adapter = sampleDataAdapter
        addItemDecoration(SpaceDecoration())

        /**
        * 1. 키보드가 올라온 경우에만 스크롤이 가능한 경우 처리
        * 키보드가 내려간 경우 스크롤이 불가능하지만 키보드가 올라오면서 스크롤이 가능한 경우
        * */
        addOnLayoutChangeListener(onLayoutChangeListener)

        /**
        * 2. 키보드가 올라온 상태에서 데이터를 추가해 키보드가 내려갔을 때에도 스크롤이 가능한 경우
        * 3. 화면 진입 시 데이터를 불러와 청므부터 스크롤이 가능한 경우
        * 키보드가 열리지 않은 상태에서 스크롤 가능 상태이면 StackFromEnd 설정
        * 키보드가 열린 상태에서 체크하면 키보드가 사라질 때 목록이 하단에 붙을 수 있음
        * */
        viewTreeObserver.addOnScrollChangedListener {
            if (isScrollable() && !isOpen) { // 스크롤이 가능하면서 키보드가 닫힌 상태일 떄만
                setStackFromEnd()
                removeOnLayoutChangeListener(onLayoutChangeListener)
            }
    }
}
  • stackFromEnd 설정을 해주면 위에서 설정해준 OnLayoutChangeListener는 설정할 필요가 없으니 해제해줍니다.
    • 만약 OnLayoutChangeListener를 유지하면 키보드를 닫을 때 스크롤이 자동으로 최하단으로 이동하게 됩니다.

 

확인

  • 키보드가 올라온 상태에서 데이터를 추가 (1~17) > 하단을 14번에 맞추고 키보드를 올림 (스크롤 유지됨) > 키보드를 다시 내림 (스크롤 유지됨)

 

  • 화면 진입 시 데이터를 불러와 이미 스크롤이 가능한 상태
private fun setupData() { // 더미 데이터 설정
    for (i in 1..20) dataList.add(i.toString())

    sampleDataAdapter?.submitList(dataList.toList())
}
  • 최초 진입 시 스크롤이 가능한 상태 > 하단을 16번으로 맞추고 키보드 올림 (스크롤 유지) > 이 상태에서 하단을 13번으로 변경 > 키보드 내림 (스크롤 유지)

 

* 글에 틀린 부분이 있으면 댓글 부탁드립니다 :D

728x90