ListView는 사용자가 정의한 데이터 목록을 세로 방향으로 나열하여 화면에 표시하는 뷰 그룹의 한 종류입니다. 또한 스크롤 기능을 지원하고 사용자가 배치된 각 항목(item)을 선택하는 것도 가능합니다.
ListView는 화면에 View를 출력할 때 한 화면에 보이는 item의 수 만큼 메모리에 Inflate시킵니다.
(* Inflate(인플레이트)란? : xml에 쓰여있는 view의 정의를 실제 view객체로 만드는 역할 )
예시를 들면 ArrayList에 10개의 item들이 들어있지만 한 화면에 최대로 보이는 List Size가 5라면 ArrayList에서 5까지만 메모리에 Inflate하고 나머지 부분은 대기하고 있습니다. 그리고 있다가 스크롤을 통해 6,7번째 데이터가 보여야한다면 그 순간 메모리에 Inflate하여 View를 생성한다.
데이터 클래스 정의
Layout에 ListView의 추가
item 생성
어댑터 생성
어댑터 설정이다
어댑터 생성쪽을 한 번 보겠다.
Adatper에는 담당하는 기능은 다음과 같다
getView(Int, View ViewGroup) : xml 파일의 View와 데이터를 연결하는 핵심 역할을 하는 메소드이다.
getItem(Int) : 해당 위치의 item을 가지고 오는 메소드이다. Int 형식으로 된 position을 파라미터로 갖는다.
getItemId(Int) : item의 id를 반환하는 메서드이다.
getCount() : ListView에 속한 Item의 전체 수를 반환한다.
getView 부분을 코드로 보자면
override fun getView(position : Int, convertView : View?, parent: ViewGroup?) : View?
{
// LayoutInflater는 item을 Adapter에서 사용할 view로 부풀려주는 역할을 한다.
val view: View = LayoutInflater.from(context).inflate(R....., null)
// 위에서 생성된 View를 xml파일의 각 view와 연결한다.
val ride = view.findViewById<TextView>(R.id.Taxi)'
...
return view
}
위에 처럼 정의가 된다. 위에서 ArrayList가 들어가게 되면
override fun getView(position : Int, convertView : View?, parent: ViewGroup?) : View?
{
// LayoutInflater는 item을 Adapter에서 사용할 view로 부풀려주는 역할을 한다.
val view: View = LayoutInflater.from(context).inflate(R....., null)
// 위에서 생성된 View를 xml파일의 각 view와 연결한다.
val rideTarget = view.findViewById<TextView>(R.id.Taxi)'
val ride = rideList[position]
rideTarger.text = ride.target
return view
override fun getItem(position: Int) : Any{
return rideList[position]
}
override fun getItemId(position : Int) : Long{
Not Yet
}
override fun getCount() : Int {
return rideList.size
}
다음과 같이 Adapter가 구성된다. 이 후에 레이아웃에 포함된 childrenView들을 findViewById()를 통해 필요한 뷰에 데이터를 set해준다. 여기서 ListView의 동작과정에서의 문제점이 발견된다. 그 문제는 findViewById()이다.
Layout를 inflate할 때 뷰가 하나 밖에 없다면 해당 View가 맞는지에 대해 한번 만 확인을 하면된다. 하지만 View가 Group을 이루고 있다면 (ViewGroup) 경우에는 비교하여 맞지 않다면 그 아래의 childrenView들을 탐색하기 시작하는데 비교를 할때마다 findViewById()를 호출하는 것이다. 이것이 동작과정의 문제점이다. Layout이 깊어질수 록 viewGroup이 많을 수록 비교해야 하는 수는 많아지게 되고 똑같은 동작을 반복하다보니 해당 기능을 수행하는 비용이 커지게 된다.
장점이라면
단점은
-viewHolder을 강제하지않기 때문에 스크롤 과정에서 발생하는 Memory Inflate가 발생하고 그러다 보니 동작의 부드러움이 덜 할 수 있다.
RecyclerView는 위의 ListView의 메모리 인플레이트 부분의 단점을 보완을 한 뷰 그룹의 한 종류이다. 기본적인 동작의 방식은 ListView와 다름이 없다. 가장 큰 차이는 형식을 인플레이트 할때 그 개수이다. ListView의 경우 눈에 보이는 개수 만큼 인플레이트를 한 후 스크롤을 통해 다음 아이템들을 메모리에 인플레이트한다. 반면 RecyclerView의 경우는 처음 눈에 보이는 개수 만큼만 형식을 메모리 인플레이트한다. 그 후 스크롤을 통해 다음 형식이 올라와야한다면 반대로 스크롤을 통해 가려지는 형식도 있을 것이다. 그 형식을 다시 불러와 데이터만 다음 아이템의 데이터로 바꿔주는 것이다. 이렇게 하게 되면 계속 형식을 메모리에서 불러오는게 아니라 아이템의 데이터만 불러오는 것이기 때문에 Memory leak의 발생을 예방하고 부드러운 스크롤 이모션을 제공한다.
위에 ListView에서는 getView를 이용하여 데이터에 접근을 하는데 RecyclerView는 ViewHolder을 통해 객체를 재사용한다.
일단 Data와 View를 연결해주는 Adapter를 만들어 준다
class ModelRecyclerAdapter<M : Model, VM : BaseViewModel>(
private var modelList: List<Model>,
private val viewModel: VM,
private val resourcesProvider: ResoucesProvider,
private val adapterListener: AdapterListener
) : ListAdapter<Model, ModelViewHolder<M>>(Model.DIFF_CALLBACK) {
override fun getItemViewType(position: Int): Int = modelList[position].type.ordinal
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ModelViewHolder<M>{
return ViewHolderMapper.map(parent, CellType.values()[viewType], viewModel, resourcesProvider)
}
@Suppress("UNCHECKED_CAST")
override fun onBindViewHolder(holder: ModelViewHolder<M>, position: Int) {
holder.bindData(modelList[position] as M)
holder.bindViews(modelList[position] as M, adapterListener)
}
ViewBinding을 사용해놔서 다른 예시들과 다를 수 있기 때문에 약간의 설명을 하자면 ModelList에서 데이터를 가지고 오고 그 데이터 getItemViewType로 가지고 온 것이다. 그리고 onCreateViewHolder로 Holder를 만들어 onBindViewHolder로 통해 데이터를 전달해준다.
장점
단점