[Android] 안드로이드 개발 레벨업 교과서 정리 #3 리사이클러뷰(1)


출처 : 안드로이드 개발 레벨업 교과서 111~120p

몰랐던 부분 정리하는 포스트!


1. RecyclerView의 특징

RecyclerView는 많은 데이터를 한정된 View를 재사용해서 표시하기 때문에 스크롤 등의 성능이 좋다. 구현할 것은 많아지지만 더 많은 옵션을 제공하며 확장성이 높아 레이아웃 변경이나 풍부한 조작, 애니메이션 등의 기능을 이용할 수 있다.

RecyclerView는 몇 가지 부품으로 구성된다. RecyclerView를 이용하려면 최소한 AdapterViewHolder를 만들 필요가 있다. LayoutManager라는 레이아웃을 관리하는 클래스는 기본적으로 준비되어있다.


[RecyclerView.Adapter]

RecyclerView.Adapter를 상속하는 클래스를 만들어 이용한다. View를 만들고 표시되는 View와 데이터를 연결한다.


[RecyclerView.ViewHolder]

일반적으로 Adapter 내에서 RecyclerView.ViewHolder를 상속하는 클래스를 만든다. ViewHolderView에 대한 참조를 유지한다. AdapteronCreateViewHolder() 메서드로 ViewHolder의 인스턴스를 생성해서 반환한다. 그리고 onBindViewHolder() 메서드로 ViewHolder에 설정한 View의 데이터를 설정한다. ViewHolder의 멤버변수에 View를 저장해 둠으로써 findViewById() 를 매번 실행할 필요가 없어지고 성능이 향상된다.


[RecyclerView.LayoutManager]

레이아웃 매니저는 RecyclerView에서 View의 위치와 크기를 결정하고 View의 재사용 규칙을 관리한다. RecyclerView에는 다음과 같은 3가지 레이아웃 매니저가 있다.

위 3개중에 필요한 것이 없다면 RecyclerView.LayoutManager 추상클래스를 확장하여 원하는 대로 구현할 수 있다.

아직 나는 리사이클러뷰를 자유롭게 사용하지 못하고 맨날 참고하는데, 가끔 아무것도 안보고 만들었을 때 LayoutManager를 설정하지 않는 실수를 범한다. 가장 중요한건데… LayoutManager를 설정하지 않으면 앱이 비정상 종료된다.


ViewGroup 구조로 RecyclerView에서 onLayout() 메서드를 호출해 LayoutManager에 처리를 맡긴다. LayoutManager는 필요해진 아이템의 ViewHolderAdapter로부터 가져오고, Adapter에서 ViewHolder에 데이터를 설정하게 한다. (그림은 첨부하지 않고 아래 글로 설명)

  1. onCreateViewHolder()ViewHolder를 넘겨준다.
  2. onBindViewHolder()ViewHolder에 데이터를 설정한다.
  3. View의 크기를 결정하고 아이템을 세팅한다.

RecyclerView에서는 View를 목록으로 표시하며, 스크롤해서 필요없어진 ViewViewHolder로서 Scrap 리스트에 추가하고, 스크롤되면 리스트에서 꺼낸다.(약간 큐 형식인듯 하다.) ViewHolder 안의 ViewAdapter로 데이터를 설정하고 다음으로 필요해진 View를 꺼내 표시한다. (어댑터를 통해 데이터를 바인드해 재사용한다)


2. RecyclerView 사용하기

이 부분은 많이 연습해봤기 때문에 책 예제 코드와 기존에 사용해봤던 방식을 비교하여 정리하는 느낌으로 써보려고 한다.

구현 순서는 다음과 같다.

  1. XML에 RecyclerView 뷰 추가하기
  • 뷰 속성에서 LayoutManager를 지정할 수 있다.

    1
    app:layoutManager="android.support.v7.widget.LinearLayoutManager"
  • 코드로도 설정할 수 있다. (kotlin)

    1
    recyclerView.layoutManager = LinearLayoutManager(context)


  1. 아이템 레이아웃 정의하기 (XML)


  1. ViewHolder 클래스 정의하기

내가 자주 봤었던 kotlin RecyclerView 구현 예제들은 어댑터 클래스 안에 뷰홀더클래스를 inner 클래스로 정의했는데, 이 책에서도 그렇게 구현되어있다. (Java도 마찬가지였던 것 같다)

다른점은 생성자가 public 이라는 점이다. 기존에 내가 구현해봤던 방식은 생성자를 private 으로 하고 create 메서드를 통해 ViewHolder 객체를 생성했다.

  • 오빠가 알려준 create 메서드 방식의 이점 : 뷰홀더 생성 시 view를 넘겨줘야하는데, 외부에서 생성할 경우 layout 리소스에 뭐가 포함되어있는지 알기 위해서는 생성자가 호출하는 코드를 봐야한다. 그러나 내부에서 create 메서드를 제공하면 뷰홀더 클래스만 봐도 어떤 layout 리소스를 필요로하는지 바로 보이는 이점이 있다.


  1. Adapter 클래스 정의하기 : 아래 3개의 메서드를 필수로 override 해야한다.
  • onCreateViewHolder(parent: ViewGroup, viewType: Int) : View의 inflate와 ViewHolder를 작성한다.
  • onBindViewHolder(holder: ViewHolder, position: Int) : ViewHolder에 데이터를 설정한다. (ViewHolder 클래스에서 각 뷰에 데이터를 설정하는 bind 메서드를 구현해놓고, holder.bind(data[position]) 으로 설정하면 된다.)
  • getItemCount : RecyclerView에서 표시할 아이템 수를 반환한다.


  1. 코드에서 RecyclerView 초기화 처리하기
  • RecyclerView 자체의 크기가 변하지 않는 것을 알고있을 때, setHasFixedSize(true) 로 설정하면 성능이 개선된다.
  • [선택] 클릭 이벤트 리스너 구현 하기

책 예제에서는 Adapter 클래스에서 클릭 이벤트 인터페이스를 만들어놨다. 보통 내가 해왔던 방법은 ViewHolder 클래스에서 하는 방법인데 어떤게 더 좋은것일까?


3. RecyclerView 커스터마이징 : 구분선 표시하기

ListView는 간단하게 구분선을 넣을 수 있지만, RecyclerView는 따로 RecyclerView.ItemDecoration 클래스를 상속하여 onDraw()메서드를 오버라이딩해서 구현해야한다. 즉, 이 클래스를 이용하면 RecyclerView를 꾸밀 수 있다. 구체적으로 말하자면 이번에는 getItemOffsets() 메서드로 각 아이템에 대한 Offset(빈 영역)을 설정하고 onDraw() 메서드로 실제 구분선을 그린다.

DividerItemDecoration.kt

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
44
45
46
47
48
class DividerItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() {
private val dividerHeight: Int
private var divider: Drawable

init {
// 기본인 ListView 구분선의 Drawable 을 얻는다(구분선을 커스터마이징 하고싶을 경우 여기서 Drawable 을 가져오면 된다.)
val typedArray: TypedArray = context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider))
divider = typedArray.getDrawable(0)

// 표시할 때마다 높이를 가져오지 않아도 되도록 여기서 구해둔다
dividerHeight = divider.intrinsicHeight
typedArray.recycle()
}

// // View 의 아이템보다 위에 그리고 싶을 경우 이 메서드를 사용한다
// override fun onDrawOver(c: Canvas?, parent: RecyclerView?, state: RecyclerView.State?) {
// super.onDrawOver(c, parent, state)
// }

// View 의 아이템보다 아래에 그리고 싶을 경우 이 메서드를 사용한다
override fun onDraw(c: Canvas?, parent: RecyclerView?, state: RecyclerView.State?) {
super.onDraw(c, parent, state)

// 좌우의 padding 으로 선의 right 와 left 를 설정
val lineLeft = parent!!.paddingLeft
val lineRight = parent.width - parent.paddingRight

val childCount = parent.childCount
for (i in 0 until childCount - 1) {
val child = parent.getChildAt(i)
val params: RecyclerView.LayoutParams = child.layoutParams as RecyclerView.LayoutParams

// 애니메이션 등의 상황에서 제대로 이동하기 위해
val childTransitionY = Math.round(ViewCompat.getTranslationY(child)) // deprecate 되었는데 대안이 있을까?
val top = child.bottom + params.bottomMargin + childTransitionY
val bottom = top + dividerHeight

// View 아래에 선을 그린다
divider.setBounds(lineLeft, top, lineRight, bottom)
divider.draw(c)
}
}

override fun getItemOffsets(outRect: Rect?, view: View?, parent: RecyclerView?, state: RecyclerView.State?) {
// View 아래에 선이 들어가므로 아래에 Offset 을 넣는다
outRect?.set(0, 0, 0, dividerHeight)
}
}


포스트가 너무 길어져서, 두 파트로 나눠야겠다.

Share