코틀린 기반으로 쓰여짐


1) 제트팩?

Android Jetpack is a set of components, tools and guidance to make great Android apps. The Android Jetpack components bring together the existing Support Library and Architecture Components and arranges them into four categories:

Android Jetpack은 훌륭한 Android 앱을 만들기 위한 구성 요소, 도구 및 지침 세트이다. Android Jetpack 구성 요소는 기존 지원 라이브러리 및 아키텍처 구성 요소를 결합하여 다음과 같은 네 가지 범주로 나뉩니다. 

  1. Architecture
  2. UI
  3. Foundation
  4. Behavior

 

 

https://android-developers.googleblog.com/2018/05/use-android-jetpack-to-accelerate-your.html

 

2) AAC?

AAC(Android Architecture Components)는 jetpack에서 Architecture에 해당하는 부분으로 테스트와 유지보수가 쉬운 안드로이드 앱을 디자인할 수 있도록 돕는 라이브러리 모음이다.

AAC를 활용하면 현재 구글에서 권장하는 MVVM 구조로 앱 설계가 가능해진다.
다음은 앱 아키텍쳐 가이드의 권장 구조의 도식도이다.

위 그림을 참고해서 보면 알겠지만 아키텍쳐파트에는 데이터 바인딩 라이프사이클 라이브데이타 네비게이션등이 있다.

오늘 우리는 아키텍쳐에 대해 본격적으로 공부하기 전 뷰 바인딩이라는것을 먼저 배워보려고 한다.

 

3) Viewbinding?

쉽게 얘기하면 findviewbyid를 쓰지 않고 XML의 view component에 접근하는 object를 반환받아 view에 접근하는 방식이다. object는 자동으로 만들어진다

3-1) 왜 써야해?

기본적으로 id는 같을 수 있다. findviewbyid 를 통하여 접근하게 되면 같은 id가 있다는 가정이면 어떤 id에 접근하는건지 명확하지 않다. 이를 방지하기 위하여 쓴다.또 레이아웃에 없는 id를 접근하게 될 때 nullsafe를 보장해준다.

 

3-2) 어떻게 써야해?

1)모듈 수준의 gradle 을 수정한다.

 

사용하고 있는 안드로이드 스튜디오 버전이 4.0 이상

buildFeatures {
        viewBinding true
    }

3.6 이상 4.0 미만

viewBinding {
        enabled = true
    }

사실 뭘 해도 둘다 적용되는걸로 보아 상관 없는거같다. 그래도 알고는 있자

 

2)액티비티에서 사용해보기

private lateinit var binding: ActivityMainBinding

 

 

참고로 액티비티 이름따라서 자동으로 만들어진다. 내 액티비티 이름이 mainActivity라 AcitivtyMainbinding인것이다. 이름 맞춰서 써주면 된다.

 

 

2-1)

원래는 R.layout.activity_main을 넘겨주지만 이번에는 우리가 생성한 루트 뷰를 넘겨준다

 

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

 

 

2-2) 사용해보기

binding.fab.setOnClickListener { view ->
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                .setAction("Action", null).show()
        }

위 코드는 리스너를 달아본것이다.

 

3)프레그먼트에서 사용해보기

class FirstFragment : Fragment() {

    private var _binding: FragmentFirstBinding? = null

    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        _binding = FragmentFirstBinding.inflate(inflater, container, false)
        return binding.root

    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.buttonFirst.setOnClickListener {
            findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

 

사실 액티비티랑 크게 다를건 없다.

 

 

 

우리가 하나 주목해봐야 할건 _binding 과 binding이 왜 두개나 있냐는건데

_binding은 우리가 사용하지 않을 때 자원을 null 값으로 만들어 주기 위해서 존재하는 것이다.

그니까 nul을 위해 존재 하는값임.

 


참고

안드로이드 뷰 바인딩(view binding) (tistory.com)

 

안드로이드 뷰 바인딩(view binding)

1. 뷰 바인딩  1-1. 라떼는 말이야...  1-2. 변천사  1-3. findViewById와의 차이점 2. 사용법  2-1. gradle 추가  2-2. 액티비티  2-3. 프래그먼트  2-4. viewBindingIgnore 1. 뷰 바인딩 1-1. 라떼는 말..

todaycode.tistory.com

 

코틀린 기반으로 쓰여짐


 

리사이클러뷰를 완벽 이해하려고 써보는 리사이클러뷰 포스팅

 

물론 틀린점 있을거다. 적극 피드백 환영

 

https://developer.android.com/jetpack/androidx/releases/recyclerview?hl=ko 

 

RecyclerView  |  Android 개발자  |  Android Developers

RecyclerView 메모리 사용량을 최소화하면서 UI에 많은 양의 데이터를 표시합니다. 이 표에는 androidx.recyclerview 그룹의 모든 아티팩트가 나열되어 있습니다. 이 라이브러리는 2022년 6월 29일에 최종 업

developer.android.com

 


리사이클러뷰란?

출처 - 안드로이드 디밸로퍼

그렇다. 리사이클러뷰란 메모리 사용량을 최소화 하면서 많은 양의 데이터를 표시하기 위한 도구이다.

그 전에 많이 쓰이던 리스트 뷰는 새로운 데이터가 필요할 때마다 기존에 있던 데이터는 삭제하고 새로운 데이터를 만드는 형식이였는데

리사이클러뷰는 기존에 있던 데이터를 재 활용해서 데이터의 낭비를 최소화 하는데 중점을 두었다.

아래 그림이 굉장히 잘 설명했다고 생각한다. 

또 오늘 만들어볼 것은 뷰바인딩을 이용한다. 고로 뷰 바인딩은 기본적으로 알고 있어야 한다!

고로 모른다면 가서 게시글 한번 보고 오기를 추천한다.

 

https://todaycode.tistory.com/29

 

안드로이드 뷰 바인딩(view binding)

1. 뷰 바인딩  1-1. 라떼는 말이야...  1-2. 변천사  1-3. findViewById와의 차이점 2. 사용법  2-1. gradle 추가  2-2. 액티비티  2-3. 프래그먼트  2-4. viewBindingIgnore 1. 뷰 바인딩 1-1. 라떼는 말..

todaycode.tistory.com

 

https://velog.io/@5y145/RecyclerView


 

각설하고 바로 만들어 보겠다.

 

1) 

틀을 만들어준다.

 

 

<?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/todo_list"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/clear_btn"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:itemCount="3" //미리볼 갯수!
        tools:listitem="@layout/item_todo"> //이렇게 하면 아이템 리스트들의 미리보기를 지원한다.


    </androidx.recyclerview.widget.RecyclerView>

    <Button
        android:id="@+id/clear_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="clear all"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/todo_list"
        app:layout_constraintStart_toStartOf="parent">

    </Button>
</androidx.constraintlayout.widget.ConstraintLayout>

 

2. 리사이클러뷰들의 아이템을 구성한다

 

 

<?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="100dp">

    <ImageView
        android:src="@drawable/ic_launcher_background"
        android:id="@+id/image_IV"
        android:layout_width="40dp"
        android:layout_height="40dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/todo_title_text"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </ImageView>

    <TextView
        android:id="@+id/todo_title_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/completed_check_box"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="do something">

    </TextView>

    <CheckBox
        android:id="@+id/completed_check_box"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </CheckBox>

</androidx.constraintlayout.widget.ConstraintLayout>

3. 데이터 클래스를 만들어준다.

 

Todo.kt

 

package com.example.todolist



data class Todo(
    val title : String,
    var completed : Boolean,


    )

 

4. 어댑터를 만들어준다

package com.example.todolist

import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.todolist.databinding.ItemTodoBinding


class TodoAdapter(private val todos:List<Todo>):RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {

    fun clearAll() {
        todos.forEach {
            it.completed=true
        }
        notifyDataSetChanged() //데이터 변경 됐으니까다시 호출해!
    }


   // 호출되는 횟수가 정해짐
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
       Log.d("TAG","onCreateViewHolder")
       val binding = (ItemTodoBinding.inflate(LayoutInflater.from(parent.context),parent,false))
       return TodoViewHolder(binding).also {
           binding.completedCheckBox.setOnCheckedChangeListener { compoundButton, b ->
               Log.d("TAG","isChecked")
               todos.getOrNull(it.adapterPosition)?.completed = b

           }
       }

    }
  // 스크롤을 할때 호출됨.
    override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
      Log.d("TAG","onBindViewHolder $position")
        holder.bind(todos[position])

    }

    override fun getItemCount(): Int= todos.size //아이템의 크기


    class TodoViewHolder(private val binding:ItemTodoBinding) :
        RecyclerView.ViewHolder(binding.root) {

            fun bind(todo:Todo) {
                binding.todoTitleText.text = todo.title
            // 재활용되는지 확인해보자.
              binding.completedCheckBox.isChecked = todo.completed

            }

    }
}

 

여기서 중요 포인트가 하나 있다.

onCreateViewHolder 에서 

  val binding = (ItemTodoBinding.inflate(LayoutInflater.from(parent.context),parent,false)) 

여기서 첫번째 파라미터로 context를 받아와야하는데 context는 주어지지 않는다.

그래서 간혹 Adapter에

class TodoAdapter(private val todos:List<Todo>,Context context) 이런식으로 주는 경우가 있는데

그럴 필요 없을 뿐더러 그러면 안된다.

Viewgroup은 view를 상속받았고 이미 context를 가지고 있다.

parent에 이미 context가 있음을 명심하자!

 

5. 연결해주기

MainAcitivty.kt

package com.example.todolist

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

class MainActivity : AppCompatActivity() {

    private lateinit var binding : ActivityMainBinding

    private val todos = listOf(
        Todo("list1",false),
        Todo("list2",false),
        Todo("list3",false),
        Todo("list4",false),
        Todo("list5",false),
        Todo("list6",false),
        Todo("list7",false),
        Todo("list8",false),
        Todo("list9",false),
        Todo("list10",false),
        Todo("list11",false),
        Todo("list12",false),
        Todo("list13",false),
        Todo("list14",false),
        Todo("list15",false),
        Todo("list16",false),
        Todo("list17",false),
        Todo("list18",false),
        

    )
    

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


        initializeViews()
    }

    private fun initializeViews() {

        binding.todoList.layoutManager = LinearLayoutManager(this)
        binding.todoList.adapter = TodoAdapter(todos)
        binding.clearBtn.setOnClickListener{

            (binding.todoList.adapter as TodoAdapter)?.clearAll()

        }

    }
}

 

 

실제로 이렇게 하드코딩하는 경우는 없겠지만 데이터베이스에서 가져오는건 나중에 따로 다루도록 하겠다!

 

 

6. 결과

 

 

 

하나 주목해서 봐야할껀 로그캣이다!

2022-08-22 21:36:35.663 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:35.688 9309-9309/com.example.todolist D/TAG: onBindViewHolder 0
2022-08-22 21:36:35.690 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:35.693 9309-9309/com.example.todolist D/TAG: onBindViewHolder 1
2022-08-22 21:36:35.696 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:35.699 9309-9309/com.example.todolist D/TAG: onBindViewHolder 2
2022-08-22 21:36:35.701 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:35.704 9309-9309/com.example.todolist D/TAG: onBindViewHolder 3
2022-08-22 21:36:35.706 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:35.710 9309-9309/com.example.todolist D/TAG: onBindViewHolder 4
2022-08-22 21:36:37.316 9309-9309/com.example.todolist D/TAG: isChecked
2022-08-22 21:36:38.215 9309-9309/com.example.todolist D/TAG: isChecked
2022-08-22 21:36:38.981 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:38.990 9309-9309/com.example.todolist D/TAG: onBindViewHolder 5
2022-08-22 21:36:39.163 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:39.169 9309-9309/com.example.todolist D/TAG: onBindViewHolder 6
2022-08-22 21:36:39.400 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:39.405 9309-9309/com.example.todolist D/TAG: onBindViewHolder 7
2022-08-22 21:36:40.201 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:40.206 9309-9309/com.example.todolist D/TAG: onBindViewHolder 8
2022-08-22 21:36:40.336 9309-9309/com.example.todolist D/TAG: onBindViewHolder 9
2022-08-22 21:36:40.338 9309-9309/com.example.todolist D/TAG: isChecked
2022-08-22 21:36:40.532 9309-9309/com.example.todolist D/TAG: onBindViewHolder 10
2022-08-22 21:36:40.535 9309-9309/com.example.todolist D/TAG: isChecked
2022-08-22 21:36:41.290 9309-9309/com.example.todolist D/TAG: onBindViewHolder 11
2022-08-22 21:36:41.370 9309-9309/com.example.todolist D/TAG: onBindViewHolder 12
2022-08-22 21:36:41.534 9309-9309/com.example.todolist D/TAG: onBindViewHolder 13
2022-08-22 21:36:42.335 9309-9309/com.example.todolist D/TAG: onBindViewHolder 14
2022-08-22 21:36:42.398 9309-9309/com.example.todolist D/TAG: onBindViewHolder 15
2022-08-22 21:36:42.499 9309-9309/com.example.todolist D/TAG: onBindViewHolder 16
2022-08-22 21:36:43.197 9309-9309/com.example.todolist D/TAG: onBindViewHolder 17
2022-08-22 21:36:45.398 9309-9309/com.example.todolist D/TAG: isChecked
2022-08-22 21:36:45.998 9309-9309/com.example.todolist D/TAG: isChecked

 

oncreateViewHolder가 일정 데이터가 지난후에는 더 이상 소환되지 않는다

왜? 재활용하기 때문에 더이상 create할 필요는 없어서이다.

 


참고

https://www.youtube.com/watch?v=RYM2H0Qzq9I 

 

+ Recent posts