본문 바로가기

Android 공부/Android UI

RecyclerView in SwipeRefreshLayout with CoordinatorLayout

1. 고민 고민

- NestedScrollView가 싫다.

- 그래서, Coordinator Layout을 통해서 다양한 뷰와 리사이클러 뷰의 성능상 이점을 갖는 구조를 만들었다.

- 새로 고침 기능이 필요해졌다.

- SwipeRefreshLayout이 필요하다. 

- SwipeRefreshLayout은 CoordinatorLayout에서는 어떻게 사용할까?

- RecyclerView의 behavior는 어떻게 해야할까?


 지난 글과 같이, NestedScrollView를 사용하지 않고, 리사이클러뷰에 다양한 뷰홀더를 만들어서 뷰 구조를 잡았다.

그 과정에서, CoordinatorLayout을 이용해서 리사이클러뷰의 재활용과 백드롭 등, 다양한 이점을 가져오고 있었다. 그리고, 새로 고침 기능이 필요해졌다. 그러면 어떻게 뷰 구조를 잡아야할까?


2. 생각보다 간단한 뷰구조

뷰 구조가 정말 심플하다.  기존에는 RecyclerView에 behavior가 설정되어 있었는데, SwipeRefreshLayout에 behavior를 이동시켜주면 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="utf-8"?>
<layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable name="subtitle" type="String"/>
    </data>
    <androidx.coordinatorlayout.widget.CoordinatorLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/view_content_background">
        <!-- 툴바 관련 -->
        <include layout="@layout/layout_coordinator_toolbar"/>
        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/srl_contents"
                app:layout_behavior="@string/appbar_scrolling_view_behavior">
            <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/rv_contents"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:paddingStart="@dimen/content_card_side_dimen"
                    android:paddingEnd="@dimen/content_card_side_dimen"
                    android:paddingBottom="@dimen/bottom_sheet_min_height"/>
        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
        <include
                android:id="@+id/bottom_sheet_container"
                layout="@layout/layout_bottom_sheet"/>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
 
cs

3. 로직

 로직에서 많이 애를 먹었다. 계속해서 애니메이션이 먼저 발생했기 때문이다. 그래서, 구글링을 이것 저것 돌려본 결과, isRefreshing이 순탄한 로직으로 되고 있지 않아서 였다. 아래와 같이 로직을 잡으면, 무조건 될 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//MyActivity, MyFragment, etc...
 
//make presnter
private val presenter by lazy {
    ContentViewPresenter(this)
}
 
//make viewBinding
private val viewContentBinding = provideDataBinding()
 
//initView
override fun initView() {
    viewContentBinding.let {
        it.srlContents.post {
            it.srlContents.setOnRefreshListener(refreshListener)
        }
    }
}
 
//refreshView
override fun refreshView() {
    presenter.initView()
    viewContentBinding.srlContents.post {
        if(viewContentBinding.srlContents.isRefreshing){
            viewContentBinding.srlContents.isRefreshing = false
        }
    }
}
 
//make SwipeRefreshListener
private val refreshListener = SwipeRefreshLayout.OnRefreshListener {
    viewContentBinding.srlContents.post {
        if(!viewContentBinding.srlContents.isRefreshing){
            viewContentBinding.srlContents.isRefreshing = true
        }
        presenter.getChatContents("0001")
    }
}
 
//1. initView
//2. call swipeRefreshListener
//3. ended presenter logic.
//4. refreshView
cs




1. 우선, 자신의 라이프사이클에 맞게 view를 초기화 해준다.

2. 초기화 할 때, refreshListener를 달아준다. 해당 리스너는 isRefreshing 상태가 false라면, true로 스와이프 애니메이션이 나타나게 해준다.

3. presneter.getChatContents()와 같이 자신만의 비지니스 로직을 호출한다.

3-1. 내가 구현한 getChatContents는 Database에 접근하고, onFinished가 됐을 때, 자동적으로 refreshView()를 호출한다.

4. 해당 로직이 호출이 종료되면, refreshView()가 호출이 된다.

5. 그 때, 획득한 데이터를 통해서, 다시 뷰를 초기화 해주고, isRefreshing이 true이면, 애니메이션이 사라지도록 false로 바꿔준다.


4. Result