최근에 제가 안드로이드 면접을 봤었습니다.
RecyclerView 이야기가 나와서 설명하다가 ViewHolder에 대해서 설명해 보라는 면접 질문을 받았었는데, 막상 아는 내용이지만 설명하려고 하니 어렵더군요. 그래서 ViewHolder와 이를 사용하는 ListView와 RecyclerView에 대해서 간략하게 정리를 해 보았습니다.
1. ViewHolder란?
한마디로 설명하면 각 뷰를 보관하는 Holder 객체로 이야기 할 수 있습니다.
"ListView / RecyclerView 는 inflate를 최소화 하기 위해서 뷰를 재활용 하는데, 이 때 각 뷰의 내용을 업데이트 하기 위해 findViewById 를 매번 호출 해야합니다. 이로 인해 성능저하가 일어남에 따라 ItemView의 각 요소를 바로 엑세스 할 수 있도록 저장해두고 사용하기 위한 객체입니다." 정도로 설명할 수 있을 것 같습니다.
※ inflate? : xml 로 쓰여있는 View의 정의를 실제 VIew 객체로 만드는 것을 말함.
2. List View 에서 ViewHolder
※ 전체 소스 코드 : https://github.com/JunpilPark/SampleCode-Android/tree/base_listView
getView() 에서 넘어오는 파라메터인 convertView 사용하지 않고 매번 새로운 뷰를 inflate하면 메모리 낭비를 하게 됩니다. 그래서 보통 convertView 가 null 일 때만 inflate 되도록 해 ListView를 위한 Adapter의 getView는 아래와 같이 구현하게 됩니다.
이때 항상 아이템의 값을 setText하기 위해서 findViewById 를 통해 XML 리소스에 접근하게 되고 이는 성능저하를 나타내는 원인이 된다.
UserAdapter.java 파일의 일부
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
convertView = layoutInflater.inflate(R.layout.user_list_item, parent, false);
}
TextView txtName = convertView.findViewById(R.id.txt_value_name);
TextView txtMail = convertView.findViewById(R.id.txt_value_mail);
TextView txtMobile = convertView.findViewById(R.id.txt_value_mobile);
txtName.setText(userList.get(position).getName());
txtMobile.setText(userList.get(position).getMobile());
txtMail.setText(userList.get(position).getEmail());
return convertView;
}
ViewHolder 패턴을 사용하여 아래와 같이 수정이 가능합니다.
이렇게 되면 convertView가 null 일 때 inflate 하고, ViewHolder를 생성해 각요소를 findViewById 를 통해 저장합니다.
그리고, 다음부터는 ViewHolder를 getTag로 불러와서 재사용하게 됩니다. 이렇게하면 xml 리소스에 findeViewById로 직접 접근하지 않아도 됩니다.
그리고, 참고사항으로 여기서 ViewHolder클래스를 내부 클래스로 만드는 이유는 UserAdapter만 관련이 있고, 소스의 가독성을 높이기 위함입니다. static을 붙히는 이유는 상위클래스의 멤버변수나 객체를 사용하지 않겠라는 것을 명시적으로 표시하기 위함입니다.
UserAdapter.java 파일의 일부
public static class UserViewHolder {
TextView txtName;
TextView txtMail;
TextView txtMobile;
public void bind(String name, String mail, String mobile) {
txtName.setText(name);
txtMail.setText(mail);
txtMobile.setText(mobile);
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
UserViewHolder userViewHolder;
if(convertView == null) {
LayoutInflater layoutInflater = LayoutInflater.from(context);
convertView = layoutInflater.inflate(R.layout.user_list_item, parent, false);
userViewHolder = new UserViewHolder();
userViewHolder.txtMail = convertView.findViewById(R.id.txt_value_mail);
userViewHolder.txtName = convertView.findViewById(R.id.txt_value_name);
userViewHolder.txtMobile = convertView.findViewById(R.id.txt_value_mobile);
convertView.setTag(userViewHolder);
}
else {
userViewHolder = (UserViewHolder) convertView.getTag();
}
userViewHolder.bind(userList.get(position).getName(),
userList.get(position).getEmail(),
userList.get(position).getMobile());
return convertView;
}
3. ListView 의 장점, 단점
이러한 리스트 뷰는 ArrayAdapter 등을 제공하여 간단하고 빠르게 리스트를 만드는 장점이 있습니다.
하지만, 다양한 View Item이 필요하면 복잡한 커스텀작업이 필요하고, 애니메이션 사용이 어렵습니다.
그리고, 위에서 설명한 ViewHolder도 강제하지 않아 매번 inflate와 findViewById 가 호출되어 리소스 비용이 올라 갈 수 있습니다.
4. RecyclerView 의 ViewHolder
기본적인 RecyclerView의 사용방법은 공식문서(https://developer.android.com/guide/topics/ui/layout/recyclerview)에 잘 나와 있으니 링크를 참조해주시면 될 것 같습니다. 여기서는 ViewHolder 중심으로 쓰도록 하겠습니다.
RecyclerView의 경우는 Recycler.Adapter를 상속 받을 때 ViewHolder Type을 지정해야 합니다.
ListView에서는 ViewHolder 를 구현하지 않아도 되지만, RecyclerView는 구현을 강제하고 있습니다.
Recycler.Adapter를 상속하면, 몇 개의 메소드를 오버라이딩 해야합니다. 그 중 ViewHolder와 관련성이 높은 메소드는 onCreateViewHolder(), onBindViewHolder() 입니다.
ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 호출 되면서 새로운 View 를 생성할 때 실행되어 ViewHolder를 리턴합니다. 그리고, void onBindViewHolder(ViewHolder holder, int position) 에서 ViewHolder의 내용을 변경합니다.
아래는 제 github에 있는 샘플 소스코드를 발췌했습니다.
public class UserRecyclerAdapter extends RecyclerView.Adapter<UserRecyclerAdapter.UserViewHolder>{
public static class UserViewHolder extends RecyclerView.ViewHolder {
TextView txtName;
TextView txtMail;
TextView txtMobile;
public UserViewHolder(@NonNull View itemView) {
super(itemView);
txtName = itemView.findViewById(R.id.txt_value_name);
txtMail = itemView.findViewById(R.id.txt_value_mail);
txtMobile = itemView.findViewById(R.id.txt_value_mobile);
}
public void bind(String name, String mail, String mobile) {
txtName.setText(name);
txtMail.setText(mail);
txtMobile.setText(mobile);
}
}
@NonNull
@Override
public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.user_list_item, parent, false);
UserViewHolder userViewHolder = new UserViewHolder(view);
return userViewHolder;
}
@Override
public void onBindViewHolder(@NonNull UserViewHolder userViewHolder, int position) {
userViewHolder.bind(userList.get(position).getName(),
userList.get(position).getEmail(),
userList.get(position).getMobile());
}
}
'Tech & Programming > 모바일(Android, Flutter)' 카테고리의 다른 글
[안드로이드] Migrating to the New Places SDK Client (구 버전은 2019.7.29 종료) (0) | 2019.08.05 |
---|---|
View의 내용을 어떻게 이미지 파일로 만들었나요? (0) | 2019.05.07 |
안드로이드 http 프로토콜 접속 시 예외발생 조치 (ERR CLEARTEXT NOT PERMITTED) (18) | 2019.01.23 |
2018 GDG DevFest in 서울 참석 후기 (0) | 2018.11.11 |
AlertDialog의 setSingleChoiceItems 리스트 아이템이 안보이는 경우 확인사항 (0) | 2018.09.05 |