반응형

뷰바인딩 소개의 5가지 실습 중 마지막 5번째 실습을 소개하겠습니다.

이번에는 앱 개발에 가장 많이 사용되는 뷰 중에 단연 으뜸인 RecyclerView에서 뷰 바인딩을 사용해 보겠습니다. 정확히 말하면 리사이클러뷰에 뷰 바인딩을 적용하는 것이 아니라 리사이클러뷰가 보여주는 아이템뷰들을 만들어주는 아답터에서 사용하는 ViewHolder의 아이템뷰에 뷰 바인딩을 적용하는 것입니다.

 

기본적으로 리사이클러뷰를 만들어 보신 적이 없다면 조금 이해하기 어려우니 먼저 다른 포스트 글 들을 통해 리사이클러뷰를 다루어본 후 이 포스트의 실습을 참여해 보시길 바랍니다.

 

먼저 리사이클러 뷰가 보여줄 목록 아이템 1개의 모양을 설계하는 레이아웃 xml 파일을 만들어 보겠습니다.

간단한 실습을 위해 ImageView 1개와 TextView 1개를 가진 CardView로 아이템 1개의 모양을 구성하겠습니다.

실습에 사용하는 이미지는 본인들을 가지고 있는 이미지 파일로 대체하여 실습하시기 바랍니다.

 

# res / layout / recycler_item.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="120dp"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:cardCornerRadius="4dp"
    android:elevation="4dp"
    android:layout_margin="8dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/iv"
            android:layout_width="match_parent"
            android:layout_height="120dp"
            android:src="@drawable/newyork"
            android:scaleType="centerCrop"/>
        <TextView
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="newyork"
            android:layout_below="@id/iv"
            android:padding="8dp"
            android:textColor="#FF333333"
            android:layout_centerHorizontal="true"/>

    </RelativeLayout>


</com.google.android.material.card.MaterialCardView>

 

다음 두번째 작업으로, 위 아이템 뷰 1개 모양에 보여질 이미지파일의 res 리소스ID를 저장할 int 변수와 텍스트뷰에 보여질 String변수 1개를 가진 데이터 클래스를 한개 만들겠습니다. 리사이클러뷰가 보여줄 대량의 데이터(배열 or 리스트)의 요소 1개 클래스 입니다.

# ItemVO.java
//리사이클러뷰가 보여줄 Item 뷰 1개안에서 보여줄 데이터를 가진 Value Object(VO) 클래스
public class ItemVO {
    int imgResId; //이미지의 리소스 ID
    String title; //이미지 아래에 있는 제목글씨

    public ItemVO(int imgResId, String title) {
        this.imgResId = imgResId;
        this.title = title;
    }
}

 

이제 세번째로, 대량의 데이터 수 만큼 리사이클러뷰에서 보여줄 아이템 항목뷰를 만들어내는 Adapter 클래스를 만들어 보겠습니다. 이번 실습에서 소개하고자 하는 가장 중요한 코드 입니다. 특히 이너클래스(inner class)인 ViewHolder를 상속한 VH 클래스의 코드를 주석과 함께 주의깊게 살펴보시길 바랍니다.

이전 포스트들에서 강조했듯이 결국 레이아웃파일에 대응되어 자동으로 연결해주는 바인딩클래스를 알아보는 것이 중요합니다.

이 아답터 클래스가 만들 항목뷰들의 레이아웃 xml 파일인 recycler_item.xml 에 대응되어 연결되는 RecyclerItemBinding 바인디 클래스가 자동으로 만들어 져 있다는 것을 이해하시면 됩니다.

 

recycler_item.xml  --- >  RecyclerItemBinding

recycler_board.xml --- >  RecyclerBoardBinding

 

# MyAdapter.java
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

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

import java.util.ArrayList;

//RecyclerView.Adapter를 상속할때 이너클래스로 설계한 VH 클래스를 <>제네릭으로 지정하면 onBindViewHolder() 에서 다운캐스팅을 안해도 되어 코드가 간결해 짐.
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.VH> {

    Context context;
    ArrayList<ItemVO> items;

    public MyAdapter(Context context, ArrayList<ItemVO> items) {
        this.context = context;
        this.items = items;
    }

    @NonNull
    @Override
    public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView= LayoutInflater.from(context).inflate(R.layout.recycler_item, parent, false);
        return new VH(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull MyAdapter.VH holder, int position) {
        //아이템의 값 연결 작업코드는 홀더안에 메소드를 만들어 처리
        holder.bindItem(items.get(position));
    }

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

    //아이템 뷰 1개의 자식뷰들 참조값을 저장하고 있는 ViewHolde 클래스 설계 - inner class
    class VH extends RecyclerView.ViewHolder{

        RecyclerItemBinding itemBinding;

        public VH(@NonNull View itemView) {
            super(itemView);
            //이미 recycler_item.xml 모양으로 만들어진 itemView와 연결하는 바인딩클래스객체 연결하여 생성하기!
            // [ recycler_item.xml  --->   RecyclerItemBinding  ]
            itemBinding= RecyclerItemBinding.bind(itemView);
        }

        //아이템뷰 1개 안에 있는 자식뷰들에 전달받은 아이템의 값들을 연결하는 기능메소드
        void bindItem(ItemVO item){
            //뷰 바인딩으로 이미 자식뷰들의 참조값들이 모두 연결되어 있음.
            itemBinding.iv.setImageResource(item.imgResId);
            itemBinding.tv.setText(item.title);
        }

        //다른 방법으로 아답터를 구현할 수도 있음. [ MyAdapter2.java ]

    }
}

마지막 주석으로 써놓은 또 다른 방법의 뷰바인딩 적용방법(MyAdapter2.java)은 이 포스트의 마지막에 소개하겠습니다.

 

이제 마지막단계로 액티비티에서 리사이클러뷰를 보여주는 코드를 추가하겠습니다. 액티비티의 레이아웃 파일에 가로방향 스크롤의 리사이클러뷰를 추가하고 액티비티 자바파일에서 ArrayList, Adapter 를 만들어 리사이클러뷰에 설정해 주도록 하겠습니다. 당연히 액티비티에서 리사이클러뷰를 제어하기 위해 findViewById()를 호출하여 참조하지 않고 바인딩클래스 객체를 통해 제어할 겁니다.

이 실습을 마지막으로 뷰 바인딩에 대해 알아보는 실습을 마무리 하겠습니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <!-- 1) 기본 TextView  -->
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textColor="#FF333333"
        android:text="Hello"/>

    <!-- 2) 버튼 클릭이벤트 처리하기 -->
    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button"/>


    <!-- 3) EditText의 글씨을 얻어와서 TextView에 보이는 예제실습 [뷰참조와 클릭이벤트 종합 simple 예제]-->
    <EditText
        android:id="@+id/et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="enter text"
        android:inputType="text"
        android:layout_marginTop="24dp"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="입력완료"
        android:onClick="clickBtn2"/>
    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textColor="#FF333333"
        android:text="RESULT"/>


    <!-- 4) Fragment에서 ViewBinding 사용하기 예제 실습 [name속성에 적용한 패키지명은 본인앱의 패키지명으로 수정하여 적용하시기 바랍니다.]  -->
    <fragment
        android:id="@+id/frag"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:name="com.kitesoft.exviewbinding.MyFragment"
        tools:layout="@layout/fragment_my"
        android:layout_marginTop="24dp"/>
    <!-- tools:layout="@layout/fragment_my" 화면의 한 부분을 담당하는 MyFragment의 모양을 이곳에서 미리보기해주는 속성 -->


    <!-- 5) 앱 개발과정에서 가장 많이 사용되는 뷰 중에 하나인 RecyclerView 에서 뷰바인딩 실습 [ ViewHolder 에서의 사용 ] -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        android:orientation="horizontal"/>

</LinearLayout>

 

지금까지의 모든 실습을 모두 작성한 최종 MainActivity.java 파일

# MainActivity.java
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    // findViewById() 메소드를 이용한 View 참조 방법이 가진 문제를 해결하기 위한 기법들 [ 코드의 번거로움과 메소드의 동작속도 문제 ]
    // 1. ButterKnife 외부 라이브러리 사용하기
    // 2. ViewBinding 안드로이드 아키텍처 구성요소
    // 3. DataBinding (view binding + data binding) : 뷰결합 + 데이터 결합 기능까지 가진 아키텍처 구성요소

    // 이번 실습에서는 뷰 참조만 관점으로 볼때 가장 성능이 좋은 ViewBinding 안드로이드 아키텍처 구성요소 라이브러리 예제 소개
    // ButterKnife의 어노테이션을 이용한 BindView 역시 코드가 간결하다고 볼수없음. 이를 개선하였다고 보면 됨.

    // ViewBinding은 외부 라이브러리가 아니고 안드로이의 아키텍처 구성요소이기에 사용설정을 (모듈단위) build.gradle 에서 해주면 됨.

    // 뷰바인딩 기능의 주요특징은 xml 레이아웃파일(activity_main.xml)의 뷰들을 이미 연결(Bind)하고 있는 클래스가 자동으로 만들어져서
    // 개발자가 직접 뷰 참조변수를 만들어서 findViewBy()로 찾아서 연결하는 일련의 모든 작업을 할 필요가 없게 만들어 놓은 기능임.

    // 이때, 자동으로 xml 레이아웃 파일의 뷰들의 id와 같은 이름을 가진 멤버변수를 가진 클래스는 그 이름이 규칙적으로 명명되어 만들어짐.
    // 레이아웃파일의 이름에 맞게 클래스이름이 만들어짐.

    // ex.1) 액티비티 레이아웃 파일명과 자동으로 만들어지는 바인딩 클래스명
    // activity_main.xml      -->   ActivityMainBinding
    // activity_second.xml    -->   ActivitySecondBinding
    // activity_xxx.xml       -->   ActivityXxxBinding

    // ex.2) 프레그먼트 레이아웃 파일명과 자동으로 만들어지는 바인딩 클래스명
    // fragment_my.xml        -->   FragmentMyBinding
    // fragment_login.xml     -->   FragmentLoginBinding
    // fragment_xxx.xml       -->   FragmentXxxBinding

    // ex.3) 리사이클러뷰(아답터뷰)의 아이템 1개 레이아웃 파일명과 자동으로 만들어지는 바인딩 클래스명
    // recycler_item.xml      -->   RecyclerItemBinding
    // recycler_xxx.xml       -->   RecyclerXxxBinding


    // 실습방법은 이전에 실습했던 ButterKnife와 완전히 동일하게 진행해 봄으로서 차이점을 극명하게 확인.

    //1) 먼저 간단하게 TextView 1개를 만들고 이를 뷰 바인딩으로 참조하여 글씨 변경해 보기
    // - ViewBinding 초기화 -
    // 액티비티의 onCreate()에서 액티비티가 직접 setContentView()를 하지 말고 뷰바인딩 클래스가 레이아웃파일을 inflate해서 만들고 참조하여 멤버변수로 가지는 객체를 생성

    // activity_main.xml 파일과 연결되어 있는 바인딩 클래스 참조변수
    ActivityMainBinding mainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //액티비티가 직접 setContentView()로 뷰들을 생성하지 않도록... 주석처리
        //setContentView(R.layout.activity_main);

        // xml 레이아웃파일명과 관련되어 뷰바이딩 기능에 의해 자동으로 설계되어진 클래스을 이용하여 뷰들의 id와 같은 이름을 가진 참조변수를 멤버로 가지는 객체 생성
        //activity_main.xml과 연결되는 바인딩클래스인 ActivityMainBinding 클래스의 inflate() static 메소드를 통해 뷰를 바인딩클래스가 직접 생성(inflate)
        // 바인딩 클래스의 멤버로 여러 뷰들의 참조변수들이 있으므로 가급적 바인딩클래스의 참조변수는 이 액티비티클래스의 멤버변수 위치를 만들것을 권장함.
        mainBinding= ActivityMainBinding.inflate(getLayoutInflater()); //파라미터로 LayoutInflater객체 전달

        //이 액티비티가 보여줄 내용물 뷰를 바인딩 클래스 객체의 최상위 뷰(rootView : activity_main.xml의 첫번째 레이아웃 뷰 - 이 예제에서는 LinearLayout)로 설정
        setContentView(mainBinding.getRoot());

        //1) TextView 글씨 변경하기
        //이미 activity_main.xml의 뷰들을 참조하여 연결하고 있는 바인딩 클래스인 ActivityMainBinding의 객체인 mainBinding의 멤버변수로
        //각 뷰들의 id 값과 이름이 완전 똑같은 해당뷰의 참조변수들이 찾아져서 연결되어 있는 상태임.
        mainBinding.tv.setText("Nice to meet you. ViewBinding");

        //2) 버튼클릭 이벤트 처리를 어노테이션으로 연결하던 ButterKnife와 다르게 뷰 바인딩을 이름그대로 뷰만 연결하기에 클릭이벤트 처리는 버튼에 리스너를 설정하거나 onClick속성으로 처리
        //바인딩클래스 객체 안에 id와 같은 이름의 뷰 참조변수가 이미 연결되어 있음.
        mainBinding.btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "clicked button", Toast.LENGTH_SHORT).show();
            }
        });

        //2-1)람다식 -> [익명클래스의 콜백메소드 코드를 좀더 간결하게 표현하기 : JDK 1.8버전 이상에서 사용가능 ]
        mainBinding.btn.setOnClickListener(v -> Toast.makeText(this, "clicked button", Toast.LENGTH_SHORT).show());

        //롱~클릭 람다식 표기법 - 리턴있음.
        mainBinding.btn.setOnLongClickListener( v-> {
            Toast.makeText(this, "long~ clicked button", Toast.LENGTH_SHORT).show();
            return true;
        });
    }



    //3) 뷰 참조와 클릭이벤트 처리 종합 simple 예제 실습 - 버튼클릭이벤트 처리는 onClick속성 이용
    //이미 바인딩 클래스객체에 의해 TextView와 EditText의 참조변수들이 연결되어 있기에 곧바로 클릭이벤트 처리 코드만 작성 - 버터나이프의 어노테이션 작업도 필요없음.
    public void clickBtn2(View view) {
        mainBinding.tv2.setText(mainBinding.et.getText().toString());
        mainBinding.et.setText(""); //EditText의 글씨 지우기
    }


    //4) 프레그먼트에 뷰바인딩 실습 [ MyFragemnt.java ]


    //5) 앱 개발과정에서 가장 많이 사용되는 뷰 중에 하나인 RecyclerView 에서 뷰바인딩 실습 [ ViewHolder 에서의 사용 ]
    // - 원래는 onCreate()에서 작업하지만 위 실습과 분리해서 보기위해 onResume()에서 리사이클러뷰 작업코딩
    // RecyclerView 참조변수는 이미 mainBinding 객체의 멤버로 연결되어 있기에 다시 만들필요 없음

    ArrayList<ItemVO> items= new ArrayList<>(); //리사이클러 뷰가 보여줄 대량의 데이터를 가지고 있는 리시트객체
    MyAdapter adapter;   //리사이클러뷰가 보여줄 뷰을 만들어내는 객체참조변수

    @Override
    protected void onResume() {
        super.onResume();

        //임이의 대량의 데이터 추가
        items.add(new ItemVO(R.drawable.newyork, "newyork"));
        items.add(new ItemVO(R.drawable.paris, "paris"));
        items.add(new ItemVO(R.drawable.sydney, "sydney"));
        items.add(new ItemVO(R.drawable.newyork, "뉴욕"));
        items.add(new ItemVO(R.drawable.paris, "파리"));
        items.add(new ItemVO(R.drawable.sydney, "시드니"));

        //아답터생성 및 리사이클러뷰에 설정
        adapter= new MyAdapter(this, items);
        mainBinding.recycler.setAdapter(adapter);

        //*추가** 다른 방법의 바인딩클래스 사용 아답터 예제소개 *****
        //MyAdapter2 adapter2= new MyAdapter2(this, items);
        //mainBinding.recycler.setAdapter(adapter2);
    }
}

여기까지 리사이클러뷰에서 뷰바인딩을 적용하는 코드를 소개하였습니다.

 

※ 추가로. 아답터의 ViewHolder에서 뷰바인딩을 적용하는 조금 다른 방법도 소개해드리겠습니다. 어떤 것이 더 좋다 나쁘다는 것이 아니니 이런방법도 있구나 정도로 참고해 보시기 바랍니다.

# MyAdapter2.java
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

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

import com.mrhi2021.ex98viewbinding.databinding.RecyclerItemBinding;

import java.util.ArrayList;

public class MyAdapter2 extends RecyclerView.Adapter<MyAdapter2.VH> {

    Context context;
    ArrayList<ItemVO> items;

    public MyAdapter2(Context context, ArrayList<ItemVO> items) {
        this.context = context;
        this.items = items;
    }

    @NonNull
    @Override
    public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // recycler_item.xml의 뷰들과 연결돠는 바인딩 클래스 객체 생성  [ recycler_item.xml  --->   RecyclerItemBinding  ]
        //RecyclerItemBinding itemBinding= RecyclerItemBinding.inflate(LayoutInflater.from(context)); //부모인 parent 없이 inflate하면 아이템뷰의 사이즈가 이상해질 수 있음.
        RecyclerItemBinding itemBinding= RecyclerItemBinding.inflate(LayoutInflater.from(context), parent, false);
        return new VH(itemBinding);
    }

    @Override
    public void onBindViewHolder(@NonNull MyAdapter2.VH holder, int position) {
        //아이템의 값 연결 작업코드는 홀더안에 메소드를 만들어 처리
        holder.bindItem(items.get(position));
    }

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

    //아이템 뷰 1개의 자식뷰들 참조값을 저장하고 있는 ViewHolde 클래스 설계 - inner class
    //뷰 바인딩으로 이미 자식뷰들의 참조값들이 모두 연결되어 있음.
    class VH extends RecyclerView.ViewHolder{

        RecyclerItemBinding itemBinding;

        //다른 방법으로 아답터를 구현할 수도 있음.

        //기존에 생성자로 inflate한 ItemView 객체를 받아서 부모클래스(ViewHolder)의 생성자로 전달했었으나.[ super(itemView) ]
        //이제는 자식뷰들을 이미 참조하고 있는 바인딩객체를 전달받아 그 root뷰를 부모의 생성자에 전달.
        public VH(RecyclerItemBinding itemBinding) {
            super(itemBinding.getRoot());
            //뷰홀더 안에 멤버변수에 대입
            this.itemBinding= itemBinding;
        }


        //아이템뷰 1개 안에 있는 자식뷰들에 전달받은 아이템의 값들을 연결하는 기능메소드
        void bindItem(ItemVO item){
            itemBinding.iv.setImageResource(item.imgResId);
            itemBinding.tv.setText(item.title);
        }


    }
}

 

 뷰 바인딩은 레이아웃파일에 대응하여 자동으로 만들어지는 바인딩 클래스의 존재를 이해하고 그 바인딩 클래스의 멤버필드로 있는 id와 같은 이름을 가진 참조변수들로 뷰를 제어하는 부분만 이해하시면 명시적으로 findViewById()를 하거나, 외부 라이브러리이면서 @어노테이션과 뷰 참조변수를 직접 작성해야 하는 ButterKnife 라이브러리 보다 더 간결하게 코딩이 가능해지고 개발자의 실수로 다른 뷰의 id를 가지고 find를 하여 null 에러가 발생하는 빈도를 현저히 줄여주며, 성능적인 부분도 가장 우수하게 뷰와 상호작용할 수 있는 방법입니다. 그래서 안드로드이 공식 문서에서도 단순하게 뷰와의 상호작용을 하고자 한다면 뷰 바인딩을 권장하고 있으니 가급적 본인들의 앱 개뱔에 사용해 보실것을 권해드립니다.

 

다음 포스트 글로는 findViewById()를 대체하는 3가지 방법 중 마지막인 Data Binding(데이터 바인딩)에 대해 소개하겠습니다.

반응형

+ Recent posts