Groo
안드로이드 RecyclerView에 대해서 알아보자 본문
안녕하세요, 오늘부터 안드로이드 프로그래밍 관련 기술들에 대해서도 함께 글을 쓰려고합니다.
기초부터가 아닌 제가 프로그래밍을 직접 해보면서 중요하다고 느낀 기술들에 대해서 소개를하려고 합니다.
🤔 어떤 기술들을 위주로 설명?
안드로이드 프로그래밍 기술 중에서도 기초가 아닌 제가 프로그래밍을 하면서 자주 사용하기도 하며 중요하다고 생각하는 기술들에 대해서 소개를 하려고 합니다. 프로그래밍 언어는 Java 와 Kotlin 두 가지 버전으로 각각 설명을 하려고 하며 프로그래밍 예제들을 구현할 때 MVVM 디자인 패턴을 활용하여 코드를 작성하려고 합니다. 이 디자인 패턴에 대해서는 추후 글로 포스팅하겠습니다.
🤷♂️ RecyclerView 리사이클러뷰는 무엇인가?
이 기술은 현대 앱들이 대표적으로 사용하는 기능 중 한개입니다. 저 또한 프로그래밍을 할 때 RecyclerView를 많이 활용하여 개발을 많이 하였습니다. 그러나 매번 사용할 때마다 햇갈리고 기억이 나지 않아 구글에 검색을 해서 구현을 하는 것이 일상이었습니다. 그래서 저는 이번 기회에 확실히 개념 정리하기 위해 첫 번째 기능으로 소개를 하려고 준비를 이전부터 해왔습니다. 소개 방식은 글로만 하면 재미가 없으니 리사이클러뷰를 활용하여 간단한 예제를 진행하면서 설명을 하는 방식으로 진행을 하도록 하겠습니다.
🎨 RecyclerView Gralde 추가해보자!
먼저 리사이클러뷰를 사용하기 위해서는 아래의 코드와 같이 Android Project 안에 존재하는 build.gradle에 아래의 코드를 추가해주셔야합니다. 이 코드를 작성하여야만 이 프로젝트에서 리사이클러뷰를 사용할 수 있다는 것을 명심하여야 합니다.
// RecyclerView
implementation 'androidx.recyclerview:recyclerview:1.1.0'
📢 MainActivity XML을 구성하여보자!
이번에 저희는 리사이클러뷰를 활용하여 고객 정보를 추가하는 간단한 애플리케이션을 구성해보려고합니다. 그럼 먼저 메인 엑티비티의 레이아웃을 구성해보록 하겠습니다. 앱의 UI는 고객정보 현황을 알려주는 텍스트와 이름, 생년월일, 전화번호를 입력할 수 있는 EditText, 등록 버튼 그리고 리사이클러뷰로 간단하게 구성이 되어있습니다. 이 부분은 똑같이 진행을 하지 않아도 되므로 각자 자신이 원하는 디자인으로 하셔도 됩니다. 하지만 필수 구성 뷰들이 포함되어야 이번 예제에 따라오시는 것이 편할 것 같습니다.
<?xml version="1.0" encoding="utf-8"?>
<layout
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">
<data>
<variable
name="viewModel"
type="com.example.customerinfo.viewmodel.MainViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:orientation="horizontal">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="고객정보 현황"
android:textSize="20sp" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0.05"
android:text="@={viewModel.count}"
android:textSize="20sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:orientation="vertical">
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:text="이름(Name)"
android:textColor="#000000"
android:textSize="20sp" />
<EditText
android:id="@+id/edit_name"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/user_info"
android:ems="10"
android:inputType="textPersonName"
android:padding="10dp"
android:text="@={viewModel.name}"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:orientation="vertical">
<TextView
android:id="@+id/textView56"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:text="생년월일(Birth)"
android:textColor="#000000"
android:textSize="20sp" />
<EditText
android:id="@+id/editText1"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/user_info"
android:ems="10"
android:inputType="textPersonName"
android:padding="10dp"
android:text="@={viewModel.birth}"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:orientation="vertical">
<TextView
android:id="@+id/textView55"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:text="전화번호(Phone)"
android:textColor="#000000"
android:textSize="20sp" />
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/user_info"
android:ems="10"
android:inputType="textPersonName"
android:padding="10dp"
android:text="@={viewModel.phone}"/>
</LinearLayout>
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:text="등록하기"
android:onClick="@{() -> viewModel.addEvent()}"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</LinearLayout>
</LinearLayout>
</layout>
🥇 RecyclerView Item 구성해보기!
위에서 리사이클러뷰와 전체적인 메인 화면의 UI 디자인을 하였다면 이번에는 리사이클러뷰 안에 포함되는 아이템들을 디자인하도록 하겠습니다. RecyclerView 안에 포함되는 디자인들을 생각해보면 모두 똑같은 디자인에 안에 내용 즉 데이터들만 다른 것을 볼 수 있습니다. 즉 같은 틀에 데이터만 다르게 포함하면 된다는 것입니다. 저희는 고객 정보를 추가할 것이니 유저의 사진을 디폴트로 설정을 하였으며 이름, 생년월일, 전화번호 텍스트를 구성하였으며 이 3가지 텍스트의 데이터만 앞으로 변경할 것입니다.
<?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="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="@drawable/user_info"
android:orientation="horizontal">
<ImageView
android:id="@+id/imageView"
android:layout_width="100dp"
android:layout_height="100dp"
app:srcCompat="@drawable/ic_person_black_24dp"
tools:ignore="VectorDrawableCompat" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="15dp"
android:text="이름"
android:textSize="20sp" />
<TextView
android:id="@+id/birth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="생년월일"
android:textSize="20sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/phone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="전화번호"
android:textSize="20sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
🍜 RecyclerView item 데이터 관리하기
RecyclerView에 들어갈 item들의 레이아웃을 모두 구성하였다면 item 레이아웃에 들어가는 데이터들을 관리하는 코드를 작성해보도록 하겠습니다. 현재 리사이클러뷰에 들어가는 item의 데이터는 이름, 생년월일, 전화번호 3가지입니다. 따라서 데이터를 관리하는 클래스를 저는 User 클래스로 정의를 하였으며 그 클래스 안에 위의 3가지 데이터를 모두 추가하여야합니다.
📗 Java 언어 활용해보기
User라는 클래스를 통해서 위에서 요구하는 사항들을 매개변수 생성자로부터 전달 받습니다. 또한 Getter, Setter을 통해 외부에서도 User 클래스의 맴버변수들의 값들을 참조할 수 있도록 허용하고 있습니다. (이번 예제에서는 Setter를 사용하지 않습니다.)
public class User {
String name;
String birth;
String phone;
public User(String name, String birth, String phone) {
this.name = name;
this.birth = birth;
this.phone = phone;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBirth() {
return birth;
}
public void setBirth(String birth) {
this.birth = birth;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
📘 Kotlin 언어 활용해보기
Kotlin 또한 Java와 크게 다르지 않습니다. Getter와 Setter 부분이 존재하지 않아 Java 언어보다 간결한 것을 볼 수 있습니다.
class User(val name : String, val birth : String, val phone : String) { }
⛳ MainActivity 코드를 작성해보자!
처음에 구성하였던 MainActivity의 XML 코드에서는 MVVM 디자인 패턴을 활용한 것을 볼 수 있습니다. 때문에 저희는 View 즉 Activity와 ViewModel을 연결시켜주는 작업을 수행하여야합니다. 그래서 MainActivity에서 뷰모델과 연결시키겠습니다.
📗 Java 언어 활용해보기
아래의 코드는 Java 언어를 활용하여 View와 ViewModel을 연결시켜주는 작업을하고 있습니다. 이러한 작업은 코드의 효율성을 높여주는 디자인 패턴을 사용하기 위해서입니다. 디자인 패턴에 대한 설명은 다음 번에 자세히 설명을 하는 시간을 가지도록 하겠습니다. 지금은 View에서 처리하는 과정을 ViewModel에서 처리를 위해 인수인계를 하고 있다고 생각을 하시면 좋을 것 같습니다.
public class MainActivity extends AppCompatActivity {
ActivityMainBinding binding;
MainViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
viewModel = ViewModelProviders.of(this).get(MainViewModel.class);
binding.setViewModel(viewModel);
binding.setLifecycleOwner(this);
}
}
📘 Kotlin 언어 활용해보기
Kotlin 언어 또한 위의 Java 코드와 똑같은 형식과 목적으로 코드가 작성이 되었으며 언어만 다를 뿐 크게 다른 점은 존재하지 않습니다. 위에서도 말했듯이 이 과정을 통해 View와 ViewModel은 서로 연결이 되게 되는 것을 볼 수 있습니다.
class MainActivity : AppCompatActivity() {
lateinit var binding : ActivityMainBinding
lateinit var viewModel : MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = DataBindingUtil.setContentView(this@MainActivity, R.layout.activity_main)
viewModel = ViewModelProviders.of(this@MainActivity).get(MainViewModel::class.java)
binding.viewModel = viewModel
binding.lifecycleOwner = this@MainActivity
}
}
⛪ MainViewModel 작성하기
먼저 저희는 MainViewModel의 코드를 작성해보도록 하겠습니다. 추후 XML 레이아웃과 MainViewModel 레이아웃을 연결시켜줄 것이니 XML 레이아웃과 MainViewModel을 연결이 되는 변수들을 선언해주었습니다. 변수들의 자료형은 라이브 데이터로 실시간으로 값이 변경되는 것을 확인할 수 있도록 MutableLiveData 자료형을 선택하였으며 View와 통신을 위해 SingleLiveEvent를 사용하여 Observer 관찰을 하는 역할을 통해 서로 소통을 할 수 있도록 구성하였습니다.
📗 Java 언어 활용해보기
public class MainViewModel extends ViewModel {
public MutableLiveData<String> count = new MutableLiveData<>();
public MutableLiveData<String> name = new MutableLiveData<>();
public MutableLiveData<String> birth = new MutableLiveData<>();
public MutableLiveData<String> phone = new MutableLiveData<>();
public SingleLiveEvent onClickEvent = new SingleLiveEvent();
public void addEvent(){
onClickEvent.call();
}
}
📘 Kotlin 언어 활용해보기
class MainViewModel : ViewModel() {
val count = MutableLiveData<String>()
val name = MutableLiveData<String>()
val birth = MutableLiveData<String>()
val phone = MutableLiveData<String>()
val onClickEvent = SingleLiveEvent<Unit>()
fun addEvent() = onClickEvent.call()
}
위에서 MainViewModel 클래스의 변수들을 다 구성을 하였다면 저희는 처음에 보여주었던 MainActivity XML 레이아웃의 각각의 뷰들과 MainViewModel을 서로 연결해주는 작업을 실시할 것 입니다. 그 과정을 통해 각각의 뷰들을 실시간으로 처리됩니다.
// ViewModel의 MutableLiveData 변수들과 각각 연결을 하고 있는 모습
<TextView
...
android:text = "@={viewModel.count}"/>
<EditTxt
...
android:text = "@={viewModel.name}" />
...
android:text = "@={viewModel.birth}"
android:text = "@={viewModel.phone}"
대부분의 TextView와 EditText 등은 ViewModel과 연결을 할 때 위와 같이 연결합니다. 하지만 클릭 이벤트와 같은 이벤트를 처리해야하는 부분들은 아래의 코드처럼 람다식의 화살표를 포함하여 ViewModel에서의 함수를 호출하는 방식을 볼 수 있습니다.
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:text="등록하기"
android:onClick="@{() -> viewModel.addEvent()}"/>
🥗 RecyclerView Adapter 설정하기!
RecyclerView를 사용하기 위한 가장 중요한 부분인 Adapter 설정을 해주어야합니다. 이 어댑터에서 이전에 만든 RecyclerView item과 데이터 관리 클래스 등을 모두 연결시켜줄 것이며 사용자로부터 데이터를 전달 받아 처리하는 역할을 진행합니다. Adapter에서는 각각의 기능별로 설명을 드리려고 합니다. 내용이 많아 분활을 하여 진행을 하려고 합니다. 이해를 하는 것이 중요합니다.
# ViewHolder 이해하기
리사이클러뷰의 아이템들이 리스트 형태로 보일 때 각각의 아이템들은 모두 뷰로 형성되며 뷰홀더에 담게 됩니다. 이 뷰홀더 역할을 하는 클래스는 RecyclerView Adapter 안에 포함이 되게 되며 RecyclerView.ViewHolder 클래스를 상속받아 정의되며 생성자로 뷰 객체가 전달이 됩니다. 이 전달받은 뷰 객체를 통해서 추후 설정하는 레이아웃의 뷰들을 참조할 수 있습니다. 또한 ViewHolder 클래스에 setItem() 매서드를 만들어 각각의 리스트 아이템에 데이터를 변경하는 역할을 하는 중요한 매서드입니다.
📗 Java 언어 활용해보기
위에서 설명을 하였듯이 ViewHolder 클래스는 RecyclerView.ViewHolder 클래스를 상속하고 있으며 생성자의 매개변수로 View 객체를 전달 받아 각각의 레이아웃 뷰들을 참조할 때 도움을 주고 있는 모습을 볼 수 있습니다.
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView name;
TextView birth;
TextView phone;
public ViewHolder(@NonNull View itemView) {
super(itemView);
name = itemView.findViewById(R.id.name);
birth = itemView.findViewById(R.id. birth);
phone = itemView.findViewById(R.id.phone);
}
public void setItem(User item){
name.setText(item.getName());
birth.setText(item.getBirth());
phone.setText(item.getPhone());
}
}
📘 Kotlin 언어 활용해보기
Kotlin 언어 또한 똑같은 역할을 하고 있습니다. 그러나 코드가 더욱 간결하고 가독성이 더 높은 모습을 볼 수 있습니다.
open class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val name : TextView = itemView.findViewById(R.id.name)
val birth : TextView = itemView.findViewById(R.id.birth)
val phone : TextView = itemView.findViewById(R.id.phone)
fun setItem(item : User){
name.text = item.name
birth.text = item.birth
phone.text = item.phone
}
}
# RecyclerView Adapter 이해하기
RecyclerViewAdapter는 위에서의 ViewHolder 클래스를 감싸고 있는 클래스입니다. 이번 예제에서 이 클래스의 이름은 UserAdapter이라고 부릅니다. UserAdapter 클래스는 RecyclerView.Adapter 클래스를 상속받고 있으며 앞에서 지정한 ViewHolder 클래스를 또한 지정합니다. 그러면 이제 UserAdapter 클래스는 필수 구현 매서드 3가지가 존재하게 됩니다.
getItemCount() 매서드
RecyclerView Adapter 어댑터에서 관리하는 아이템의 개수를 반환합니다.
리스트 아이템을 보관하는 ArrayList items 변수에 데이터들의 size를 반환하는 역할을 합니다.
onCreateViewHolder() 매서드
앞에서 정의한 뷰 홀더가 새롭게 만들어지는 시점에 호출이 됩니다.
그 안에서 각각의 아이템을 위해 정의한 XML 레이아웃을 뷰 객체로 만들어줍니다.
그 후 그 객체를 반환하면서 ViewHolder 클래스에서 레이아웃을 참조할 수 있도록 도와줍니다.
즉 리사이클러뷰에 보이는 레이아웃 구성을 설정하고 인플레이션하는 매서드이라고 생각하면 됩니다.
onBindViewHolder() 매서드
애플리케이션은 내부의 한정된 용량이 있기 때문에 매번 뷰 홀더를 다 만들지는 않습니다.
onCreateViewHolder 매서드를 통해 생성한 객체를 재활용하여 내부의 데이터만 바꾸는 형식입니다.
ViewHolder 클래스에 새로운 데이터를 담아 설정을 하여서 같은 객체에 다른 데이터를 포함합니다.
addItem() 매서드
이 매서드는 필수 구현 매서드는 아니지만 외부에서 RecyclerView에 데이터를 추가할 때 사용합니다.
매개변수로 User 데이터 관리 클래스의 값을 (name, birth, phone) 등을 받아오는 역할을 합니다.
그 후 Adapter 클래스 안에 포함된 items 데이터 배열에 저장을 하여 추후 사용할 수 있도록 합니다.
📗 Java 언어 활용하기
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder>{
ArrayList<User> items = new ArrayList<User>();
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(R.layout.user_item, parent, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
User item = items.get(position);
holder.setItem(item);
}
@Override
public int getItemCount() {
return items.size();
}
public void addItem(User item){
items.add(item);
}
}
📘 Kotlin 언어 활용하기
class UserAdapter : RecyclerView.Adapter<UserAdapter.ViewHolder>() {
val items = ArrayList<User>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val itemView = inflater.inflate(R.layout.user_item, parent, false)
return ViewHolder(itemView)
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item : User = items.get(position)
holder.setItem(item)
}
fun addItem(item : User){
items.add(item)
}
}
🎉 RecyclerView & Adapter 연결하기!
이제 Adapter에 대한 모든 설정이 끝난다면 MainActivity에서 Layout의 RecyclerView와 Adapter를 서로 연결을 시켜주고 데이터를 전달만 해주면 모든 준비는 끝나게 됩니다. 저희는 MVVM 디자인 패턴을 사용하였기 때문에 MainActivity에서의 이벤트 관찰을 하는 observerViewModel 매서드에서 그 과정이 진행되며 이 과정은 어렵지 않게 진행을 할 수 있습니다.
📗 Java 언어 활용하기
UserAdapter adapter = new UserAdapter();
int counter = 0;
public void observerViewModel(){
viewModel.onClickEvent.observe(this, new Observer() {
@Override
public void onChanged(Object o) {
adapter.addItem(new User(viewModel.name.getValue(), viewModel.birth.getValue(), viewModel.phone.getValue()));
binding.recyclerView.setAdapter(adapter);
viewModel.count.setValue(++counter + "명");
}
});
}
📘 Kotlin 언어 활용하기
val adapter = UserAdapter()
var counter = 0
fun observerViewModel(){
with(viewModel){
onClickEvent.observe(this@MainActivity, Observer {
adapter.addItem(User(viewModel.name.value.toString(), viewModel.birth.value.toString(), viewModel.phone.value.toString()))
binding.recyclerView.adapter = adapter
viewModel.count.setValue((++counter).toString() + "명")
})
}
}
👍 글을 마치며
오늘은 RecyclerView를 활용한 간단한 예제를 구성해보았습니다. 오늘 글을 적으면서 Java와 Kotlin 두 가지 언어로 설명을 한다고 까다롭기도 하였으며 햇갈리도 하였습니다. RecyclerView 기능을 많은 사람들이 이용을 하지만 그 기능들에 대한 자세한 이해와 개념을 정리한 사람은 소수였습니다. 그래서 저는 그 개념들을 확실히 이해하고 코드를 작성한다면 더욱 효율성이 있다고 생각을 하여 이번에 글을 작성하게 되었습니다. 아직은 많이 부족하고 미숙하지만 앞으로 더 열심히 하도록 하겠습니다. 감사합니다.
'Android' 카테고리의 다른 글
Android Studio 레이어 분리 방법에 대해서 (2) | 2020.05.23 |
---|---|
Koin 코인 라이브러리 (1) | 2020.05.07 |
ViewModel & LiveData 라이브러리 (7) | 2020.03.19 |
DataBinding 데이터 바인딩 라이브러리 (0) | 2020.02.25 |
Butter Knife 버터 나이프 라이브러리 (4) | 2020.02.23 |