RecyclerView로 목록 만들기

RecyclerView로 목록 만들기 Android Jetpack의 구성요소.

앱에서 대량의 데이터 세트 또는 자주 변경되는 데이터에 기반한 요소의 스크롤 목록을 표시해야 한다면 이 페이지에서 설명하는 대로 RecyclerView를 사용하면 됩니다.

팁: File > New > Fragment > Fragment (List)를 클릭하여 Android 스튜디오에서 제공하는 템플릿 코드로 시작해 보세요. 그런 다음 활동 레이아웃에 프래그먼트를 추가하기만 하면 됩니다.

그림 1. RecyclerView를 사용한 목록

그림 2. CardView를 사용한 또 다른 목록

그림 2와 같이 카드로 목록을 만들려면 카드 기반 레이아웃 만들기에 설명한 대로 CardView 위젯을 사용합니다.

RecyclerView의 샘플 코드를 보려면 RecyclerView 샘플 앱 자바 | Kotlin을 확인하세요.

RecyclerView 개요

RecyclerView 위젯은 ListView가 더 진보하고 유연해진 버전입니다.

RecyclerView 모델에서는 여러 다양한 구성요소가 함께 작동하여 데이터를 표시합니다. 사용자 인터페이스의 전체 컨테이너는 레이아웃에 추가하는 RecyclerView 객체입니다. RecyclerView는 개발자가 제공한 레이아웃 관리자에서 제공한 뷰로 채워집니다. 표준 레이아웃 관리자(예: LinearLayoutManager 또는 GridLayoutManager) 중의 하나를 사용하거나 직접 구현할 수 있습니다.

목록의 뷰는 뷰 홀더 객체로 표현됩니다. 이러한 객체는 RecyclerView.ViewHolder를 확장하여 정의한 클래스의 인스턴스입니다. 각 뷰 홀더는 뷰를 사용하여 단일 항목을 표시하는 역할을 합니다. 예를 들어, 목록에서 음악 컬렉션을 표시한다면 각 뷰 홀더는 단일 앨범을 표현할 수도 있습니다. RecyclerView는 동적 콘텐츠에서 화면에 나타나는 부분을 표시하는 데 필요한 뷰 홀더와 몇 개의 추가 뷰 홀더만큼만 만들면 됩니다. 사용자가 목록을 스크롤하면 RecyclerView는 화면에 나타나지 않는 뷰를 가져와서 화면에 스크롤되는 데이터와 다시 바인딩합니다.

뷰 홀더 객체는 RecyclerView.Adapter를 확장하여 만든 어댑터에서 관리합니다. 어댑터는 필요에 따라 뷰 홀더를 만듭니다. 또한 어댑터는 뷰 홀더를 데이터에 바인딩합니다. 이 작업은 뷰 홀더를 특정 위치에 할당하고 어댑터의 onBindViewHolder() 메서드를 호출하여 실행됩니다. 이 메서드는 뷰 홀더의 위치를 사용하여 목록 위치를 기반으로 콘텐츠를 결정합니다.

RecyclerView 모델은 많은 최적화 작업을 하므로 다음을 실행할 필요가 없습니다.

  • 목록이 처음 게재되면 목록의 측면에 뷰 홀더를 만들고 바인딩합니다. 예를 들어, 뷰가 목록 위치를 0에서 9로 표시하면 RecyclerView는 목록 위치의 뷰 홀더를 만들고 바인딩하며 위치 10의 뷰 홀더를 만들고 바인딩할 수도 있습니다. 이러한 방식으로, 사용자가 목록을 스크롤할 경우 다음 요소가 표시될 준비가 됩니다.
  • 사용자가 목록을 스크롤하면 RecyclerView는 필요에 따라 새 뷰 홀더를 만듭니다. 또한 화면 밖으로 스크롤된 뷰 홀더를 재사용할 수 있도록 저장합니다. 사용자가 스크롤 방향을 바꾸면 화면 밖으로 스크롤됐던 뷰 홀더는 곧바로 되돌아올 수 있습니다. 반면, 사용자가 같은 방향으로 스크롤을 계속하면 가장 오래전에 화면 밖으로 스크롤된 뷰 홀더는 새 데이터로 다시 바인딩될 수 있습니다. 뷰 홀더를 만들거나 뷰 홀더의 뷰를 확장할 필요는 없습니다. 대신 앱은 바인딩된 새 항목과 일치하도록 뷰 콘텐츠를 업데이트하기만 하면 됩니다.
  • 표시된 항목이 변경되면 적절한 RecyclerView.Adapter.notify…() 메서드를 호출하여 어댑터에 알려줄 수 있습니다. 그런 다음 어댑터의 내장 코드는 영향을 받은 항목만 다시 바인딩합니다.

지원 라이브러리 추가

RecyclerView 위젯에 액세스하려면 다음과 같이 프로젝트에 v7 지원 라이브러리를 추가해야 합니다.

  1. 앱 모듈의 build.gradle 파일 열기
  2. dependencies 섹션에 지원 라이브러리 추가
        dependencies {
            implementation 'com.android.support:recyclerview-v7:28.0.0'
        }
        

레이아웃에 RecyclerView 추가

이제 레이아웃 파일에 RecyclerView를 추가할 수 있습니다. 예를 들어, 다음 레이아웃은 RecyclerView를 전체 레이아웃의 유일한 뷰로 사용합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <!-- A RecyclerView with some commonly used attributes -->
    <android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    

레이아웃에 RecyclerView 위젯을 추가하면 다음과 같이 객체의 핸들을 가져와서 레이아웃 관리자에 추가하고 표시될 데이터의 어댑터를 연결합니다.

Kotlin

    class MyActivity : Activity() {
        private lateinit var recyclerView: RecyclerView
        private lateinit var viewAdapter: RecyclerView.Adapter<*>
        private lateinit var viewManager: RecyclerView.LayoutManager

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.my_activity)

            viewManager = LinearLayoutManager(this)
            viewAdapter = MyAdapter(myDataset)

            recyclerView = findViewById<RecyclerView>(R.id.my_recycler_view).apply {
                // use this setting to improve performance if you know that changes
                // in content do not change the layout size of the RecyclerView
                setHasFixedSize(true)

                // use a linear layout manager
                layoutManager = viewManager

                // specify an viewAdapter (see also next example)
                adapter = viewAdapter

            }
        }
        // ...
    }
    

자바

    public class MyActivity extends Activity {
        private RecyclerView recyclerView;
        private RecyclerView.Adapter mAdapter;
        private RecyclerView.LayoutManager layoutManager;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.my_activity);
            recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);

            // use this setting to improve performance if you know that changes
            // in content do not change the layout size of the RecyclerView
            recyclerView.setHasFixedSize(true);

            // use a linear layout manager
            layoutManager = new LinearLayoutManager(this);
            recyclerView.setLayoutManager(layoutManager);

            // specify an adapter (see also next example)
            mAdapter = new MyAdapter(myDataset);
            recyclerView.setAdapter(mAdapter);
        }
        // ...
    }
    

목록 어댑터 추가

모든 데이터를 목록에 피드하려면 RecyclerView.Adapter 클래스를 확장해야 합니다. 이 객체는 항목의 뷰를 만들고 원래의 항목이 더 이상 표시되지 않을 때 일부 뷰의 콘텐츠를 새 데이터 항목으로 교체합니다.

다음 코드 예는 TextView 위젯을 사용하여 표시된 문자열 배열을 구성하는 데이터 세트의 간단한 구현을 보여줍니다.

Kotlin

    class MyAdapter(private val myDataset: Array<String>) :
            RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

        // Provide a reference to the views for each data item
        // Complex data items may need more than one view per item, and
        // you provide access to all the views for a data item in a view holder.
        // Each data item is just a string in this case that is shown in a TextView.
        class MyViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)

        // Create new views (invoked by the layout manager)
        override fun onCreateViewHolder(parent: ViewGroup,
                                        viewType: Int): MyAdapter.MyViewHolder {
            // create a new view
            val textView = LayoutInflater.from(parent.context)
                    .inflate(R.layout.my_text_view, parent, false) as TextView
            // set the view's size, margins, paddings and layout parameters
            ...
            return MyViewHolder(textView)
        }

        // Replace the contents of a view (invoked by the layout manager)
        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            // - get element from your dataset at this position
            // - replace the contents of the view with that element
            holder.textView.text = myDataset[position]
        }

        // Return the size of your dataset (invoked by the layout manager)
        override fun getItemCount() = myDataset.size
    }
    

자바

    public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
        private String[] mDataset;

        // Provide a reference to the views for each data item
        // Complex data items may need more than one view per item, and
        // you provide access to all the views for a data item in a view holder
        public static class MyViewHolder extends RecyclerView.ViewHolder {
            // each data item is just a string in this case
            public TextView textView;
            public MyViewHolder(TextView v) {
                super(v);
                textView = v;
            }
        }

        // Provide a suitable constructor (depends on the kind of dataset)
        public MyAdapter(String[] myDataset) {
            mDataset = myDataset;
        }

        // Create new views (invoked by the layout manager)
        @Override
        public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent,
                                                       int viewType) {
            // create a new view
            TextView v = (TextView) LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.my_text_view, parent, false);
            ...
            MyViewHolder vh = new MyViewHolder(v);
            return vh;
        }

        // Replace the contents of a view (invoked by the layout manager)
        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            // - get element from your dataset at this position
            // - replace the contents of the view with that element
            holder.textView.setText(mDataset[position]);

        }

        // Return the size of your dataset (invoked by the layout manager)
        @Override
        public int getItemCount() {
            return mDataset.length;
        }
    }
    

레이아웃 관리자가 어댑터의 onCreateViewHolder() 메서드를 호출합니다. 이 메서드는 RecyclerView.ViewHolder를 생성하고 뷰 홀더가 콘텐츠를 표시하기 위해 사용하는 뷰를 설정해야 합니다. ViewHolder의 유형은 Adapter 클래스 서명에 선언된 유형과 일치해야 합니다. 일반적으로 XML 레이아웃 파일을 확장하여 뷰를 설정합니다. 뷰 홀더는 아직 특정 데이터에 할당되지 않았으므로 이 메서드에서 실제로 뷰 콘텐츠를 설정하지는 않습니다.

그런 다음, 레이아웃 관리자는 뷰 홀더를 데이터에 바인딩합니다. 레이아웃 관리자는 어댑터의 onBindViewHolder() 메서드를 호출하고 RecyclerView에 뷰 홀더의 위치를 전달하여 이 작업을 실행합니다. onBindViewHolder() 메서드는 적절한 데이터를 가져오고 그 데이터를 사용하여 뷰 홀더의 레이아웃을 채워야 합니다. 예를 들어, RecyclerView가 이름 목록을 표시한다면 메서드는 목록에서 적절한 이름을 찾고 뷰 홀더의 TextView 위젯에 채울 것입니다.

목록이 업데이트가 필요하다면 RecyclerView.Adapter 객체의 알림 메서드(예: notifyItemChanged())를 호출합니다. 그런 다음 레이아웃 관리자는 영향을 받은 모든 뷰 홀더를 다시 바인딩하여 데이터가 업데이트되도록 합니다.

팁: ListAdapter 클래스는 목록이 변경될 때 업데이트되어야 하는 항목을 결정하는 데 유용합니다.

RecyclerView 맞춤설정

RecyclerView 객체를 맞춤설정하여 구체적인 필요를 충족할 수 있습니다. 표준 클래스는 대부분의 개발자가 필요로 하는 모든 기능을 제공합니다. 많은 경우 개발자가 해야 하는 맞춤설정은 각 뷰 홀더의 뷰를 설계하고 적절한 데이터를 포함하는 뷰를 업데이트하기 위해 코드를 작성하는 것입니다. 하지만 앱에 구체적인 요구 사항이 있다면 여러 가지 방법으로 표준 동작을 수정할 수 있습니다. 다음 섹션에서는 기타 일반적인 맞춤설정의 일부를 설명합니다.

레이아웃 수정

RecyclerView는 레이아웃 관리자를 사용하여 화면에서 개별 항목의 위치를 정하고 사용자에게 더 이상 보이지 않는 항목 뷰를 재사용할 시점을 결정합니다. 뷰를 재사용(또는 재활용)하려면 레이아웃 관리자는 어댑터에 데이터 세트의 다른 요소를 사용하여 뷰의 콘텐츠를 교체하도록 요청할 수 있습니다. 이러한 방식으로 뷰를 재활용하면 불필요한 뷰의 생성을 피하거나 비싼 findViewById() 조회를 실행하여 성능이 향상됩니다. Android 지원 라이브러리에는 다음의 세 가지 표준 레이아웃 관리자가 포함되며 각 관리자는 다양한 맞춤설정 옵션을 제공합니다.

조건에 맞는 레이아웃 관리자가 없다면 RecyclerView.LayoutManager 추상 클래스를 확장하여 고유의 레이아웃 관리자를 만들 수 있습니다.

항목 애니메이션 추가

항목이 변경될 때마다 RecyclerView애니메이터를 사용하여 모양을 변경합니다. 이 애니메이터는 RecyclerView.ItemAnimator 추상 클래스를 확장한 객체입니다. 기본적으로 RecyclerViewDefaultItemAnimator를 사용하여 애니메이션을 제공합니다. 맞춤 애니메이션을 제공하려면 RecyclerView.ItemAnimator를 확장하여 고유의 애니메이터 객체를 정의하면 됩니다.

목록 항목 선택 사용 설정

recyclerview-selection 라이브러리는 사용자가 터치 또는 마우스 입력을 사용하여 RecyclerView 목록에서 항목을 선택할 수 있도록 합니다. 선택된 항목의 시각적 표현을 제어할 수 있습니다. 또한 선택 동작을 제어하는 정책(예: 선택할 수 있는 항목 및 선택할 수 있는 항목의 개수)을 제어할 수도 있습니다.

RecyclerView 인스턴스에 선택 지원을 추가하려면 다음 단계를 따르세요.

  1. 사용할 선택 키 유형을 결정한 다음 ItemKeyProvider를 빌드

    선택된 항목을 식별하는 데 사용할 수 있는 키 유형은 Parcelable(Uri 같은 모든 서브클래스 포함), StringLong 세 가지가 있습니다. 선택 키 유형에 관한 자세한 내용은 SelectionTracker.Builder를 참조하세요.

  2. ItemDetailsLookup 구현
  3. ItemDetailsLookupMotionEvent를 통해 선택 라이브러리가 RecyclerView 항목에 관한 정보에 액세스할 수 있도록 합니다. 이는 사실상 RecyclerView.ViewHolder 인스턴스에 의해 백업된(또는 인스턴스에서 추출된) ItemDetails 인스턴스의 팩토리입니다.

  4. RecyclerView에서 항목 Views를 업데이트하여 사용자가 항목을 선택하거나 선택 해제한 것을 반영

    선택 라이브러리는 선택된 항목에 기본적인 시각 장식을 제공하지 않습니다. onBindViewHolder()를 구현할 때 이를 제공해야 합니다. 추천하는 방법은 다음과 같습니다.

  5. ActionMode를 사용하여 사용자에게 선택에 관한 작업을 실행할 수 있는 도구를 제공합니다.
  6. SelectionTracker.SelectionObserver를 등록하여 선택이 변경될 때 알림을 받습니다. 처음 선택을 만들 때 ActionMode를 시작하여 사용자에게 선택을 표시하고 선택에 특화된 작업을 제공합니다. 예를 들어, 삭제 버튼을 ActionMode 막대에 추가하고 막대에 있는 뒤로 화살표에 연결하여 선택을 지울 수 있습니다. 선택한 항목이 없어지면(사용자가 마지막 선택을 지운 경우) 작업 모드를 종료해야 합니다.

  7. 해석된 모든 보조 작업을 실행
  8. 이벤트 처리 파이프라인의 끝에서 라이브러리는 사용자가 항목을 탭하여 활성화하는 것을 시도하거나 항목 또는 선택된 항목 집합을 드래그 앤 드롭하는 것을 시도하도록 결정할 수 있습니다. 적절한 리스너를 등록하여 이러한 해석에 반응합니다. 자세한 내용은 SelectionTracker.Builder를 참조하세요.

  9. SelectionTracker.Builder를 사용하여 모두 조립
  10. 다음 예는 Long 선택 키를 사용하여 이러한 조각들을 합하는 방법을 보여줍니다.

    Kotlin

        var tracker = SelectionTracker.Builder(
            "my-selection-id",
            recyclerView,
            StableIdKeyProvider(recyclerView),
            MyDetailsLookup(recyclerView),
            StorageStrategy.createLongStorage())
                .withOnItemActivatedListener(myItemActivatedListener)
                .build()
        

    자바

        SelectionTracker tracker = new SelectionTracker.Builder<>(
                "my-selection-id",
                recyclerView,
                new StableIdKeyProvider(recyclerView),
                new MyDetailsLookup(recyclerView),
                StorageStrategy.createLongStorage())
                .withOnItemActivatedListener(myItemActivatedListener)
                .build();
        

    SelectionTracker 인스턴스를 빌드하려면 앱은 RecyclerView를 초기화하는 데 사용했던 동일한 RecyclerView.AdapterSelectionTracker.Builder에 제공해야 합니다. 이런 이유로, RecyclerView.Adapter가 생성된 후 SelectionTracker 인스턴스가 생성되면 RecyclerView.Adapter에 삽입해야 합니다. 그렇게 하지 않으면 onBindViewHolder() 메서드에서 항목의 선택 상태를 확인할 수 없습니다.

  11. 액티비티 수명 주기 이벤트에 선택 포함
  12. 액티비티 수명 주기 이벤트에 걸쳐 선택 상태를 보존하려면 앱은 활동의 onSaveInstanceState()onRestoreInstanceState() 메서드 각각에서 선택 추적기의 onSaveInstanceState()onRestoreInstanceState() 메서드를 호출해야 합니다. 또한 앱에서 고유한 선택 ID를 SelectionTracker.Builder 생성자에 제공해야 합니다. 활동 또는 프래그먼트에는 모두 저장된 상태로 유지되어야 하는 두 개 이상의 구별된 선택 가능 목록이 있을 수 있기 때문에 이 ID는 필수입니다.

    추가 리소스

    RecyclerView해바라기 디자인 데모 앱에서 사용됩니다.