ListView와 RecyclerView

yugyeongKim·2023년 1월 4일
0

안드로이드

목록 보기
13/13

ListView

ListView가 해야하는 일은 매우 많다. 그래서 ListView의 부담을 줄일 수 있도록 기능을 분산해야한다.
기능 분산을 위해 데이터 관리를 위임받는 것이 Adapter

Adapter

  • 데이터를 적절히 가공하여 View에 전달 (예: TextView 설정, ImageView설정...)

ListView Adapter의 구성 요소

  • 실제 item을 담고 있는 List
  • item을 List에서 가져오는 함수
  • item에 고유한 ID를 가져오는 함수(대부분 List에서의 순서)
  • ListView에서 표현할 총 item 수를 가져오는 함수 등

ListView 구현하기

1. ListView 생성

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />


</androidx.constraintlayout.widget.ConstraintLayout>

Listview각각에 대한 레이아웃을 생성해준다.

  • activity_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <LinearLayout
        android:id="@+id/linear"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        >
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher"
            />
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_gravity="center"
            android:layout_marginLeft="10dp"
            >
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="미션임파서블"
                android:layout_marginBottom="10dp" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="4.4" />
        </LinearLayout>

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

tools:listitem="@layout/activity_item를 사용하면 화면을 바로 listView에 반영해서 확인할 수 있다.

2. 데이터 클래스 생성

변화하는 것이 이름과 별점인데 그것들을 하나의 데이터 클래스에 묶어서 만든다.

  • Movie.kt
package com.example.listview

data class Movie(val title:String, val movie_rating: Double)

3. adapter 생성

BaseAdapter: BaseAdapter를 상속받는다.
private val ~ : 아까만든 데이터타입에 맞춰 동일한 형태의 arrayList를 받아준다.

클릭 후

모두 받아온다.

11번째 줄:Context는 getSystemService 시스템에있는 api를 호출해서 어플리케이션에서 쓸 수 있는 쉽게 말해서 앱과 os의 중재자 역할
getView를 하기 위해 inflater필요

12번째 줄: 뷰바인딩을 사용하기 때문에 해당하는 xml의 바인딩 클래스 생성되어 있고 그걸 가져온다.
14번째 줄: 어레이리스트 크기 반환
16번째 줄:
18번째 줄:
20번째 줄:

override fun getView(position: Int, p1: View?, p2: ViewGroup?): View {
    binding = ActivityItemBinding.inflate(inflater,p2, false)

    binding.title.text = movieArrayList[position].title
    binding.rating.text = "${movieArrayList[position].movie_rating}"

    return binding.root
}

화면 크기만큼 레이아웃이 들어갈 수 있는 공간을 채울 때 최초로 실행되고 그다음에는 스크롤이 되어서 아이템이 보일때마다 호출되는 함수

리스트뷰의 아이템에 들어갈 title과 rating을 채워준다.

4. adapter 사용

adapter에 movieList를 넘겨주고 listView에 adapter를 넣어주면 완성



코틀린 List 정리글
참고 블로그

RecyclerView

RecyclerView 구현 하기

1. Gradle에서 Implementation 추가

dependencies {
    implementation 'com.android.support:recyclerview-v7:26.1.0'
    ...
}

2-1. 리사이클러뷰 요소의 레이아웃 만들기

  • list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:id="@+id/linear"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        >
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher"
            />
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_gravity="center"
            android:layout_marginLeft="10dp"
            >
            <TextView
                android:id="@+id/title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="미션임파서블"
                android:layout_marginBottom="10dp" />
            <TextView
                android:id="@+id/rating"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="4.4" />
        </LinearLayout>

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

부모 레이아웃의 높이를 match_parent로 하면 한페이지에 하나의 요소가 뜨는 불상사가 일어난다 그러니 높이는 wrap_content를 해야한다

2-2. MainActivity xml작성

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_data"
        android:layout_width="match_parent"
        android:layout_height="match_parent"

        />

</androidx.constraintlayout.widget.ConstraintLayout>

3. 데이터 클래스 생성

4. adapter 생성

  1. RecyclerView.Adapter 상속받기
  2. 생성자로 사용될 미리 만들어둔 데이터 형식의 타입인 ArrayList를 적어준다.
class CustomAdapter(val movieList: ArrayList<Movie>): RecyclerView.Adapter {
}

나머지도 완성해준다

class CustomAdapter(val movieList: ArrayList<Movie>): RecyclerView.Adapter<CustomAdapter.CustomViewHolder>() {

}

CustomViewHolder라는 클래스를 만든다
에러가 계속 나올텐데 해결 가능하다.

implement Members를 누르고 다 받아오면 된다.

인데 저거를 눌러도 안받아오진데 다른 방법으로 진행하니 저게 제대로 작동한다. 대체 뭐지? 일단 그건 차차 알아보고 제대로 되는 방법을 보자

class DataRVAdapter {

}

// 1. RecyclerView.Adapter 상속
class DataRVAdapter(): RecyclerView.Adapter<> {

}

//  2. DataViewHolder라는 클래스를 생성
class DataRVAdapter(): RecyclerView.Adapter<RVAdapter.DataViewHolder>() {

}

//  3. DataViewHolder중첩 클래스 작성
class RVAdapter(private val movieList: ArrayList<Movie>): RecyclerView.Adapter<RVAdapter.DataViewHolder>() {
    class DataViewHolder(private val viewBinding: ListItemBinding): RecyclerView.ViewHolder(viewBinding.root) {
        fun bind(data:Movie) {
        
   		}
    }
    ...
}

수업에서는 inner class로 되어있었지만 내부클래스 말고 중첩클래스를 사용해야하는 이유를 보고 3번을 수정했다.
(그리고 유튜브를 보면 3번을 하기 전에 implement Members가 되던데 나는 저걸 안적어주면 안되더라 무슨 이유인지는 찾아봐야겠다.)

  • 4.1 implement Members를 클릭한다.

create: ViewHolder 만들어질 때 실행할 동작
bindviewholder: ViewHolder가 실제로 데이터를 표시해야 할 때 호출되는 함수
getitemcount: 표현할 item 개수

  • 4.2 fun bind 함수 내용 작성

리스트뷰의 요소 레이아웃을 제어하는 내용들(예: TextView의 text, setOnClickListener등)

fun bind(data:Movie) {
    viewBinding.title.text = data.title
    viewBinding.rating.text = "${data.rating}"
}
  • 4.3 onCreateViewHolder 함수 내용 작성
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RVAdapter.DataViewHolder {
    val viewBinding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
    return RVAdapter.DataViewHolder(viewBinding)
}

어뎁트에서는 layoutInflater를 바로 가져올 수 없어서 부모의 context(activty에서 담고있는 모든 정보)정보를 이용해서 만들고 부모 정보도 넘겨주고, 부모에 바로 붙일거냐도 물어봄
그리고 여기서 부모는 연결할 activity 지금 상황의 parent는 MainActivity다.

return을 해줘서 커스텀 뷰홀더에 viewBinding을 담고 실행이 된다.

  • 4.4 getItemCount 함수 내용 작성
//1. movieList를 인자로 선언한다: 부모(메인액티비티)에서 변화하면 바로 수정이 되기때문)
class RVAdapter(private val movieList: ArrayList<Movie>) {
}

2. movieList의 크기를 반환
override fun getItemCount(): Int = movieList.size
  • 4.5 onBindViewHolder 함수 내용 작성
    onCreateViewHolder에서 만들어진 거를 실제 연결해주는 영역 스크롤하거나 할 때 지속적으로 호출되며 데이터들을 매치되는 곳
override fun onBindViewHolder(holder: RVAdapter.DataViewHolder, position: Int) {
    holder.bind(movieList[position])
}

5. adapter 연결

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.myapplication.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private lateinit var viewBinding: ActivityMainBinding

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

        val movieList: ArrayList<Movie> = arrayListOf()

        val RVAdapter = RVAdapter(movieList)
        RVAdapter.notifyDataSetChanged()

        viewBinding.rvData.adapter = RVAdapter
        viewBinding.rvData.layoutManager = LinearLayoutManager(this)

        Handler(mainLooper).postDelayed({
            movieList.apply {
                add(Movie("아이언맨1", 4.5))
                add(Movie("상견니", 4.5))
                add(Movie("토르 라그나로크", 4.4))
                add(Movie("윤희에게", 4.2))
                add(Movie("아이언맨1", 4.5))
                add(Movie("상견니", 4.5))
                add(Movie("토르 라그나로크", 4.4))
                add(Movie("윤희에게", 4.2))
                add(Movie("아이언맨1", 4.5))
                add(Movie("상견니", 4.5))
                add(Movie("토르 라그나로크", 4.4))
                add(Movie("윤희에게", 4.2))

            }
            RVAdapter.notifyDataSetChanged()
        }, 1000)
    }
}

0개의 댓글