[응용] ViewHolder 패턴과 viewBinding

쓰리원·2022년 4월 27일
0
post-thumbnail

1. 리스트뷰와 viewBinding으로 ViewHolder 구현하기.

viewBinding과 Listview에 대해서 기본적인 것을 안다고 전제하에 글을 작성하겠습니다. viewBinding의 경우 잘 모르시면 viewBinding 제가 작성한 글을 참고해주시기 바랍니다.

viewBinding 글 주소 : https://velog.io/@ilil1/%EA%B0%9C%EB%85%90-%EB%B7%B0-%EB%B0%94%EC%9D%B8%EB%94%A9view-binding

1. MainActivity.kt

class MainActivity : AppCompatActivity() {

    private lateinit var adapter : ListviewAdapter
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val list = ArrayList<Model>()
        var contact = Model("Add ","00000","https://picsum.photos/200")
        for(i: Int in 1..10)
            list.add(contact)
        adapter = ListviewAdapter(list)
        binding.listview.adapter = adapter
    }
}

viewBinding을 이용해서 findViewById() 직접 사용을 하지않고 ActivityMainBinding 클래스에 자동으로 캐싱된 레이아웃 id를 접근 해서 binding.listview.adapter 에 list 배열의 값을 파라미터로 넘겨받아 ListviewAdapter(list) 인스턴스를 할당해 줍니다.

실제 viewBinding 되서 xml의 주소를 가지고 클래스가 생성되어 있는 위치 입니다. 안의 코드도 한번 살펴보겠습니다.

public final class ActivityMainBinding implements ViewBinding {
  @NonNull
  private final LinearLayout rootView;

  @NonNull
  public final ListView listview;

  private ActivityMainBinding(@NonNull LinearLayout rootView, @NonNull ListView listview) {
    this.rootView = rootView;
    this.listview = listview;
  }

  @Override
  @NonNull
  public LinearLayout getRoot() {
    return rootView;
  }

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, null, false);
  }

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup parent, boolean attachToParent) {
    View root = inflater.inflate(R.layout.activity_main, parent, false);
    if (attachToParent) {
      parent.addView(root);
    }
    return bind(root);
  }

  @NonNull
  public static ActivityMainBinding bind(@NonNull View rootView) {
    // The body of this method is generated in a way you would not otherwise write.
    // This is done to optimize the compiled bytecode for size and performance.
    int id;
    missingId: {
      id = R.id.listview;
      ListView listview = ViewBindings.findChildViewById(rootView, id);
      if (listview == null) {
        break missingId;
      }

      return new ActivityMainBinding((LinearLayout) rootView, listview);
    }
    String missingId = rootView.getResources().getResourceName(id);
    throw new NullPointerException("Missing required view with ID: ".concat(missingId));
  }
}

위 코드를 좀더 아래에서 자세히 보겠습니다.

    int id;
    missingId: {
      id = R.id.listview;
      ListView listview = ViewBindings.findChildViewById(rootView, id);
      if (listview == null) {
        break missingId;
      }

      return new ActivityMainBinding((LinearLayout) rootView, listview);
    }

id = R.id.listview;
ListView listview = ViewBindings.findChildViewById(rootView, id); 를 보면 findChildViewById(rootView, id); 를 해서 우리가 뷰홀더에서 따로 하던 작업을 여기서 자동으로 해놓는 것을 알 수 있습니다.

2. ListviewAdapter.kt

class ListviewAdapter(
    private val items: List<Model>
) : BaseAdapter() {

    override fun getCount(): Int = items.size
    override fun getItem(position: Int): Any = items[position]
    override fun getItemId(position: Int): Long = position.toLong()

    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        val binding = if (convertView == null) {
            val tempBinding = BoardListBinding.inflate(LayoutInflater.from(parent?.context))
            tempBinding.root.tag = tempBinding
            tempBinding

        } else {
            convertView.tag

        } as BoardListBinding

        bind(binding, items[position])

        return binding.root
    }

    private fun bind(binding: BoardListBinding, data: Model) = with(binding) {
        thumbnail.clear()
        titleText.text = data.title
        contentText.text = data.content
        thumbnail.load(data.imageurl, 16f)
    }
}

위 코드중 getView를 따로 분석해 보겠습니다.

override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        val binding = if (convertView == null) {
            val tempBinding = BoardListBinding.inflate(LayoutInflater.from(parent?.context))
            tempBinding.root.tag = tempBinding
            tempBinding

        } else {
            convertView.tag
            
        } as BoardListBinding

        bind(binding, items[position])

        return binding.root
    }

3. Model.kt

data class Model(
    val title: String,
    val content: String,
    val imageurl : String
)

화면에 출력할 Model를 위와 같이(title, content, imageurl) 디자인 해줍니다.

2. 리사이클러뷰와 viewBinding으로 ViewHolder 구현하기.

1. MainActivity.kt

class MainActivity : AppCompatActivity() {

    private lateinit var adapter : RecyclerAdapter
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val list = ArrayList<Model>()
        var contact = Model("Add ","00000","https://picsum.photos/200")
        for(i: Int in 1..10)
            list.add(contact)

        adapter = RecyclerAdapter(list)
        binding.recyclerView.adapter = adapter
        binding.recyclerView.layoutManager = LinearLayoutManager(this)
    }
}

viewBinding을 이용해서 findViewById() 직접 사용을 하지않고 ActivityMainBinding 클래스에 자동으로 캐싱된 레이아웃 id를 접근 해서 binding.recyclerView.adapter 에 list 배열의 값을 파라미터로 넘겨받아 RecyclerAdapter(list) 인스턴스를 할당해 줍니다.

2. RecyclerAdapter.kt

class RecyclerAdapter(private val items: ArrayList<Model>) :
    RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {

    class ViewHolder(val binding: BoardListBinding) : RecyclerView.ViewHolder(binding.root) {

        //val title : TextView = itemView.findViewById(R.id.titleText)
        //val content : TextView = itemView.findViewById(R.id.contentText)
        //val thumbnail : ImageView = itemView.findViewById(R.id.thumbnail)
        
        fun bind(item: Model) {
            binding.thumbnail.clear()
            binding.titleText.text = item.title
            binding.contentText.text = item.content
            binding.thumbnail.load(item.imageurl, 16f)
        }
    }

    override fun getItemCount() = items.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerAdapter.ViewHolder {
       val binding = BoardListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
       return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: RecyclerAdapter.ViewHolder, position: Int) {
        val item = items[position]
        holder.bind(item)
    }
}

val title : TextView = itemView.findViewById(R.id.titleText)
val content : TextView = itemView.findViewById(R.id.contentText)
val thumbnail : ImageView = itemView.findViewById(R.id.thumbnail)

이전 리사이클러뷰에서는 위와 같이 findViewById를 직접해줘야 했습니다. 그러나 뷰바인딩 사용시 BoardListBinding 클래스에서 자동으로 해주기 때문에 그러한 과정이 필요가 없이 (binding. ~) 으로 참조해서 값을 정의해주면 됩니다.

3. Model.kt

data class Model(
    val title: String,
    val content: String,
    val imageurl : String
)

화면에 출력할 Model를 위와 같이(title, content, imageurl) 디자인 해줍니다.

3. 구현 코드

  1. 리스트뷰 : https://github.com/ilil1/listviewexamplewithviewbinding
  2. 리사이클러뷰 : https://github.com/ilil1/RecyclerviewExamplewithviewBinding

4. reference

https://medium.com/@yo___oo/kotlin%EC%9C%BC%EB%A1%9C-recyclerview-%EC%82%AC%EC%9A%A9-%EC%8B%9C-%EC%98%AC%EB%B0%94%EB%A5%B8-viewholder-%EC%82%AC%EC%9A%A9%EB%B2%95-31acb26fef9a

profile
가장 아름다운 정답은 서로의 협업안에 있다.

0개의 댓글