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

Compose 사용해 보기
Jetpack Compose는 Android를 위한 권장 UI 도구 키트입니다. Compose에서 레이아웃을 사용하는 방법을 알아보세요.

RecyclerView를 사용하면 대량의 데이터 세트를 효율적으로 표시할 수 있습니다. 개발자가 데이터를 제공하고 각 항목의 모양을 정의하면 RecyclerView 라이브러리가 필요할 때 요소를 동적으로 생성합니다.

이름에서 알 수 있듯이 RecyclerView는 이러한 개별 요소를 재활용합니다. 항목이 스크롤되어 화면에서 벗어나더라도 RecyclerView는 뷰를 제거하지 않습니다. 대신 RecyclerView는 화면에서 스크롤된 새 항목의 뷰를 재사용합니다. RecyclerView는 성능과 앱의 응답성을 개선하고 전력 소모를 줄입니다.

주요 클래스

동적 목록을 만드는 데는 여러 클래스가 함께 사용됩니다.

  • RecyclerView는 데이터에 해당하는 뷰가 포함된 ViewGroup입니다. 이는 뷰 자체이므로, 다른 UI 요소를 추가할 때처럼 레이아웃에 RecyclerView를 추가하면 됩니다.

  • 목록의 각 개별 요소는 뷰 홀더 객체로 정의됩니다. 뷰 홀더가 생성되었을 때는 뷰 홀더에 연결된 데이터가 없습니다. 뷰 홀더가 생성된 후 RecyclerView가 뷰 홀더를 뷰의 데이터에 바인딩합니다. RecyclerView.ViewHolder를 확장하여 뷰 홀더를 정의할 수 있습니다.

  • RecyclerView는 뷰를 요청한 다음, 어댑터에서 메서드를 호출하여 뷰를 뷰의 데이터에 바인딩합니다. RecyclerView.Adapter를 확장하여 어댑터를 정의할 수 있습니다.

  • 레이아웃 관리자는 목록의 개별 요소를 정렬합니다. RecyclerView 라이브러리에서 제공하는 레이아웃 관리자 중 하나를 사용하거나 레이아웃 관리자를 직접 정의할 수도 있습니다. 레이아웃 관리자는 모두 라이브러리의 LayoutManager 추상 클래스를 기반으로 합니다.

모든 요소가 RecyclerView 샘플 앱(Kotlin) 또는 RecyclerView 샘플 앱(Java)에서 조화를 이루며 작동하는지 확인할 수 있습니다.

RecyclerView 구현 단계

RecyclerView를 사용하려면 몇 가지 작업을 실행해야 합니다. 세부정보는 다음 섹션에 설명되어 있습니다.

  1. 목록 또는 그리드의 모양을 결정합니다. 일반적으로 RecyclerView 라이브러리의 표준 레이아웃 관리자 중 하나를 사용할 수 있습니다.

  2. 목록에 있는 각 요소의 모양과 동작 방식을 설계합니다. 이 설계에 따라 ViewHolder 클래스를 확장합니다. 사용 중인 ViewHolder 버전은 목록 항목에 필요한 모든 기능을 제공합니다. 뷰 홀더는 View의 래퍼이고 그 뷰는 RecyclerView로 관리됩니다.

  3. 데이터를 ViewHolder 뷰와 연결하는 Adapter를 정의합니다.

RecyclerView를 정확한 요구 사항에 맞게 조정할 수 있는 고급 맞춤설정 옵션도 있습니다.

레이아웃 계획

RecyclerView의 항목은 LayoutManager 클래스를 통해 정렬됩니다. RecyclerView 라이브러리는 가장 일반적인 레이아웃 상황을 처리하는 3가지 레이아웃 관리자를 제공합니다.

  • LinearLayoutManager는 항목을 1차원 목록으로 정렬합니다.
  • GridLayoutManager는 항목을 2차원 그리드로 정렬합니다.
    • 그리드가 세로로 정렬된 경우 GridLayoutManager는 각 행의 모든 요소를 동일한 너비와 높이로 만들려고 하지만 행마다 높이가 다를 수 있습니다.
    • 그리드가 가로로 정렬되는 경우 GridLayoutManager는 각 열의 모든 요소를 동일한 너비와 높이로 만들려고 열마다 너비가 다를 수 있습니다.
  • StaggeredGridLayoutManagerGridLayoutManager와 비슷합니다. 하지만 행의 항목이 동일한 높이(세로 그리드인 경우)이거나 동일한 열의 항목이 동일한 너비(가로 그리드인 경우)일 필요가 없습니다. 결과적으로 행 또는 열의 항목이 서로 오프셋 상태가 될 수 있습니다.

개별 항목의 레이아웃도 디자인해야 합니다. 다음 섹션에 설명된 것처럼 뷰 홀더를 설계할 때 이 레이아웃이 필요합니다.

어댑터 및 뷰 홀더 구현

레이아웃을 결정했으면 AdapterViewHolder를 구현해야 합니다. 이 두 클래스가 함께 작동하여 데이터 표시 방식을 정의합니다. ViewHolder는 목록에 있는 개별 항목의 레이아웃을 포함하는 View의 래퍼입니다. Adapter는 필요에 따라 ViewHolder 객체를 만들고 이러한 뷰에 데이터를 설정하기도 합니다. 뷰를 데이터에 연결하는 프로세스를 바인딩이라고 합니다.

어댑터를 정의할 때는 다음 세 가지 키 메서드를 재정의합니다.

  • onCreateViewHolder(): RecyclerViewViewHolder를 새로 만들어야 할 때마다 이 메서드를 호출합니다. 이 메서드는 ViewHolder와 그에 연결된 View를 생성하고 초기화하지만 뷰의 콘텐츠를 채우지는 않습니다. ViewHolder가 아직 특정 데이터에 바인딩된 상태가 아니기 때문입니다.

  • onBindViewHolder(): RecyclerViewViewHolder를 데이터와 연결할 때 이 메서드를 호출합니다. 이 메서드는 적절한 데이터를 가져와서 그 데이터를 사용하여 뷰 홀더의 레이아웃을 채웁니다. 예를 들어 RecyclerView가 이름 목록을 표시하는 경우 메서드는 목록에서 적절한 이름을 찾아 뷰 홀더의 TextView 위젯을 채울 수 있습니다.

  • getItemCount(): RecyclerView는 데이터 세트 크기를 가져올 때 이 메서드를 호출합니다. 예를 들어 주소록 앱에서는 총 주소 개수가 여기에 해당할 수 있습니다. RecyclerView는 이를 사용하여 더 이상 표시할 수 있는 항목이 없는 경우를 확인합니다.

다음은 간단한 어댑터의 일반적인 예로, 이 어댑터에는 데이터 목록을 표시하는 중첩된 ViewHolder가 있습니다. 이 경우 RecyclerView는 텍스트 요소의 간단한 목록을 표시합니다. 어댑터에는 ViewHolder 요소의 텍스트가 포함된 문자열 배열이 전달됩니다.

Kotlin

class CustomAdapter(private val dataSet: Array<String>) :
        RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

    /**
     * Provide a reference to the type of views that you are using
     * (custom ViewHolder)
     */
    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView

        init {
            // Define click listener for the ViewHolder's View
            textView = view.findViewById(R.id.textView)
        }
    }

    // Create new views (invoked by the layout manager)
    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
        // Create a new view, which defines the UI of the list item
        val view = LayoutInflater.from(viewGroup.context)
                .inflate(R.layout.text_row_item, viewGroup, false)

        return ViewHolder(view)
    }

    // Replace the contents of a view (invoked by the layout manager)
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {

        // Get element from your dataset at this position and replace the
        // contents of the view with that element
        viewHolder.textView.text = dataSet[position]
    }

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

}

자바

public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {

    private String[] localDataSet;

    /**
     * Provide a reference to the type of views that you are using
     * (custom ViewHolder)
     */
    public static class ViewHolder extends RecyclerView.ViewHolder {
        private final TextView textView;

        public ViewHolder(View view) {
            super(view);
            // Define click listener for the ViewHolder's View

            textView = (TextView) view.findViewById(R.id.textView);
        }

        public TextView getTextView() {
            return textView;
        }
    }

    /**
     * Initialize the dataset of the Adapter
     *
     * @param dataSet String[] containing the data to populate views to be used
     * by RecyclerView
     */
    public CustomAdapter(String[] dataSet) {
        localDataSet = dataSet;
    }

    // Create new views (invoked by the layout manager)
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        // Create a new view, which defines the UI of the list item
        View view = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.text_row_item, viewGroup, false);

        return new ViewHolder(view);
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, final int position) {

        // Get element from your dataset at this position and replace the
        // contents of the view with that element
        viewHolder.getTextView().setText(localDataSet[position]);
    }

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

각 뷰 항목의 레이아웃은 종전처럼 XML 레이아웃 파일에서 정의됩니다. 이 경우 앱에 다음과 같은 text_row_item.xml 파일이 들어 있습니다.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/list_item_height"
    android:layout_marginLeft="@dimen/margin_medium"
    android:layout_marginRight="@dimen/margin_medium"
    android:gravity="center_vertical">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/element_text"/>
</FrameLayout>

다음 단계

다음 코드 스니펫은 RecyclerView를 사용하는 방법을 보여줍니다.

Kotlin

class MainActivity : AppCompatActivity() {

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

        val dataset = arrayOf("January", "February", "March")
        val customAdapter = CustomAdapter(dataset)

        val recyclerView: RecyclerView = findViewById(R.id.recycler_view)
        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.adapter = customAdapter

    }

}

자바

RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.layoutManager = new LinearLayoutManager(this)
recyclerView.setAdapter(customAdapter);

또한 이 라이브러리는 구현을 맞춤설정할 수 있는 다양한 방법을 제공합니다. 자세한 내용은 고급 RecyclerView 맞춤설정을 참고하세요.

추가 리소스

Android에서 테스트하는 방법을 자세히 알아보려면 다음 자료를 참고하세요.

샘플 앱