본문 바로가기
Android

Android Studio ] 리사이클러뷰의 어댑터, 뷰홀더를 다른 파일로 만들어보기

by 김마리님 2020. 10. 28.

기존 포스팅에서는 뷰홀더와 어댑터를 한 파일에서 처리했었다. 

itstudy-mary.tistory.com/209?category=929375

 

안드로이드 스튜디오, JAVA] 리사이클러 뷰(recycler View)

https://itstudy-mary.tistory.com/208 안드로이드 스튜디오,JAVA] 커스텀 리스트뷰에 이미지 넣기 https://itstudy-mary.tistory.com/207 안드로이드 스튜디오, JAVA] 커스텀 리스트뷰(ListView) 만들기 https://..

itstudy-mary.tistory.com

 

그런데, 이제 배우고 공부하고 하다 보니까 어댑터와 뷰홀더를 따로 처리하는 방식이 있었다(!)

당연하게도 장단점이 존재하는데,

 

장점 

- 같은 리사이클되는 뷰의 형태를 가지며(그러니까, 같은 뷰홀더를 쓸 수 있는 상태를 말한다.), 다른 데이터를 넣어야 할 때 어댑터만 다르게 유지하고 뷰홀더는 같은 것을 사용해도 무방하다.

- 따라서 코드의 모듈화가 쉬워지고, (개인적이지만) 코드의 가독성이 올라간다.

 

단점

- 설계를 잘못하다가는 코드의 결합도가 올라갈 가능성이 있다.

 

개인적으로는 뷰홀더를 따로 두는 것이 참 편했기 때문에.. 예시는 어제 끝낸(어차피 인텐트랑 퍼미션 공부용이라 따로 디자인은 안할거임..!) HYPersonnalApp를 기준으로 한다.

itstudy-mary.tistory.com/250?category=952417

 

HYPersonnalApp Project 0. 개요.

ShareHouseProject로 스타트업 기업에 애플리케이션 개발 주니어로 입사한 지 한달 째입니다. 자취를 시작하다보니까 편한 점도 있는데, 저도 그렇고 함께 살았던 본가 사람들도 그렇고 불편한 점이

itstudy-mary.tistory.com

 

 

 

보다시피 두 개를 다른 파일로 생성했다.

처음 만들어야 할 것은 어댑터이다. 어댑터를 이용해 부모 뷰에 있는 리사이클러뷰와 뷰홀더를 결합한다.

그러니, 어댑터를 만들기 전 부모 뷰(이 프로젝트에선 액티비티임)에서 리사이클러뷰를 찾아주어야 한다.

 

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_select_book_number);
        findView();
        init();
        setListener();

        readPhoneContact();
    }

    private void findView(){
        ...
        recyclerView = findViewById(R.id.recyclerview);
        ...
    }

 

이제 어댑터를 만든다.

RecyclerView.Adapter를 상속받으면, 리사이클러뷰의 어댑터 역할로서 어떤 메소드를 사용해야하는지 친절하게 알려준다(프레임워크의 특장점!).

 

- Adapter

package com.example.hypersonnalsnsapp.selectBookNumber.adapter;

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

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.example.hypersonnalsnsapp.R;
import com.example.hypersonnalsnsapp.selectBookNumber.model.PhoneBook;
import com.example.hypersonnalsnsapp.selectSMSNumber.adapter.SelectSMSViewHolder;
import com.example.hypersonnalsnsapp.util.DebugLogUtil;

import java.util.ArrayList;
import java.util.List;

public class SelectBookNumberAdapter extends RecyclerView.Adapter {

    private static final String TAG = "SelectBookNumberAdapter";

    private List<PhoneBook> phoneBookList = new ArrayList<>();

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_holder_phone_contact_list, parent, false);
        return new SelectBookNumberViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

        SelectBookNumberViewHolder selectBookNumberViewHolder = (SelectBookNumberViewHolder) holder;
        selectBookNumberViewHolder.phoneBook=phoneBookList.get(position);
        selectBookNumberViewHolder.updateView();

    }

    public void reload(List<PhoneBook> phoneBookList){
        this.phoneBookList.clear();
        this.phoneBookList.addAll(phoneBookList);
        notifyDataSetChanged();
    }

    @Override
    public int getItemCount() {
        return phoneBookList.size();
    }
}

 

onCreateViewHolder. 이 함수에서 먼저 데이터가 들어갈 껍데기를 생성한다고 보면 될 것 같다.

이후 onBindViewHolder 함수에서 이 생성된 껍데기에 포지션에 따라 데이터를 넣어준다고 생각하면 될 것 같다.

그러니, 리사이클러뷰가 얼마나 껍데기를 생성해야할지 그것도 알려주어야 하는데, 이것은 getItemCount()메서드에서 데이터의 전체적인 크기를 리턴 받아야 한다. 

(이 프로젝트에서는 휴대폰 연락처를 cursor을 이용해서 받아오기 때문에 그 데이터를 while문을 통해 이미 앞서 ArrayList에 담아둔 상태입니다.)

 

더보기

액티비티입니다. 다음과 같이 데이터를 미리 받아서, 어댑터의 reload 매서드를 통해 미리 ArrayList를 만들어두었죠.

    public void readPhoneContact(){
        List<PhoneBook> phoneBookList = new ArrayList<>();

        ContentResolver resolver=SelectBookNumberActivity.this.getContentResolver();

        Uri phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;

        String[] projection = {ContactsContract.CommonDataKinds.Phone.CONTACT_ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER};
        Cursor c= resolver.query(phoneUri, projection, null, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);

        while (c.moveToNext()){

            String id = c.getString(c.getColumnIndex(projection[0]));
            String name = c.getString(c.getColumnIndex(projection[1]));
            String number = c.getString(c.getColumnIndex(projection[2]));

            PhoneBook phoneBook = new PhoneBook();
            phoneBook.setId(id);
            phoneBook.setName(name);
            phoneBook.setPhoneNumber(number);

            DebugLogUtil.logD(TAG, phoneBook.getPhoneNumber());

            phoneBookList.add(phoneBook);

        }

        selectBookNumberAdapter.reload(phoneBookList);

    }

 

아직 바인딩할 홀더가 없으니 이 파일을 완성할 수 없습니다.

 

이제 바인딩할 뷰홀더를 만들어줍니다. 마찬가지로 RecyclerView.ViewHolder만 상속받으면 됩니다.

 

- ViewHolder

package com.example.hypersonnalsnsapp.selectBookNumber.adapter;

import android.app.AlertDialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.view.View;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.example.hypersonnalsnsapp.R;
import com.example.hypersonnalsnsapp.getBankAndAddress.GetBankAndAddressActivity;
import com.example.hypersonnalsnsapp.selectBookNumber.model.PhoneBook;
import com.example.hypersonnalsnsapp.selectProduct.SelectProductActivity;
import com.example.hypersonnalsnsapp.util.ActivityUtil;
import com.example.hypersonnalsnsapp.util.DebugLogUtil;
import com.example.hypersonnalsnsapp.util.SharedPreferenceUtil;

public class SelectBookNumberViewHolder extends RecyclerView.ViewHolder {

    private static final String TAG = "SelectBookNumberViewHol";

    private TextView textViewName;
    private TextView textViewPhoneNumber;
    private TextView textViewSelect;

    public PhoneBook phoneBook;

    public SelectBookNumberViewHolder(@NonNull View itemView) {
        super(itemView);

        findView();
        setListener();
    }

    private void findView() {
        textViewName = itemView.findViewById(R.id.textViewName);
        textViewPhoneNumber = itemView.findViewById(R.id.textViewPhoneNumber);
        textViewSelect = itemView.findViewById(R.id.textViewSelect);
    }

    private void setListener() {
        textViewSelect.setOnClickListener(v -> {
...
    }

    public void updateView() {
        textViewName.setText(phoneBook.getName());
        String phoneNum = phoneBook.getPhoneNumber();

        if (!phoneNum.contains("-")) {
            if (phoneNum.length() == 10) {
                String address1 = phoneNum.substring(0, 3);
                String address2 = phoneNum.substring(3, 6);
                String address3 = phoneNum.substring(6, 10);
                textViewPhoneNumber.setText(address1 + "-" + address2 + "-" + address3);
            } else if (phoneNum.length() == 11) {
                String address1 = phoneNum.substring(0, 3);
                String address2 = phoneNum.substring(3, 7);
                String address3 = phoneNum.substring(7, 11);
                textViewPhoneNumber.setText(address1 + "-" + address2 + "-" + address3);
            }
        } else {
            textViewPhoneNumber.setText(phoneNum);
        }
    }
}

 

여기는 사실상.. 할 일이 없고요... 뷰홀더 내부클래스만 선언해주고, 리사이클러뷰 각각의 아이템에서 무엇을 해줄지만 결정해주면 됩니다. 보시다시피 어댑터의 onBind에서 넘어오는 데이터를 view에 set하거나, 리스너를 여기에 걸 수 있죠. 아주 편안합니다.

 

이제 createViewHolder 매서드에서 만든 뷰홀더로 뷰를 지정해주고, onBindViewHolder 포지션마다 데이터를 삽입하기만 하면 끝납니다..

 

마지막으로 부모 뷰(액티비티)로 돌아와서 어댑터를 선언해주고, 리사이클러뷰에 어댑터를 set 하면 정말 끝납니다.

    private void init(){
        selectBookNumberAdapter = new SelectBookNumberAdapter();
        recyclerView.setLayoutManager(new LinearLayoutManager(SelectBookNumberActivity.this));
        recyclerView.setAdapter(selectBookNumberAdapter);
    }

 

아주.. 편안해요. 어댑터와 함께 처리하는 것도 좋지만 이렇게 따로 사용해보는것도 추천드립니다.

 

 

추신 :

가급적 뷰홀더의 리스너에서는 외부서버나 데이터베이스에서 데이터를 가지고 오는 것을 추천드리진 않습니다. 뷰홀더에겐 뷰만 맡기는 것이 좋아요. 데이터 처리는 액티비티쪽에서 해주세요. 까딱하다간 포지션 오류가 일어날 수 있고, 그것으로 인해 서버에 부하가 걸릴 수 있으니..

꼭.. 꼭 포지션이 필요하다면 부모뷰 쪽에서 public으로 빈 컨테이너를 선언하고, 부모뷰에서 setPosition 함수와 이후의 데이터 호출 매서드를 하나 더 생성한 후 이벤트 리스너로 setPosition과 데이터 매서드를 호출하는 형태를 추천드립니다..

반응형