본문 바로가기

gyub's 공부일기/그저 내 공부

ListView vs RecyclerView

※※   그저 공부하는 흐름대로 작성한 것이니 정돈된 글이 아님 주의 ※※

 

ListView의 문제점

 

1. ViewHolder Pattern 을 강요하지 않는다.

 

ListView는 이름에서도 알 수 있듯이 리스트 즉, 목록을 구현하는데 사용된다. ListView는 안드로이드에 임베디드 되어 있는 코드로 동작하며, API level 1부터 존재했다.

위의 형태는 가장 일반적인 ListView의 getView() 접근 방법이다. 하지만 위와 같이 동작하게 되면 getView() 즉, ListView의 재사용성이 떨어지게 된다.

 

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    Holder holder = new Holder();
    View rowView = inflater.inflate(R.layout.item_list, null);
    holder.tv = (TextView) rowView.findViewById(R.id.text);
    holder.img = (ImageView) rowView.findViewById(R.id.image);
    holder.tv.setText(result[position]);
    holder.img.setImageResource(imageId[position]);
    rowView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            Toast.makeText(context, "You Clicked " + result[position], Toast.LENGTH_LONG).show();
        }
    });
    return rowView;
}

재사용이라는게 getView()는 현재 화면상에 아이템이 보일 때 호출되는 함수이다. 예를 들어, 아이템이 20개가 있고 이를 스크롤한다고 가정하면 스크롤 시에도 getView() 함수는 계속해서 호출된다.

Holder holder = new Holder();
View rootView = inflater.inflate(R.layout.item_list, null)

또한, 위의 코드는 별도의 null 처리가 없으므로 스크롤을 할 때마다 inflate를 통해서 View의 create가 발생하고 findViewById도 함께 호출된다. [이러한 부분들이 비효율적이다.] 리스트의 특성상 하나의 View만 있으면 이 View가 연속적으로 사용이 가능한 형태가 만들어지면 되는데 ListView는 이러한 것이 강제적이지 않아서 힘들다.

그래서 ViewHolder의 개념이 등장하게 된다. 구글의 권장 사항이라 강제적이지는 않다. 다만 위와 같이 inflate와 findViewById를 리스트뷰에서 연속적으로 발생시키면 메모리와 성능에 악영향을 미칠 수 있다. 그래서 ViewHolder 패턴을 사용하는 것을 권장한다.

 

2. Vertical 모드만 지원한다.

 

3. Animation 을 따로 지원하지 않기에 뷰에 대해 직접 구현해야 한다.

 

4. 데이터가 변경될때 notifyDataSetChanged() 를 통해 뷰를 갱신하는데 모든 뷰를 갱신하기에 비용이 매우 크다.

 

이러한 문제를 해결하고자 RecyclerView 가 나왔다.

 

RecyclerView 의 장점

RecyclerView.ViewHolder

LayoutManager

ItemAnimator

ItemDecoration

DiffUtil

OnItemTouchListener 가 있으며 이것을 통해 문제점을 개선하였다.

 

RecyclerView.ViewHolder

RecyclerView 에서는 ViewHolder 를 강제하고 있기에 뷰에 대한 findViewById() 작업 비용을 줄일 수 있다. ListView 에서도 직접 패턴을 구현하여 적용할 수 있지만, 강제하지 않기 때문에 잘못 사용될 위험이 공존한다.

 

LayoutManager

LayoutManager를 통해 Linear, Grid, Staggered 형식으로 다양하게 리스트뷰를 확장할 수 있다. 

Linear에서는 Horizontal, Vertical 모드를 지원하기에 오픈 소스를 찾거나 커스텀 할 필요없이 쉽게 구현이 가능하다.

 

ItemAnimator

animateAdd, animateRemove, animateMove 를 구현함으로써 추가, 삭제, 변경시 적용할 애니메이션을 쉽게 적용할 수 있다.

 

ItemDecoration

ListView 에서는 xml 파일에 android:divider 항목을 추가함으로써 구현할 수 있지만 모양과 크기를 유연하게 커스텀하기 어려움 

ItemDecoration은 뷰가 그려질 때 공백을 넣거나, 경계선을 추가하는 등 뷰를 꾸미는 작업을 쉽고 다양하게 적용할 수 있다.

따라서 별도의 뷰를 추가하여 성능 저하가 발생하거나, padding 을 통해 뷰에 공백을 추가하여 애니메이션이 어색해지는 문제를 해결할 수 있다.

 

DiffUtil

DiffUtil은 데이터의 변화를 감지하여 뷰를 갱신하는 클래스이다.

areItemsTheSame, areContentsTheSame, getChangePayload 등을 오버라이딩하여 데이터간 변화를 감지한다. RecyclerView.Adatper 에 notifyItemMoved, notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved 가 호출되면 DiffUtil 에서 해당 범위의 데이터의 변화를 감지하고 뷰를 선택적으로 갱신함으로써 비용이 큰 notifyDataSetChanged 대신 효율적으로 사용될 수 있다.

 

OnItemTouchListener

ListView 는 OnItemClickListener 를 통해 아이템 클릭 이벤트를 쉽게 구현할 수 있었다.

반면 RecyclerView 는 OnItemTouchListener 에서 GestureDetector 를 이용하여 클릭뿐만 아니라 롱클릭이나 탭 등의 다양한 제스쳐 이벤트를 구현할 수 있다. 하지만 구현이 복잡하다는 단점이 있다

 

RecyclerView

기존의 ListView는 커스터마이징 하기에 힘들었고, 구조적인 문제로 성능상의 문제도 있었다.

RecyclerView는 ListView의 문제를 해결하기 위해 개발자에게 더 다양한 형태로 커스터마이징 할 수 있도록 제공되었다.

RecyclerView와 ListView의 가장 큰 차이점은 LayoutManager와 ViewHolder 패턴의 의무적인 사용, Item에 대한 뷰의 변형이나

애니메이션할 수 있는 개념이 추가된 것이다.

 

Create Lists

creating Lists and Cards에 정의된 List 표현이다.

widget인 RecyclerView는 LayoutManager를 통해서 View를 그리는 방법을 정의한다. RecyclerView.Adapter에서는 Data의 ViewHolder 정의에 따라서 UI가 선택되고 이를 표현하게 된다.

  • 강제적인 ViewHolder의 적용으로 View의 재사용을 가능하게 해준다.
  • 많은 데이터를 리스트 형태로 제공이 가능하다.
  • RecyclerView.ItemAnimator을 이용하여 Item의 Animator를 이용할 수 있다.
  • LayoutManager를 통해서 아이템의 배치 방법을 다양하게 적용할 수 있다.

주요 클래스

  • Adapter : 기존의 ListView에서 사용하는 Adapter와 같은 개념으로 데이터와 아이템에 대한 View 생성
  • ViewHolder : 재활용 View에 대한 모든 서브 뷰를 보유
  • LayoutManager : 아이템 항목을 어떻게 배치하는가를 결정
  • ItemDecoration : 아이템 항목에서 서브뷰에 대한 처리
  • ItemAnimation : 아이템 항목이 추가, 삭제되거나 정렬될 때 애니메이션 처리를 할 수 있다.

1. Adapter

 

리스트뷰는 데이터가 어디서 왔느냐에 따라 BaseAdapter를 상속한 ArrayAdapter(배열로부터 데이터를 가져올 때 사용), CursorAdapter(DB로부터 데이터를 가져올 때 사용), SimpleAdapter(XMl 등으로부터 가져올 때 사용)를 구분하여 사용한다.

하지만 RecyclerView는 Universal한 Adapter를 사용하여 데이터 소스를 처리한다. 이것은 리싸이클러뷰의 유연성을 보여준다. 다음의 3가지 인터페이스를 구현해야 한다.

  • onCreateViewHolder(ViewGroup parent, int viewType) : 뷰 홀더를 생성하고 뷰를 붙여주는 부분이다.
  • onBindViewHolder(CustomViewHolder holder, int position) : 재활용 되는 뷰가 호출하여 실행되는 메소드, 뷰 홀더를 전달하고 어댑터는 position의 데이터를 결합시킨다.
  • getItemCount() : 데이터의 개수 반환

getItemCount() -> onCreateViewHolder() -> onBindViewHolder() 순으로 호출된다.

리스트뷰가 사용했던 getView() 메소드는 매번 호출되면서 null 처리를 해주는 귀찮은 작업을 해줘야했다면, onCreateViewHolder는 새롭게 생성될 때만 호출된다.

 

2. ViewHolder

 

리스트뷰에서는 뷰홀더 패턴을 권장했다. UI를 수정할 때마다 부르는 findViewById()를 뷰홀더 패턴을 이용해 한번만 호출함으로써 리스트뷰의 지연을 초래하는 무거운 연산을 줄여주었다. 이 문제를 리싸이클러뷰에서는 뷰홀더 패턴을 항상 사용하도록(강제하도록) 함으로써 해결했다.

하지만 실제로 앱의 퍼포먼스를 향상시켜주지만 최신의 디바이스는 뷰홀더 패턴을 사용하지 않은 리스트뷰나 리싸이클러뷰의 성능 차이는 미세하다.

단지 차이점은 리싸이클러뷰는 뷰홀더 패턴이 강제되는 것일 뿐이다. 이전의 리스트뷰는 선택적이었지만 성능 차이가 너무 컸기 때문에 변화된 것으로 생각된다. 간과하기 쉬운 중요한 점은 뷰홀더 패턴을 사용한 리스트뷰와 리싸이클러뷰의 성능은 같다.

 

3. LayoutManager

리스트뷰는 수직 스크롤만 가능하다. 리스트뷰를 수평으로 사용할 수는 없었다. 그것을 구현하기 위한 몇가지 방법이 있지만 리스트뷰는 그렇게 동작하도록 디자인 되지 않았다고 한다.

그러나 이제 리싸이클러뷰에서는 수직뿐만 아니라 수평 스크롤 또한 지원한다. 뿐만 아니라 더 다양한 타입의 리스트들을 지원하고, 커스텀할 수 있도록 해준다. 많은 타입의 리스트를 사용학 위해서 LayoutManager를 사용하면 된다.

 

LayoutManager의 종류

 

1) LinearLayoutManager

 

  • Vertical(가로) / Horizontal(세로) 형태로 아이템을 배치한다.

2) GridLayoutManager

  • 한 줄에 1개 이상의 이미지를 표시할 수 있지만 아이템의 크기는 줄의 첫 번째 아이템의 크기에 따라서 달라질 수 있다.(고정시에는 모두 고정)

3) StaggeredGridLayoutManager

  • 그리드 형태의 아이템에 크기를 다양하게 적용할 수 있다.

4) Custome LayoutManager

  • 3개의 레이아웃 매니저를 상속받아 구현할 수 있다.

5) Item Decorateion

  • 리스트뷰에서는 XML에 파라미터를 추가함으로써 쉽게 divide할 수 있었다. 리싸이클러뷰에서는 RecyclerView.ItemDecoration 클래스를 통해 divider를 원하는 아이템에 추가할 수 있도록 되었다. 조금 복잡해졌지만 동적인 데코레이팅이 가능해졌다.

 

6) Item Animator

Material Design에 대해 조명된 이후로 리스트에서의 애니메이션을 무궁무진한 가능성을 가지게 되었다. 리스트뷰에서는 아이템의 삽입이나 삭제에 특별한 애니메이션이 없었다.

하지만 리싸이클러뷰에서는 RecyclerView.ItemAnimator 클래스를 통해 애니메이션을 핸들링 할 수 있게 되었다. 이 클래스를 통해서 아이템의 삽입, 삭제, 이동에 대한 커스터마이징이 가능하고, 또한 DefaultItemAnimator가 제공되므로 커스터마이징이 필요 없이 사용할 수도 있다.

notifyItemChanged(int position), notifyItemInserted(int position), notifyItemRemoved(int position)을 ItemAnimator을 통해 특정 아이템에 대한 애니메이션을 발생시킬 수 있습니다.

 

7) 클릭 이벤트 처리

터치 이벤트를 통해 사용자가 아이템을 클릭했는지 롱클릭 했는지를 직접처리

RecycleView.OnItemTouchListener은 리싸이클러뷰의 터치 이벤트를 감지한다. 좀 복잡하지만 개발자에게 터치 이벤트를 인터셉트하는 제어권한을 주게 되었다.

안드로이드 공식 문서에서는 터치 이벤트를 인터셉트함으로서 리싸이클러뷰에게 전달하기 전에 조작함으로써 유용하게 사용될 수 있다고 한다.(ListView에서 아이템을 클릭시 콜백 받을 수 있는 리스너는 RecyclerViewd에는 존재하지 않음.)

즉, RecyclerView에는 Click 이벤트에 대한 처리를 자체적으로 할 수 없다. 그래서 onClickListener를 달아줘야 하는 문제가 발생한다.

내가 사용하는 방법으로는 RecyclerView를 사용하는 액티비티에서 View.OnClickListener를 상속받고 그 액티비티의 Context를 RecyclerView.Adapter에서 만든 함수에게 넘긴다.

이 함수는 액티비티에서 받은 Context(여기에 View.OnClickListener이 포함되어 있음)를 Adapter 클래스의 View.OnClickListener 타입의 변수인 ItemClick을 초기화한다.

그러면 Adapter는 ClickListener 정보를 받아서 onCreateViewHolder에서 만든 View를 리턴하기 전에 그 View에게 onClickListener를 붙여주면 된다.

 

8) DiffUtil

DiffUtil은 데이터의 변화를 감지하여 뷰를 갱신하는 클래스이다. areItemsTheSame, areContentsTheSame, getChangedPayload 등을 오버라이딩하여 데이터간 변화를 감지한다.

RecyclerView.Adapter에서 notifyItemMoved, notifyItemRangeChanged, notifyItemRangeInserted,

notifyItemRangeRemoved 등이 호출되면 DiffUtil에서 해당 범위의 데이터의 변화를 감지하고 뷰를 선택적으로 갱신함으로써 비용이 큰 notifyDataSetChanged 대신 효율적으로 사용될 수 있다.

 

 

 

그럼 간단하게 ListView와 RecyclerView의 차이점을 표 형식으로 정리해서 보겠다. 아래의 표는 블로그를 참고했으며 참고 링크는 아래에 적어놓았다.

다음 세가지를 비교/정리

  • ListView
    리스트뷰에서는 BaseAdapter를 상속받은 ArrayAdapter나 CursorAdapter 등을 사용한다.
    ViewHolder 패턴을 선택적으로 구현하기 때문에 구현하지 않는 경우 각각의 View를 그릴 때마다 findViewById()를 호출하기 때문에 성능 저하 문제가 발생한다.
    getView() 메소드에서 뷰를 그릴 때마다 findViewById()를 매번 호출하여 성능이 저하된다.
  • ListView+ViewHolder
    리스트뷰에서 ViewHolder 패턴을 구현한다면 성능에 관해서는 RecyclerView와 비슷하지만 기존의 ListView는 뷰 커스텀 작업에 대한 유연성이 떨어진다.
  • RecyclerView리싸이클러뷰는 ViewHolder 패턴의 사용을 강제하고 Adapter 클래스를 직접 구현하기 때문에 뷰 커스텀 작업에 대한 유연성이 ListView보다 더욱 쉽고 편하다.
반응형

'gyub's 공부일기 > 그저 내 공부' 카테고리의 다른 글

interface 사용 이유  (0) 2021.04.03
자바에서 클래스 다중상속을 막은 이유  (0) 2021.04.03
Lambda식이란  (0) 2021.04.03
Java 메모리 구조 Heap, Data, Stack  (0) 2021.04.03
제네릭  (0) 2021.04.01