안드로이드 리사이클러뷰 사용 예제. (Android RecyclerView Example)

2019. 5. 31. 11:06


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

[안드로이드 리사이클러뷰 기본 사용법. (Android RecyclerView)]에서 설명한 리사이클러뷰의 기본 사용법에 이어, 여기서는 아주 조금 더 복잡한 예제를 통해 리사이클러뷰를 사용하는 방법에 대해 살펴보겠습니다.


예제에서 표시할 화면은 [안드로이드 커스텀 리스트뷰 만드는 방법. (Android Custom ListView)]에서 만든 화면과 유사합니다.

리사이클러뷰 예제 화면 구성


본문의 내용과 [안드로이드 커스텀 리스트뷰 만드는 방법. (Android Custom ListView)]의 내용을 같이 살펴보시면, 리사이클러뷰와 리스트뷰의 구현 과정을 비교해보실 수 있습니다.

2. 리사이클러뷰를 사용하기 위한 기본 절차.

리사이클러뷰를 어떤 용도로 사용할지, 어떤 형태로 표시할지에 따라 그 구현 과정의 세부적인 내용와 코드의 복잡도는 달라지겠지만, 일반적으로 아래와 같은 절차를 통해 리사이클러뷰를 사용할 수 있습니다. (참고로, 아래 순서는 리사이클러뷰 사용 과정을 쉽게 설명하기 위해 정리한 것 일 뿐, 반드시 그대로 따를 필요는 없습니다. 각 단계는 경우에 따라 변경 또는 생략해도 무방합니다.)

리사이클러뷰 구현 절차


3. 리사이클러뷰 사용 예제 앱 작성하기.

그럼 지금부터, 리사이클러뷰 사용 예제를 통해 앞서 구성한 아이템뷰를 표시하는 앱을 작성해보도록 하겠습니다.


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

3.1 리사이클러뷰가 표시될 위치 결정.

가장 먼저 할 일은, 리사이클러뷰가 표시될 위치를 결정하는 것인데요, 예제에서는 리사이클러뷰를 메인액티비티에 표시합니다.

[STEP-1] "content_main.xml" - 메인액티비티 레이아웃 리소스 XML에 리사이클러뷰 추가.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".MainActivity"
    tools:showIn="@layout/activity_main">

    <android.support.v7.widget.RecyclerView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:id="@+id/recycler1"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

3.2 리사이클러뷰 아이템 배치 형태 결정.

리사이클러뷰에 표시될 아이템을 어떤 형태로 배치할 것인지 결정해야 하는데요, 여기서 선택된 결과에 따라 어떤 레이아웃매니저(LayoutManager)를 사용할지가 결정됩니다.


그런데 사실 이 단계는, 보통 앱 설계 과정에서 리사이클러뷰를 사용하기로 결정하는 시점에 이미 결정됩니다. 아이템 뷰가 어떻게 배치될 것인지, 어떤 요소를 사용할 것인지는 UI 화면 설계 과정에서 미리 고려되는게 일반적입니다.


예제에서는 세로 방향으로 아이템을 나열하므로, 리니어레이아웃매니저(LinearLayoutManager)를 사용합니다.


[STEP-2] 레이아웃매니저(LayoutManager) 결정.

리니어레이아웃매니저


3.3 아이템 뷰 레이아웃 구성.

다음 단계는 리사이클러뷰 각 아이템에 표시될 아이템 뷰의 레이아웃을 작성하는 단계입니다.


[STEP-3] "res/layout/recycler_item.xml" - 아이템 뷰 레이아웃 구성.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/icon"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" /> />

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:id="@+id/title"
        android:textSize="24dp"
        android:textColor="#000000"
        app:layout_constraintLeft_toRightOf="@id/icon"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:id="@+id/desc"
        android:textSize="16dp"
        android:textColor="#666666"
        app:layout_constraintLeft_toRightOf="@id/icon"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/title" />

</android.support.constraint.ConstraintLayout>

3.4 아이템 데이터 클래스 정의.

아이템 뷰 레이아웃을 구성했으니, 아이템뷰에 표시될 데이터를 저장할 클래스를 정의합니다.


아이템 뷰는 하나의 이미지뷰와 두 개의 텍스트뷰로 구성됩니다. 그래서 아이템 데이터 클래스에는 이미지뷰를 위한 하나의 Drawable 변수와 텍스트뷰에 표시될 문자열을 저장하는 두 개의 String 변수를 정의합니다.


[STEP-4] "RecyclerItem.java" - 리사이클러뷰 아이템 데이터 클래스 정의.
import android.graphics.drawable.Drawable;

public class ListViewItem {
    private Drawable iconDrawable ;
    private String titleStr ;
    private String descStr ;

    public void setIcon(Drawable icon) {
        iconDrawable = icon ;
    }
    public void setTitle(String title) {
        titleStr = title ;
    }
    public void setDesc(String desc) {
        descStr = desc ;
    }

    public Drawable getIcon() {
        return this.iconDrawable ;
    }
    public String getTitle() {
        return this.titleStr ;
    }
    public String getDesc() {
        return this.descStr ;
    }
}

3.5 어댑터 상속 및 구현.

이제 리사이클러뷰 어댑터(RecyclerView.Adapter)를 상속받은 클래스를 추가하고, 필수 구현 메서드를 오버라이드합니다.


[안드로이드 리사이클러뷰 기본 사용법 - 3.4 리사이클러뷰 어댑터 구현]에서 설명했듯이, 리사이클러뷰 어댑터를 구현할 때는 아래 세 개의 메서드를 오버라이드 해야 합니다.


  • onCreateViewHolder() : 뷰홀더 객체 생성.
  • onBindViewHolder() : 데이터를 뷰홀더에 바인딩.
  • getItemCount() : 전체 아이템 갯수 리턴.


[STEP-5] "RecyclerImageTextAdapter.java" - 어댑터 상속 및 구현.
public class RecyclerImageTextAdapter extends RecyclerView.Adapter<RecyclerImageTextAdapter.ViewHolder> {
    private ArrayList<RecyclerItem> mData = null ;

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

    // onCreateViewHolder() - 아이템 뷰를 위한 뷰홀더 객체 생성하여 리턴.
    @Override
    public RecyclerImageTextAdapter.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.recycler_item, parent, false) ;
        RecyclerImageTextAdapter.ViewHolder vh = new RecyclerImageTextAdapter.ViewHolder(view) ;

        return vh ;
    }

    // onBindViewHolder() - position에 해당하는 데이터를 뷰홀더의 아이템뷰에 표시.
    @Override
    public void onBindViewHolder(RecyclerImageTextAdapter.ViewHolder holder, int position) {

        RecyclerItem item = mData.get(position) ;

        holder.icon.setImageDrawable(item.getIcon()) ;
        holder.title.setText(item.getTitle()) ;
        holder.desc.setText(item.getDesc()) ;
    }

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

3.6 뷰홀더 상속 및 구현.

앞선 단계에서 어댑터를 구현할 때, 각 오버라이드 메서드에서 참조한 뷰홀더(RecyclerImageTextAdapter.ViewHolder) 클래스를 작성합니다.


[STEP-6] "RecyclerImageTextAdapter.java" - 뷰홀더 상속 및 구현.
public class RecyclerImageTextAdapter extends RecyclerView.Adapter<RecyclerImageTextAdapter.ViewHolder> {
    
    // ... 코드 계속

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

        ViewHolder(View itemView) {
            super(itemView) ;

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

3.7 어댑터 생성 및 지정.

자, 이제 어댑터와 뷰홀더 작성을 완료했으니, 어댑터 인스턴스를 하나 만든 다음, 리사이클러뷰 참조를 통해 생성된 인스턴스를 지정하겠습니다.


[STEP-7] "MainActivity.java" - 어댑터 생성 및 지정.
public class MainActivity extends AppCompatActivity {

    RecyclerView mRecyclerView = null ;
    RecyclerImageTextAdapter mAdapter = null ;
    ArrayList<RecyclerItem> mList = new ArrayList<RecyclerItem>();

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

        mRecyclerView = findViewById(R.id.recycler1) ;

        // 리사이클러뷰에 SimpleTextAdapter 객체 지정.
        mAdapter = new RecyclerImageTextAdapter(mList) ;
        mRecyclerView.setAdapter(mAdapter) ;

        /// ... 코드 계속.
    }
}

3.8 레이아웃매니저 생성 및 지정.

앞서 "3.2 리사이클러뷰 아이템 배치 형태 결정." 단계에서 결정한대로, 리사이클러뷰 아이템들을 세로 방향으로 배치하기 위해 LinearLayoutManager을 사용합니다. [안드로이드 리사이클러뷰 기본 사용법 - 3.5 리사이클러뷰에 어댑터와 레이아웃매니저 지정하기.]에서 설명했듯이, LinearLayoutManager 인스턴스를 만들 때 orientation에 대한 파라미터를 지정하지 않으면, orientation이 "VERTICAL"로 지정됩니다.


[STEP-8] "MainActivity.java" - LinearLayoutManager 생성 및 지정.
public class MainActivity extends AppCompatActivity {

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

        // 리사이클러뷰에 LinearLayoutManager 지정. (vertical)
        recyclerView.setLayoutManager(new LinearLayoutManager(this)) ;

        /// ... 코드 계속
    }
}

3.9 데이터 추가 및 아이템 표시.

마지막으로, 리사이클러뷰에 표시될 아이템 데이터를 추가하여 화면에 표시하도록 만듭니다.


일단 [안드로이드 커스텀 리스트뷰 만드는 방법. (Android Custom ListView)] 예제와 동일하게 몇 개의 이미지 파일을 "/res/drawable/"에 추가합니다.

Drawable 리소스 추가


그리고 MainActivity 클래스에 addItem() 이라는 메서드를 추가하고, 리사이클러뷰의 초기화가 끝난 후에 호출합니다. 그리고 마지막으로 어댑터의 notifyDataSetChanged() 메서드를 호출하여 리사이클러뷰에 아이템이 표시되게 만듭니다.


[STEP-9] "MainActivity.java" - 데이터 추가 및 아이템 표시.
public class MainActivity extends AppCompatActivity {

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

        // 아이템 추가.
        addItem(getDrawable(R.drawable.ic_account_box_black_36dp),
                "Box", "Account Box Black 36dp") ;
        // 두 번째 아이템 추가.
        addItem(getDrawable(R.drawable.ic_account_circle_black_36dp),
                "Circle", "Account Circle Black 36dp") ;
        // 세 번째 아이템 추가.
        addItem(getDrawable(R.drawable.ic_assignment_ind_black_36dp),
                "Ind", "Assignment Ind Black 36dp") ;

        adapter.notifyDataSetChanged() ;
    }

    public void addItem(Drawable icon, String title, String desc) {
        RecyclerItem item = new RecyclerItem();

        item.setIcon(icon);
        item.setTitle(title);
        item.setDesc(desc);

        mList.add(item);
    }
}

여기까지, 모든 구현 과정이 완료되었습니다.

4. 실행 화면.

예제를 빌드하고 실행하면, 아래 그림과 같은 실행 화면을 확인할 수 있습니다.

리사이클러뷰 예제 실행 화면


5. 참고.

.END.


ANDROID 프로그래밍/RECYCLERVIEW , , , , , , , , ,

  1. Blog Icon
    codeslave

    안녕하세요? 안드로이드 공부중인 초보자입니다! 책은 없이 이것저것 영상이나 사이트를 참고해가며 공부를 시작했는데요~ 찾던중 뽀따님이 정리하신글이 너무너무 잘되어있어서 여기서 많은 도움을 얻고 있습니다.
    그런데 한가지 질문같은것이 있는데 뷰나 위젯의 사용법이나 개념을 설명하실때 자주 등장하는 함수?같은게 있는데 그런 함수들은 어떠한 개념인지 어려워 이해하기가 힘들때가 있더라구요,..
    예를들어 Context context = viewgroup.getContext() 라던지 inflater의 개념이라던지..이런 함수같은것은 스스로 경험해가며 배울수밖에 없나요?

  2. 제 경험에 비추어 답변드리자면,

    질문하신 요소들에 대한 내용은 관련 코드를 접할 때 마다 찾아보시는 수 밖에 없을 것 같습니다.

    아주 많은 수의 안드로이드 요소들과 거대한 양의 소스들을 모두 꿰고 있을 수는 없으니, 필요할 때 또는 관련 코드를 접할 때, 구글링이나 안드로이드 개발 참조 문서(developer.android.com)를 통해 찾아보시면 될 것 같습니다.

    사실 모두 다 이해하실 필요도 없습니다. 자세한 내막은 알지 못해도, "음.. 그렇구나...", "이렇게 쓰는 거구나.." 라고 기억하시고 넘어가셔도 괜찮은 경우가 많습니다.

    결론은, "예. 스스로 경험해가며 배울 수 밖에 없습니다." 입니다.

    감사합니다.

  3. Blog Icon
    codeslave

    추가로 한가지만 더 여쭈어보자면..리사이클러뷰를 지금 공부하고있는데 EditText를 추가하고 싶습니다.
    버튼을 클릭할때마다요. 정확히는 MaterialDesign의 EditText겠죠. 그러면 TextInputlayout과 함께 추가가 되어야하는데 이 문제 전에.. EditText는 어떤식으로 추가가되어야하나요? EditText는 TextView와 달리 입력을 하는것이라 아이템 클래스에서 본문의 예제에서 TextView의 값을 저장하기위한 String을 따로 저장해주어야하나요?

  4. 어떤 기능을 구현하실지는 정확히 모르겠지만, TextView를 사용하는 방법처럼 EditText를 사용하시면 됩니다.

    아이템뷰를 표시할 때 EditText에 표시되어야 할 내용이 있다면, 질문글의 내용대로 아이템 데이터에 String 변수를 따로 추가해주면 됩니다.

    아이템 데이터와 아이템 뷰의 바인딩은 어댑터의 onBindViewHolder에서 처리해주시면 되구요.

    전혀 어렵게 생각하실 필요가 없습니다. 제 생각엔 이미 방법을 이해하고 계신 것 같은데요. 음, 일단 만들어보세요. 이것 저것 시도해보시면 금방 구현하실 수 있을 것 같습니다.

    구현 중 잘 안되는 내용이 있으면 에러 내용과 함께 질문글 남겨주세요.

    감사합니다.

  5. Blog Icon
    bene33

    서버에서 값을 받아 사용하는 리사이클러뷰를 만들었는데요.. 계속 에러가 나는데 잘 모르겠어서 질문드립니다..
    package com.example.studyroom;

    import android.os.Bundle;

    import androidx.fragment.app.Fragment;
    import androidx.recyclerview.widget.LinearLayoutManager;
    import androidx.recyclerview.widget.RecyclerView;

    import android.util.Log;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;

    import com.android.volley.RequestQueue;
    import com.android.volley.Response;
    import com.android.volley.toolbox.Volley;

    import org.json.JSONArray;
    import org.json.JSONObject;

    import java.util.ArrayList;


    public class StudyroomListFragment extends Fragment {
    SRLAdapter adapter;
    ArrayList<itemForm2> list;
    RecyclerView recyclerView;
    itemForm2 sItem;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_studyroom_list, container, false);

    recyclerView = (RecyclerView)view.findViewById(R.id.srList);
    LinearLayoutManager lim = new LinearLayoutManager(getActivity());
    recyclerView.setHasFixedSize(true);
    recyclerView.setLayoutManager(lim);

    list = new ArrayList<>();

    final Response.Listener responseListener1 = new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
    try {
    JSONObject jsonResponse = new JSONObject(response.substring(response.indexOf("{"), response.lastIndexOf("}") + 1));
    JSONArray srData = jsonResponse.getJSONArray("srData");

    Log.d("mytest", srData + "");
    String srID, srName, srLocation, srMin, srMax, srOpen, srClose;
    for (int i = 0; i < srData.length(); i++) {
    JSONObject object = srData.getJSONObject(i);
    srID = object.getString("srID");
    srName = object.getString("srName");
    srLocation = object.getString("srLocation");
    srMin = object.getString("srMin");
    srMax = object.getString("srMax");
    srOpen = object.getString("srOpen");
    srClose = object.getString("srClose");

    sItem = new itemForm2(srID, srName, srLocation, srMin, srMax, srOpen, srClose);
    list.add(sItem);
    }
    adapter = new SRLAdapter(getActivity(), list);
    recyclerView.setAdapter(adapter);// 만든 객체를 리싸이클러뷰에 적용시킨다.
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    };
    StudyroomListRequest studyroomListRequest = new StudyroomListRequest(responseListener1);
    RequestQueue queue1 = Volley.newRequestQueue(getContext());
    queue1.add(studyroomListRequest);

    return view;
    }
    }

    2019-11-14 20:47:43.855 15329-15329/com.example.studyroom E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.studyroom, PID: 15329
    android.view.InflateException: Binary XML file line #118: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
    Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
    at com.example.studyroom.SRLAdapter.onCreateViewHolder(SRLAdapter.java:30)
    at com.example.studyroom.SRLAdapter.onCreateViewHolder(SRLAdapter.java:16)

  6. 이 건에 대해선, XML 측 의견도 들어봐야 합니다.

    레이아웃 리소스 XML의 내용들도 잘 살펴보세요. 오타는 없는지, 대소문자 구분은 잘 했는지..

    그리고 원인 분석 결과 또는 XML 내용을 댓글로 달아주세요.

  7. Blog Icon
    bene33

    xml에서 이미지뷰 아이디랑 잘못 연결되서 그 부분은 고쳤는데도 안되네요.
    그리고 다른 xml과 이름이 같아서 혹시 몰라 pNum 부분도 Num으로 변경 했습니다. name부분도 srName으로 변경하고요...

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="10dp"
    android:background="@drawable/edt">

    <TextView
    android:id="@+id/srID"
    android:visibility="gone"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>

    <ImageView
    android:id="@+id/imgSR"
    android:layout_width="140dp"
    android:layout_height="90dp"
    android:scaleType="fitXY"
    android:src="@drawable/studyroom"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toLeftOf="@+id/srName"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toTopOf="@+id/guideline1"/>

    <TextView
    android:id="@+id/srName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="10dp"
    android:layout_marginTop="5dp"
    android:text="본관 스터디룸1"
    android:textSize="20sp"
    app:layout_constraintLeft_toRightOf="@id/imgSR"
    app:layout_constraintTop_toTopOf="parent" />

    <TextView
    android:id="@+id/txtTime"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="10dp"
    android:layout_marginTop="5dp"
    android:layout_marginBottom="15dp"
    android:text="이용시간"
    android:textSize="18sp"
    app:layout_constraintLeft_toRightOf="@id/imgSR"
    app:layout_constraintRight_toLeftOf="@+id/opentime"
    app:layout_constraintTop_toBottomOf="@+id/srName" />

    <TextView
    android:id="@+id/opentime"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="5dp"
    android:text="00:00"
    android:textSize="18sp"
    android:layout_marginStart="10dp"
    app:layout_constraintLeft_toRightOf="@+id/txtTime"
    app:layout_constraintTop_toBottomOf="@+id/srName" />
    <TextView
    android:id="@+id/txt"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="5dp"
    android:text=" - "
    android:textSize="18sp"
    app:layout_constraintLeft_toRightOf="@+id/opentime"
    app:layout_constraintTop_toBottomOf="@+id/srName" />
    <TextView
    android:id="@+id/closetime"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="5dp"
    android:text="00:00"
    android:textSize="18sp"
    app:layout_constraintLeft_toRightOf="@+id/txt"
    app:layout_constraintTop_toBottomOf="@+id/srName" />

    <TextView
    android:id="@+id/txtpNum"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="5dp"
    android:text="인 원"
    android:textSize="18sp"
    android:layout_marginStart="10dp"
    app:layout_constraintLeft_toRightOf="@id/imgSR"
    app:layout_constraintRight_toLeftOf="@+id/Num"
    app:layout_constraintTop_toBottomOf="@+id/txtTime" />

    <TextView
    android:id="@+id/Num"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="5dp"
    android:text="4 - 8명"
    android:layout_marginStart="10dp"
    android:textSize="18sp"
    app:layout_constraintLeft_toRightOf="@+id/txtpNum"
    app:layout_constraintRight_toLeftOf="@+id/location"
    app:layout_constraintTop_toBottomOf="@+id/opentime" />

    <TextView
    android:id="@+id/location"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="5dp"
    android:text="/ 본관 1층"
    android:textSize="18sp"
    android:layout_marginStart="10dp"
    app:layout_constraintLeft_toRightOf="@+id/Num"
    app:layout_constraintTop_toBottomOf="@+id/opentime" />

    <view
    android:visibility="invisible"
    android:id="@+id/guideline1"
    android:layout_width="match_parent"
    android:layout_height="0.6dp"
    android:background="#FFFFFF"
    app:layout_constraintTop_toBottomOf="@+id/imgSR"
    app:layout_constraintBottom_toTopOf="@+id/txtState"/>
    />

    <TextView
    android:id="@+id/txtState"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="6dp"
    android:layout_marginStart="10dp"
    android:text="예약 현황"
    android:textSize="18sp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/guideline1" />

    <Button
    android:id="@+id/btnReserve"
    android:layout_width="20dp"
    android:layout_height="20dp"
    android:layout_margin="8dp"
    android:background="@drawable/state2"
    app:layout_constraintRight_toLeftOf="@+id/txtReserv"
    app:layout_constraintTop_toBottomOf="@+id/guideline1"
    app:layout_constraintBottom_toTopOf="@+id/view1"/>

    <TextView
    android:id="@+id/txtReserv"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="6dp"
    android:text="예약 "
    android:textSize="18sp"
    app:layout_constraintRight_toLeftOf="@id/btnAvaliable"
    app:layout_constraintTop_toBottomOf="@+id/guideline1" />

    <Button
    android:id="@+id/btnAvaliable"
    android:layout_width="20dp"
    android:layout_height="20dp"
    android:layout_margin="8dp"
    android:background="@drawable/state"
    app:layout_constraintRight_toLeftOf="@+id/txtAvaliable"
    app:layout_constraintTop_toBottomOf="@+id/guideline1"
    app:layout_constraintBottom_toTopOf="@+id/view1"/>

    <TextView
    android:id="@+id/txtAvaliable"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="6dp"
    android:layout_marginRight="10dp"
    android:text="사용 가능"
    android:textSize="18sp"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/guideline1" />
    <view
    android:id="@+id/view1"
    android:layout_margin="5dp"
    android:layout_width="match_parent"
    android:layout_height="0.6dp"
    app:layout_constraintBottom_toTopOf="@+id/table1" />

    <TableLayout
    android:id="@+id/table1"
    android:layout_marginTop="5dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintBottom_toBottomOf="parent">
    <TableRow
    android:gravity="left"
    >
    <TextView

    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"

    android:textSize="20sp"
    android:text="9" />
    <TextView
    android:layout_width="0dp"
    android:layout_weight="1"

    android:textSize="20sp"
    android:text="10" />
    <TextView
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"

    android:textSize="20sp"
    android:text="11" />
    <TextView
    android:layout_width="0dp"
    android:layout_weight="1"

    android:textSize="20sp"
    android:text="12" />
    <TextView
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"

    android:textSize="20sp"
    android:text="13" />
    <TextView
    android:layout_width="0dp"
    android:layout_weight="1"

    android:textSize="20sp"
    android:text="14" />
    <TextView
    android:layout_width="0dp"
    android:layout_weight="1"

    android:textSize="20sp"
    android:text="15" />
    </TableRow>
    <TableRow
    android:background="#A5A5A5"
    android:padding="1dp">
    <TextView
    android:id="@+id/time9"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textSize="20sp"
    android:layout_marginRight="2dp"
    android:background="#FFFFFF"/>
    <TextView
    android:id="@+id/time10"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:gravity="center"
    android:textSize="20sp"
    android:layout_marginRight="2dp"
    android:background="#FFFFFF"/>
    <TextView
    android:id="@+id/tim11"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textSize="20sp"
    android:layout_marginRight="2dp"
    android:background="#FFFFFF"/>
    <TextView
    android:id="@+id/time12"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:gravity="center"
    android:textSize="20sp"
    android:layout_marginRight="2dp"
    android:background="#FFFFFF"/>
    <TextView
    android:id="@+id/time13"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textSize="20sp"
    android:layout_marginRight="2dp"
    android:background="#FFFFFF"/>
    <TextView
    android:id="@+id/time14"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:gravity="center"
    android:textSize="20sp"
    android:layout_marginRight="2dp"
    android:background="#FFFFFF"/>
    <TextView
    android:id="@+id/time15"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:gravity="center"
    android:textSize="20sp"
    android:background="#FFFFFF"/>
    </TableRow>
    <TableRow
    android:layout_marginTop="3dp">
    <TextView
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textSize="20sp"
    android:text="16" />
    <TextView
    android:layout_width="0dp"
    android:layout_weight="1"
    android:gravity="center"
    android:textSize="20sp"
    android:text="17" />
    <TextView
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textSize="20sp"
    android:text="18" />
    <TextView
    android:layout_width="0dp"
    android:layout_weight="1"
    android:gravity="center"
    android:textSize="20sp"
    android:text="19" />
    <TextView
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textSize="20sp"
    android:text="20" />
    <TextView
    android:layout_width="0dp"
    android:layout_weight="1"
    android:gravity="center"
    android:textSize="20sp"
    android:text="21" />
    <TextView
    android:layout_width="0dp"
    android:layout_weight="1"
    android:gravity="center"
    android:textSize="20sp"
    android:text="22" />
    </TableRow>
    <TableRow
    android:background="#A5A5A5"
    android:padding="1dp">
    <TextView
    android:id="@+id/time16"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textSize="20sp"
    android:layout_marginRight="2dp"
    android:background="#FFFFFF"/>
    <TextView
    android:id="@+id/time17"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:gravity="center"
    android:textSize="20sp"
    android:layout_marginRight="2dp"
    android:background="#FFFFFF"/>
    <TextView
    android:id="@+id/time18"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textSize="20sp"
    android:layout_marginRight="2dp"
    android:background="#FFFFFF"/>
    <TextView
    android:id="@+id/time19"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:gravity="center"
    android:textSize="20sp"
    android:layout_marginRight="2dp"
    android:background="#FFFFFF"/>
    <TextView
    android:id="@+id/time20"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textSize="20sp"
    android:layout_marginRight="2dp"
    android:background="#FFFFFF"/>
    <TextView
    android:id="@+id/time21"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:gravity="center"
    android:textSize="20sp"
    android:layout_marginRight="2dp"
    android:background="#FFFFFF"/>
    <TextView
    android:id="@+id/tim22"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:gravity="center"
    android:textSize="20sp"
    android:background="#FFFFFF"/>
    </TableRow>
    </TableLayout>
    </androidx.constraintlayout.widget.ConstraintLayout>

  8. 올려주신 XML의 내용을 후루룩 훓어보니, 손보셔야 할 곳이 좀 보이네요.

    하지만 구현하시면서 잘 만들어가실 거라 생각합니다. ^^

  9. Blog Icon
    bene33

    해결했습니다.
    계속 xml에서 수평선을 그리기 위해 view를 사용했는데 이부분에서 계속 에러가 났더라고요.
    정확히 view가 무슨 에러를 일으킨건지는 모르겠지만 해결됐네ㅎㅎ

  10. 해결하셨다니 다행입니다. ^^
    너무 급하게 만드려고만 하지 마시고, 차분히 코드를 살펴보면서, 의미를 이해해가면서 작업해가시면 원하시는 기능 구현하실거라 생각합니다.

    또 다른 도움이 필요하면 언제든 질문글 남겨주세요.

    감사합니다.

  11. Blog Icon
    초보자.

    안녕하세요
    마지막 메쏘드에서 있는 list.add(item);
    여기서 list는 어떤 리스트를 말하는건가요?

    여기는 제 코드인데, 이렇게 마지막 메쏘드에 있는 dataList.add(item);
    이렇게 하면 에러가 나서 질문드립니다.

    public class SettingScreen extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_setting_screen);

    RecyclerView recyclerView = findViewById(R.id.recyclerView);

    RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
    recyclerView.setLayoutManager(layoutManager);

    List<CardItem> dataList = new ArrayList<>();

    addItem(getDrawable(R.drawable.ic_noti), "User Profile") ;




    RecyclerAdapter adapter = new RecyclerAdapter(dataList);
    recyclerView.setAdapter(adapter);
    }

    public void addItem(Drawable icon, String title) {
    CardItem item = new CardItem();

    item.setIconDrawable(icon);
    item.setTitle(title);

    LinkedList<CardItem> dataList;
    dataList.add(item);

    }

  12. 앗. 본문 예제에 잘못 작성된 내용이 있었네요.

    [STEP-9] 소스 addItem() 메서드의 마지막 코드는

    "list.add(item) ;"이 아닌
    "mList.add(item);"가 되어야 합니다.

    본문 내용을 수정해 놓았습니다.


    그리고 본문에서 mList는,
    [STEP-7]에서 확인할 수 있듯이 클래스 멤버 변수로 만들어져 있습니다. 그리고 mList 인스턴스를 액티비티의 onCreate() 메서드에서 RecyclerImageTextAdapter() 생성자를 통해 전달하죠.

    질문글에 올려주신 코드에서 dataList.add(item)를 호출하는 것은 본문의 내용에 부합하지만, dataList를 선언하고 인스턴스를 만드는 부분은 고쳐주셔야 합니다.

    onCreate()와 addItem()에 있는 dataList 변수 선언을 SettingScreen 클래스의 클래스 멤버로 선언하셔야 합니다.

    답변이 되셨는지 모르겠네요.
    본문에 수정된 내용을 다시 한번 참고하시기 바랍니다.

    감사합니다.

  13. 안녕하세요. 잘 정리해 주셔서 많이 배웁니다.
    리사이클러뷰의 아이템클릭시 리스트뷰와 달리 미디어플레이어
    에의한 음악 실행이 안되는 건지 아시면 알려주시면
    매우 고맙겠읍니다.

  14. 리사이클러뷰에서 아이템을 클릭했을 때와 리스트뷰에서 아이템을 클릭했을 때 어떤 기능을 수행할 것인지는 오로지 개발자의 선택에 달려 있습니다.

    리사이클러뷰를 사용했을 때는 되고, 리스트뷰를 사용했을 때는 안되고.. 이런 건 없습니다.

    답변이 되셨나 모르겠네요.

    감사합니다.

  15. Blog Icon
    딱딱이

    내용 잘 정리해주셔서 잘보고 갑니다.
    그런데 리사이클러뷰 3월인가 내용것도 있고 이것도 있고 일부로 나눠서 쓰신건가요??

  16. 네. 3월에 작성한 글("안드로이드 리사이클러뷰 기본 사용법")은 리사이클러뷰 개념과 구조 등에 초점을 두고 작성한 글이고, 본문의 글("안드로이드 리사이클러뷰 사용 예제")은 간단한 예제를 통해 리사이클러뷰 사용법을 설명하고자 작성한 글입니다.

    기본 개념에 더해 구체적인 예제를 보여주기 위해 작성했다라고 보시면 될 것 같습니다.

    감사합니다.

  17. Blog Icon
    주승

    안녕하세요. 어플을 처음 공부하는 학생입니다. 제가 간단한 사전 어플을 만들려고합니다. 메인 인터페이스에 기본 메뉴화면을 만들고(명사, 동사, 형용사 등등) 그 메뉴 버튼들을 누르면 그 메뉴에 해당하는 단어들 리스트가 쭉 나옵니다. 그리고 그 단어 리스트에서 하나를 선택하면 이제 그 단어에 대한 설명이 뜨는 그런 어플을 만들려고 합니다.(메인메뉴 -> 단어리스트 -> 단어설명) 그래서 제가 생각한거는 메인화면에 리스트를 띄우고 클릭이벤트를 처리하여 특정 화면(단어리스트화면)으로 넘어가고 그 화면에도 단어리스트를 띄울라고 합니다. 그리고 그 단어 리스트 화면에도 메인화면처럼 리스트 만들고 클릭이벤트처리하여 단어설명 페이지가 뜨도록 할 예정입니다. 혹시 제가 빠뜨린 부분이 있을까요? 매우 간단한 어플이지만 처음이라 여쭤봅니다. 감사합니다!!

  18. 흠... 질문하신 내용처럼, 앱 설계와 관련된 내용은 제가 뭔가 확답을 드리기가 힘들 것 같습니다.

    정답이 있는 문제가 아니라서, 그리고 선호하는 패턴이 개발자마다 다 달라서, 맞다 틀리다 할 수 있는 문제가 아니거든요.

    그래도 대략적으로 설계하신 시나리오대로 하면 만드시는데 큰 문제는 없을 것 같습니다.

    그러니 일단 만들어보시고, 수정할 부분이 생기면 그 때 다시 만드셔도 될 것 같습니다.

    감사합니다.

  19. 3.5부터 3.7까지는 경로는 왜 없나요 ㅠㅠ
    [STEP-3] "res/layout/recycler_item.xml" 위에껀 이런식으로 경로 나와서 그대로 따라하면 쉬운데
    [STEP-4] "RecyclerItem.java" 이렇게 되있으니 어디에 만들어야되는지 감이 안잡히네요

  20. 조금 더 친절하게 예제를 가이드하지 못해서 죄송합니다.

    그런데 파일의 경로는 크게 상관없습니다.
    적당한 곳에 만드시고, import만 잘해서 쓰시면 됩니다.

    일단 예제를 따라서 작성하시는 것도 좋지만, 책을 통해 기본적인 프로그래밍 작성 방법부터 익히시면 더 편하게 예제를 시험해보실 수 있을 것 같습니다.

    감사합니다.

  21. Blog Icon
    감사합니다

    혹시 코틀린 예제도 있을까요

  22. 죄송하게도, 코틀린 예제는 따로 정리된 것이 없습니다.

    감사합니다.

  23. Blog Icon
    공부

    setDrawable이 없는데 아이콘 이미지가 어떻게 바뀐거에요?? 거의 똑같이 따라했는데 저는 이미지가 바뀌지 않는 것 같아서..

  24. "3.5 어댑터 상속 및 구현."에 있는 코드를 보시면, 아래 코드가 있습니다.

    holder.icon.setImageDrawable(item.getIcon()) ;

    아이템 뷰의 ImageView(@+id/icon)에 이미지를 표시하는 코드입니다.

    감사합니다.