ANDROID 프로그래밍/RECYCLERVIEW

안드로이드 리사이클러뷰 기본 사용법. (Android RecyclerView)

뽀따 2019. 3. 13. 17:39


1. 안드로이드 리사이클러뷰(RecyclerView)

리사이클러뷰(RecyclerView)는, "많은 수의 데이터 집합을, 제한된 영역 내에서 유연하게(flexible) 표시할 수 있도록 만들어주는 위젯"입니다.


[안드로이드 개발 참조문서. RecyclerView]에 소개된 문장(A flexible view for providing a limited window into a large data set.)을 살짝 번역해봤는데, 리사이클러뷰가 어떤 것인지 머리 속에 그려지시나요? 음, 손에 잡힐듯 말듯, 조금 아쉬운 느낌입니다. 설명을 조금 덧붙여보겠습니다.


리사이클러뷰(RecyclerView)는 "사용자가 관리하는 많은 수의 데이터 집합(Data Set)을 개별 아이템 단위로 구성하여 화면에 출력하는 뷰그룹(ViewGroup)이며, 한 화면에 표시되기 힘든 많은 수의 데이터를 스크롤 가능한 리스트로 표시해주는 위젯"입니다.

문장을 하나 더 추가해봤는데요, 리사이클러뷰의 형태가 조금은 더 명확해지죠?


그런데 위의 문장을 읽어보면, 어디선가 이미 한번 봤던 내용이라는 것을 알 수 있습니다. "데이터 집합", "아이템 단위", "뷰그룹", "스크롤", "리스트"... 바로 [안드로이드 리스트뷰 기본 사용법]에서 봤던 "리스트뷰(ListView)"에 대한 내용들이죠.


기본적으로 리사이클러뷰(RecyclerView)는 리스트뷰(ListView)와 "사용 목적" 및 "동작 방식"이 매우 유사한 요소입니다.


리사이클러뷰(RecyclerView)는 기존에 리스트 형태의 화면 구성에 사용되던 리스트뷰(ListView)에 "유연함(Flexibility)"과 "성능(Performance)"을 더한, 리스트뷰의 확장판 또는 개선판이라고 볼 수 있습니다. 구글에서도 현재는 리스트뷰 대신 리사이클러뷰를 "리스트 표시를 위한 UI 구성"에 사용하도록 권고하고 있습니다.

1.1 리사이클러(Recycler)

그런데 왜 위젯 이름을 리사이클러뷰(RecyclerView)라고 지었을까요? 리스트뷰(ListView)의 개선판이라고 하면, "AdvancedListView" 또는 "ExListView"와 같이 조금 더 직관적인(?) 이름을 붙일만도 한데 말이죠. "Recycler"라는 단어에서 "List"와의 연관성은 쉽게 떠올려지지 않습니다. 이 의문을 조금이나마 해소하고 리사이클러뷰의 특징을 조금 더 쉽게 이해하기 위해서는, "Recycler"라는 단어의 사전적 의미와 리스트뷰의 단점에 대해 언급할 필요가 있을 것 같네요.


"Recycler"라는 단어의 사전적 의미는 "재생처리기", "재활용하는 사람" 입니다. 그리고 "재활용"이라는 말은 "한번 사용했던 것을 다시 사용한다"라는 의미를 가진 단어죠. 그럼 리사이클러뷰는 뭘 재활용한다는 걸까요?


1.2 리사이클러뷰(RecyclerView)의 재활용(Recycle).

리스트뷰(ListView)의 경우, 기본 가이드에 따라 구현했을 때 만날 수 있는 문제점 중의 하나는, 리스트 항목이 갱신될 때마다, 매번 아이템 뷰를 새로 구성해야 한다는 것이었습니다. 이는 "많은 수의 데이터 집합을 표시"하는데 있어서, 성능 저하를 야기할 수 있는 요인이 됩니다.


물론, 뷰홀더(ViewHolder) 패턴을 선택적으로 적용하여 성능 저하 문제를 해결할 순 있지만, 말 그대로 "선택적"으로 적용할 수 있는 사항이고, "다중 아이템 뷰"와 같은 요구사항이 더해지게 되면 코드 작성에서 고려해야 할 사항들이 훨씬 복잡해질 수 있습니다.


이러한 리스트뷰의 단점을 참고하여, 리사이클러뷰는 아이템을 표시하기 위해 생성한 뷰를 재활용(recycle)합니다. 그리고 이를 위해 기본적으로 뷰홀더(ViewHolder) 패턴을 사용하도록 만들어 놓았습니다.


뷰홀더(ViewHolder)가 필수 구현 사항으로 만들어졌다는 말은, 리사이클러뷰에 단순히 뷰홀더가 포함된 것을 넘어, 개발자가 직접 뷰홀더 패턴을 적용할 때 고민해야 했던 여러 이슈들이 리사이클러뷰 구현 사항에 고려되었다는 것을 의미합니다. 이는 특히, 리사이클러뷰의 가장 큰 장점인 "유연함(Flexibility)"에 대한 고려도 충분히 이루어졌다는 것을 의미하죠.


그런데 잠깐, 위에서 계속 리사이클러뷰의 장점으로 "유연함(Flexibility)"을 강조하는데요, 여기서 "유연(Flexiable)하다"는 것은 어떤 의미일까요? 리사이클러뷰의 어떤 점이?

1.3 리사이클러뷰(RecyclerView)의 유연함(Flexibility)

"유연함"이라는 뜻의 "Flexibility"는, 프로그래밍 분야에서, "구현 요소" 또는 구현에 따른 "결과물"이 쉽게 변경되거나 확장될 수 있음을 의미합니다. 이 내용을 리사이클러뷰에 적용해보자면, "리사이클러뷰의 유연함(Flexibility)이란, 리사이클러뷰의 구현 요소 또는 구현에 따른 결과물이 쉽게 변경되거나 확장될 수 있음"으로 서술될 수 있겠네요.


잠시, 리스트뷰의 내용으로 돌아가 볼까요? 리스트뷰가 "많은 수의 데이터 목록을 아이템 단위로 구성하여 화면에 표시"함에 있어 매우 유용한 건 맞지만, 한계가 있는 것 또한 사실입니다.


먼저, 리스트뷰의 기본 구현 내에서는 아이템들을 수직(Vertical) 방향으로만 나열할 수 있습니다. 만약 수평(Horizontal) 방향으로 아이템들을 나열하려면, 리스트뷰가 아닌 다른 뷰를 사용하거나, 리스트뷰의 기능을 상당 부분 재 구현 해야하죠. 그리고 아이템 뷰를 동적으로(Dynamically) 구성하기가 쉽지 않은 한계도 있습니다. 사용자의 선택에 따라 아이템 뷰를 완전히 새로운 형태로 바꾸고자 한다면, 모든 상황에 대한 대응을 어댑터 내에서 개발자가 직접 처리해야 합니다. 어떠한 가이드도 없이 말이죠.


하지만 리사이클러뷰는 이러한 단점들을 보완하여, 개발자가 쉽게 구현할 수 있도록 만들어줍니다. 수직(Vertical) 뿐만 아니라 수평(Horizontal) 방향으로 아이템들이 나열되게 만들 수 있고, 아이템 뷰의 동적(Dynamic) 구성을 용이하게 만들어주며, 이를 런타임(run-time)에 바꾸게 만들 수도 있습니다.


이런 특징들이 바로, 리사이클러뷰의 "유연함(Flexibility)"을 나타내는 장점들이죠.


그렇다면, 리사이클러뷰는 어떤 구조를 통해 이런 장점들을 제공하는 걸까요?

2. 리사이클러뷰(RecyclerView)를 위한 구성 요소.

"리사이클러뷰(RecyclerView)"는 다른 "리스트 표시를 위한 요소"들이 그러하듯, 데이터 목록을 아이템 단위의 뷰로 구성하여 화면에 표시하기 위해 "어댑터(Adapter)"를 사용합니다.


그런데 화면에 표시될 아이템 뷰들을 수직(Vertical) 방향으로, 일렬로 나열하는 리스트뷰와 달리, 리사이클러뷰는 수평(Horizontal) 방향 레이아웃 또는 격자(Grid) 형태의 레이아웃으로도 나타낼 수 있습니다. (물론, 이보다 더 다양하고 복잡한 형태의 레이아웃으로 직접 커스터마이징할 수도 있습니다.) 이를 위해 리사이클러뷰에서는 아이템 뷰가 나열되는 형태를 관리하기 위한 요소를 제공하는데, 이를 "레이아웃매니저(Layout Manager)"라고 부릅니다.


마지막으로 레이아웃매니저(LayoutManager)가 제공하는 레이아웃 형태로, 어댑터를 통해 만들어진 각 아이템 뷰는 "뷰홀더(ViewHolder)"객체에 저장되어 화면에 표시되고, 필요에 따라 생성 또는 재활용(Recycle)됩니다.


리사이클러뷰의 각 구성 요소가 어떤 구조로 처리되는지는, 아래 그림을 참고하시면 조금 더 이해하기 쉬울 거라 생각합니다.


2.1 리사이클러뷰(RecyclerView)

리사이클러뷰는 v7 지원 라이브러리(v7 Support Library)에서 제공되는 위젯으로, 앞서 소개한 구성 요소들을 통해, 사용자 데이터를 리스트 형태로 화면에 표시하는 컨테이너 역할을 수행합니다. (android.support.v7.widget.RecyclerView)


2.2 어댑터(Adpater)

리사이클러뷰에 표시될 아이템 뷰를 생성하는 역할은 어댑터가 담당합니다. 사용자 데이터 리스트로부터 아이템 뷰를 만드는 것, 그것이 바로 어댑터가 하는 역할이죠. (android.support.v7.widget.RecyclerView.Adapter)


하지만 앞서도 언급했듯이, 수직(Vertical) 방향으로 아이템을 배치할 수 있는 리스트뷰와 다르게, 리사이클러뷰는 다양한 형태로 아이템을 배치할 수 있죠. 이를 위해, 어댑터에서 아이템 뷰를 생성하기 이전에, 어떤 형태로 배치될 아이템 뷰를 만들 것인지를 결정하는 요소가 제공되는데, 바로 레이아웃매니저(LayoutManager)입니다.

2.3 레이아웃매니저(LayoutManager)

레이아웃매니저는 리사이클러뷰가 아이템을 화면에 표시할 때, 아이템 뷰들이 리사이클러뷰 내부에서 배치되는 형태를 관리하는 요소입니다. (android.support.v7.widget.RecyclerView.LayoutManager)


안드로이드 SDK에서는 다음과 같은 레이아웃매니저들이 기본으로 제공되고 있으며, 각 레이아웃매니저가 아이템 뷰들을 배치하는 형태는 아래 그림과 같습니다.


  • 리니어(LinearLayoutManager) : 수평(Horizontal) 또는 수직(Vertical) 방향, 일렬(Linear)로 아이템 뷰 배치.
  • 그리드(GridLayoutManager) : 바둑판 모양의 격자(Grid) 형태로 아이템 뷰 배치.
  • 스태거드그리드(StaggeredGridLayoutManager) : 엇갈림(Staggered) 격자(Grid) 형태로 아이템 뷰 배치.


그리고 레이아웃매니저는 더 이상 화면에 표시되지 않는(no longer visible to the user) 아이템 뷰를 언제 재활용(recycle)할 것인지에 대한 정책도 결정합니다.

2.4 뷰홀더(ViewHolder)

뷰홀더(ViewHolder)는 화면에 표시될 아이템 뷰를 저장하는 객체입니다. (android.support.v7.widget.RecyclerView.ViewHolder)


어댑터에 의해 관리되는데, 필요에 따라(좀 더 정확히는, 레이아웃매니저의 아이템 뷰 재활용 정책에 따라) 어댑터에서 생성됩니다. 물론, 미리 생성된 뷰홀더 객체가 있는 경우에는 새로 생성하지 않고 이미 만들어져 있는 뷰홀더를 재활용하는데, 이 때는 단순히 데이터가 뷰홀더의 아이템 뷰에 바인딩(Binding)됩니다.


3. 리사이클러뷰(RecyclerView) 기본 사용법.

자, 그럼 이제 간단한 예제 작성을 통해, 리사이클러뷰를 사용하는 기본적인 방법에 대해 알아보도록 하겠습니다.


예제 화면은 아래 그림과 같이, 각 아이템이 텍스트뷰 하나로 구성된 형태로 데이터 리스트를 표시합니다.


화면의 형태는 가장 기본적인 리스트 표시 형태인데, [안드로이드 리스트뷰 기본 사용법]에서 작성했던 예제와 표시 형태가 동일합니다. 리스트뷰를 사용해봤던 경험이 있다면, 두 가지 예제 코드 비교하여 그 차이를 식별하는 것도 리사이클러뷰를 이해하는데 많은 도움이 되실거라 생각합니다.

3.1 워크플로우

리사이클러뷰 기본 사용법 예제 작성을 위한 워크플로우는 아래와 같습니다. 


참고로 아래의 예제는, 안드로이드 스튜디오 프로젝트 생성 단계에서 "Basic Activity"를 선택하여 생성된 코드를 기반으로 작성되었습니다.

3.2 메인액티비티에 리사이클러뷰 추가.

가장 먼저, 메인액티비티에 리사이클러뷰를 추가합니다. 리사이클러뷰가 v7 지원 라이브러리(v7 Support Library)에서 지원되는 위젯이라는 것을 기억하세요.

[STEP-1] "content_main.xml" - 메인액티비티 레이아웃 리소스 XML에 리사이클러뷰 추가.
<android.support.v7.widget.RecyclerView
    android:id="@+id/recycler1"
    android:scrollbars="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

3.3 리사이클러뷰 아이템 뷰 레이아웃 추가.

다음, 리사이클러뷰 아이템에 표시될 아이템 뷰 레이아웃을 추가합니다. 앞서 설계한 화면과 같이, 아이템 뷰는 텍스트뷰 위젯 하나만 가집니다.

[STEP-2] "recyclerview_item.xml" - 아이템 뷰를 위한 리소스 레이아웃 XML 작성.

<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/text1" android:textSize="32sp"/> </android.support.constraint.ConstraintLayout>

3.4 리사이클러뷰 어댑터 구현.

이제 리사이클러뷰 어댑터를 만들텐데요. 리스트뷰의 경우, 어댑터를 사용할 때 안드로이드 SDK에서 제공되는 몇 가지 어댑터 중 하나를 선택하거나, 필요한 경우(커스텀 리스트뷰)에 BaseAdapter 클래스를 상속받아 새로운 어댑터를 만들었던 것에 반해, 리사이클러뷰에서는 반드시 개발자가 어댑터를 직접 구현해야 합니다. 그리고 이 때 새로 만드는 어댑터는 RecyclerView.Adapter를 상속하여 구현해야 합니다.


RecyclerView.Adapter를 상속받아 새로운 어댑터를 만들 때, 오버라이드가 필요한 메서드는 아래와 같습니다.


메서드 설명
onCreateViewHolder(ViewGroup parent, int viewType) viewType 형태의 아이템 뷰를 위한 뷰홀더 객체 생성.
onBindViewHolder(ViewHolder holder, int position) position에 해당하는 데이터를 뷰홀더의 아이템뷰에 표시.
getItemCount() 전체 아이템 갯수 리턴.


어댑터는 아래 코드와 같이 작성합니다.
[STEP-3] "SimpleTextAdapter.java" - 리사이클러뷰 어댑터 작성.
public class SimpleTextAdapter extends RecyclerView.Adapter<SimpleTextAdapter.ViewHolder> {

    private ArrayList<String> mData = null ;

    // 아이템 뷰를 저장하는 뷰홀더 클래스.
    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView textView1 ;

        ViewHolder(View itemView) {
            super(itemView) ;

            // 뷰 객체에 대한 참조. (hold strong reference)
            textView1 = itemView.findViewById(R.id.text1) ;
        }
    }

    // 생성자에서 데이터 리스트 객체를 전달받음.
    SimpleTextAdapter(ArrayList<String> list) {
        mData = list ;
    }

    // onCreateViewHolder() - 아이템 뷰를 위한 뷰홀더 객체 생성하여 리턴.
    @Override
    public SimpleTextAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Context context = parent.getContext() ;
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) ;

        View view = inflater.inflate(R.layout.recyclerview_item, parent, false) ;
        SimpleTextAdapter.ViewHolder vh = new SimpleTextAdapter.ViewHolder(view) ;

        return vh ;
    }

    // onBindViewHolder() - position에 해당하는 데이터를 뷰홀더의 아이템뷰에 표시.
    @Override
    public void onBindViewHolder(SimpleTextAdapter.ViewHolder holder, int position) {
        String text = mData.get(position) ;
        holder.textView1.setText(text) ;
    }

    // getItemCount() - 전체 데이터 갯수 리턴.
    @Override
    public int getItemCount() {
        return mData.size() ;
    }
}

위 코드를 보면, 어댑터 내에서 뷰홀더를 위한 클래스를 구현한 것을 확인할 수 있습니다. RecyclerView.ViewHolder를 상속받아서 말이죠. 그리고 해당 뷰홀더에서는 아이템에 표시될 텍스트뷰(textView1)에 대한 참조를 가지도록 만들었습니다.


이렇게 작성한 뷰홀더는 어댑터의 onCreateViewHolder()onBindViewHolder() 메서드를 통해 각각 생성 및 바인딩(데이터 표시)되어 화면에 표시됩니다.

3.5 리사이클러뷰에 어댑터와 레이아웃매니저 지정하기.

이제 마지막으로 남은 일은, 위에서 작성한 어댑터에 대한 객체와 레이아웃매니저의 객체를 생성한 다음, 각 객체를 setAdapter() 메서드와 setLayoutManager() 메서드를 통해 리사이클러뷰에 지정하는 것입니다.

[STEP-4] "MainActivity.java" - 리사이클러뷰에 어댑터와 레이아웃매니저 지정.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ... 코드 계속

        // 리사이클러뷰에 표시할 데이터 리스트 생성.
        ArrayList<String> list = new ArrayList<>();
        for (int i=0; i<100; i++) {
            list.add(String.format("TEXT %d", i)) ;
        }

        // 리사이클러뷰에 LinearLayoutManager 객체 지정.
        RecyclerView recyclerView = findViewById(R.id.recycler1) ;
        recyclerView.setLayoutManager(new LinearLayoutManager(this)) ;

        // 리사이클러뷰에 SimpleTextAdapter 객체 지정.
        SimpleTextAdapter adapter = new SimpleTextAdapter(list) ;
        recyclerView.setAdapter(adapter) ;
    }

위의 코드에서 레이아웃매니저를 지정할 때, LinearLayoutManager 객체를 사용한 것을 볼 수 있습니다. 예제 화면 설계 시, 수직(Vertical) 방향으로 나열되도록 설계했기 때문에 LinearLayoutManager을 사용한 것인데요, 특히 LinearLayoutManager의 경우 orientation 기본 값이 "VERTICAL"이므로 객체를 생성하면 수직(Vertical) 방향으로 아이템을 표시할 수 있습니다. 만약 수평(Horizontal) 방향으로 아이템을 배치하려면, 아래 코드처럼 객체 생성 시 아이템 배치 방향을 수평(LinearLayoutManager.HORIZONTAL) 방향으로 지정하면 됩니다.

    recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)) ;

4. 예제 실행 화면.

예제 작성을 완료한 다음, 빌드하고 실행하면 아래와 같은 화면이 표시됩니다. 아래위 화면 스크롤을 통해 화면에 표시되는 데이터가 변경되는 것을 확인할 수 있습니다.


5. 참고.

.END.