Android) RecyclerView

김명성·2023년 4월 3일

이 글은 Inflearn의 CodeWithJoyce - [쌩초보 가능한 안드로이드 프로그래밍 A-Z : 앱으로 수익 창출까지] 강의를 보고 정리한 내용입니다.

RecyclerView

효율적으로 작동하는 ListView

필수 Override 메서드를 보니 RN의 VirtualizedList 같다.

구성요소

  • 어댑터
  • 레이아웃 매니저

어댑터

아이템 뷰를 생성하고, 보여주어야 하는 데이터와 바인딩한다.

아이템뷰 생성 예시 -

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/tv_importance"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="@color/red_400"
        android:gravity="center"
        android:text="2"
        android:textColor="@color/white"
        android:textSize="24sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginStart="16dp"

         />
        <TextView
            android:id="@+id/tv_title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toEndOf="@id/tv_importance"
            app:layout_constraintEnd_toEndOf="parent"
            android:text="할 일 제목"
            android:paddingHorizontal="16dp"
            />

</androidx.constraintlayout.widget.ConstraintLayout>

** xml 작성. (바인딩으로 해당 뷰를 객체의 프로퍼티처럼 사용 가능)

<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/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/btn_add_todo"
        />
    <Button
        android:id="@+id/btn_add_todo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/btn_add_todo"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginHorizontal="8dp"
        android:layout_marginBottom="8dp"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

데이터 바인딩

class TodoRecyclerViewAdaptor(private val todoList : ArrayList<TodoEntity>): RecyclerView.Adapter<TodoRecyclerViewAdaptor.MyViewHolder>() {


    inner class MyViewHolder(binding : ItemTodoBinding) : RecyclerView.ViewHolder(binding.root) {
        val tvImportance = binding.tvImportance
        val tvTitle = binding.tvTitle
        val root = binding.root
    }

    // MyViewHolder를 객체로 반환. (뷰 객체 생성)
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val binding: ItemTodoBinding = ItemTodoBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return MyViewHolder(binding)
    }

    // 방금 만든 뷰홀더와 데이터를 묶어주는 역할
    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val todoData = todoList[position]
        holder.tvImportance.text = todoData.importance.toString()
        holder.tvTitle.text = todoData.title
        
        when (todoData.importance) {
            1 -> holder.tvImportance.setBackgroundResource(R.color.red)
            2 -> holder.tvImportance.setBackgroundResource((R.color.yellow))
            3 -> holder.tvImportance.setBackgroundResource((R.color.green))
        }
    }
    
    // 아이템의 개수를 반환함.
    override fun getItemCount(): Int {
        return todoList.size
    }
}

Main Activity에서의 사용

class MainActivity : AppCompatActivity() {

    // findViewById를 사용하지 않고 뷰를 바인딩하기 위해 ViewBinding 라이브러리 사용
    // 여기서는 activity_main.xml에 존재하는 뷰를 바인딩함.
    // 1. xml(layout)의 뷰들을 바인딩 할 변수 설정
    private lateinit var binding: ActivityMainBinding
    private lateinit var db : AppDatabase
    private lateinit var todoDao: TodoDao
    private lateinit var todoList: ArrayList<TodoEntity>
    // 작성한 adapter(TodoRecyclerViewAdapter)를 가져온다
    private lateinit var adapter : TodoRecyclerViewAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
//      2. inflate : xml에 표기된 레이아웃들을 메모리에 객체화한다.
//       => XML에 작성된 코드를 객체화해서 해당 클래스에서 객체의 프로퍼티를 꺼내오듯이 사용한다.
        binding = ActivityMainBinding.inflate(layoutInflater)

//      3. setContentView를 호출하며 인수로 binding에 묶인 xml들을 객체화한다.
//      이후 xml에 기입된 UI 요소들을 끌어와 쓸 수 있게된다.
        setContentView(binding.root)

        db = AppDatabase.getInstance(this)!!
        todoDao = db.getTodoDao()

        getAllTodoList()

        binding.btnAddTodo.setOnClickListener {
            val intent = Intent(this, AddTodoActivity::class.java)
            startActivity(intent)
        }
    }

    private fun getAllTodoList() {
        Thread {
            todoList = ArrayList(todoDao.getAllTodo())
            setRecyclerView()
        }.start()
    }

    // UI 변경이기 때문에 메인쓰레드(UI쓰레드)에서 사용해야 함.
    private fun setRecyclerView() {
        runOnUiThread {
            adapter = TodoRecyclerViewAdapter(todoList)
            binding.recyclerview.adapter = adapter
            binding.recyclerview.layoutManager = LinearLayoutManager(this)
        }
    }

    override fun onRestart() {
        super.onRestart()
        getAllTodoList()
    }
}

AddTodo Activity 에서의 사용

class AddTodoActivity : AppCompatActivity() {

    // 여기서는 activity_add_todo.xml에 존재하는 뷰를 바인딩함.
    private lateinit var binding : ActivityAddTodoBinding
    private lateinit var db : AppDatabase
    private lateinit var todoDao: TodoDao

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityAddTodoBinding.inflate(layoutInflater)
        setContentView(binding.root)

        db = AppDatabase.getInstance(this)!!
        todoDao = db.getTodoDao()


        binding.btnComplete.setOnClickListener {
            insertTodo()
        }
    }

    private var impData = 0;
    private fun insertTodo() {
        val todoTitle = binding.edittextTitle.text.toString()

        when(binding.radioGroup.checkedRadioButtonId) {
            R.id.btn_high -> impData = 1;
            R.id.btn_middle -> impData = 2;
            R.id.btn_low -> impData = 3;
        }
        if(impData == 0 || todoTitle.isBlank()) {
            Toast.makeText(this, "모든 항목을 채워주세요", Toast.LENGTH_SHORT).show()
        }else {
            Thread {
                todoDao.insertTodo(TodoEntity(id = null, todoTitle, impData))
                runOnUiThread {
                    Toast.makeText(this, "추가되었습니다.", Toast.LENGTH_SHORT).show()
                    // addTodo activity 종료 메인엑티비티로 전환
                    finish()
                }
            }.start()
        }
    }
}

레이아웃 매니저

아이템뷰의 배치를 결정한다. (Linear , Grid, Staggered)

// UI 변경이기 때문에 메인쓰레드(UI쓰레드)에서 사용해야 함.
private fun setRecyclerView() {
    runOnUiThread {
        adapter = TodoRecyclerViewAdapter(todoList)
        binding.recyclerview.adapter = adapter
        binding.recyclerview.layoutManager = LinearLayoutManager(this)
        }
    }

This type has a constructor, and thus must be initialized here
이 유형에는 생성자가 있으므로 여기에서 초기화해야 합니다.
=> 상속시 ()을 작성하지 않았을 때 발생하는 오류

0개의 댓글