코틀린 기반으로 쓰여짐


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

 

본 포스팅은 각종 인터넷과 유튜브에서 올라온 자료를 재구성한것으로 틀릴 가능성이 다분히 있음.

코틀린 기반으로 쓰여짐


 

 

1.Modern Android Development?

Modern Android Development  |  Android 개발자  |  Android Developers

 

Modern Android Development  |  Android 개발자  |  Android Developers

Android 개발자를 위한 공식 사이트입니다. 앱 개발자 및 디자이너를 위한 Android SDK 및 문서를 제공합니다.

developer.android.com

 

Modern Android Development 는

 

 

Android팀에서 권장하는 개발 도구, API, 언어, 배포 기술을 통해 개발자는 생산성을 높이고 수많은 기기에서 실행되는 더 나은 앱을 만들 수 있습니다.

라고 안드로이드 디밸로퍼에 서술되어 있다.

 

근데 그래서 이게뭔데? 

쉽게 얘기하면 안드로이드 앱 번들 + 제트팩 + 코틀린 + 안드로이드 스튜디오를 합쳐서 일컫는 말이다.

오늘부터 포스팅할 내용은 앱 번들을 제외한 안드로이드 모던 디밸로퍼의 모든 내용들을 공부할 것이다.

코틀린과 안드로이드 스튜디오는 사실 필수이고, 주된 내용은 제트팩에 관한 얘기일 것이다.

 


2.Architecture Pattern

제트팩에 관해 공부하기전에 우리는 아키텍쳐 패턴에 관하여 알 필요가 있다.

아키텍쳐 패턴은 디자인 패턴의 상위 개념이라고 말할 수 있다.혹시나 아키텍쳐 패턴, 디자인 패턴이 무엇인지 모르는 사람들을 위하여 간단하게 서술하자면

 

주어진 상황의 소프트웨어 구조에서 발생하는 문제점을 해결하기 위한 일반화된 재사용 가능한 솔루션

정도로 정의할 수 있다. 대표적인 아키텍쳐 패턴으로는

계층화패턴, 마스터-슬레이브 패턴, 파이프-필터 패턴,브로커 패턴 등 엄청나게 많지만 

모바일 환경에서는 대표적으로 3가지가 있다.

 

1)MVC

Model + View + Controller

  • Model  어플리케이션에서 사용되는 데이터와 그 데이터를 처리하는 부분
  • View    사용자에게 보여지는 부분 (UI)
  • Controller 사용자의 입력을 받고 처리하는 부분

MVC 패턴의 장점은 널리 사용되고 있는 패턴이라는 점에 걸맞게 가장 하다. 보편적으로 많이 사용되는 디자인패턴이다.

MVC 패턴의 단점은 View와 Model 사이의 의존성이 높다는 것이다. View와 Model의 높은 의존성은 어플리케이션이 커질 수록 복잡하지고 유지보수가 어려워 진다.

 

2)MVP

Model + View + Presenter

  • Model       어플리케이션에서 사용되는 데이터와 그 데이터를 처리하는 부분
  • View         사용자에서 보여지는 UI 부분
  • Presenter  View에서 요청한 정보로 Model을 가공하여 View에 전달해 주는 부분

MVP 패턴의 장점은 View와 Model의 의존성이 없다는 것이다.

반면에 View와 Presenter 사이의 의존성이 높아 지는 단점이 있다.

 

 

3)MVVM

 Model + View + View Model

  • Model  어플리케이션에서 사용되는 데이터와 그 데이터를 처리하는 부분
  • View    사용자에게 보여지는 UI
  • View Model : View를 표현하기 위해 만든 View를 위한 Model

사실 MVVM 모델에서 아직도 제대로 이해하고 있는지 애매한 부분이 뷰 모델이다. 뷰란? 사용자에게 보여지는 UI 근데 사용자에게 보여지는 UI를 표현하기 위해 만든 뷰를 위한 모델?? 이 부분은 앞으로 공부하면서 차차 해결해 나가리라 생각한다.

 

 

 

MVVM 패턴은 Command 패턴과 Data Binding 두 가지 패턴을 사용하여 구현,View와 Model 사이의 의존성이 없다.

 View Model 사이의 의존성 또한 없앤 디자인패턴입니다. 각각의 부분은 독립적이기 때문에 모듈화 하여 개발가능하지만

MVVM 패턴의 단점은 View Model의 설계가 쉽지 않다는 점이 큰 단점이다.

 


3.Support Library

지원 라이브러리  |  Android 개발자  |  Android Developers

 

지원 라이브러리  |  Android 개발자  |  Android Developers

지원 라이브러리 참고: Android 9.0(API 수준 28)의 출시와 함께 Jetpack의 일부인 새로운 버전의 지원 라이브러리 AndroidX가 제공됩니다. AndroidX 라이브러리는 기존 지원 라이브러리를 포함하며 최신 Jetp

developer.android.com

지원 라이브러리는 몇 가지 뚜렷한 용도가 있습니다. 플랫폼의 이전 버전에 대한 하위 호환성 클래스는 그중 하나입니다. 

 

 

 

라고 안드로이드 디밸로퍼에 서술되어 있다. 쉽게 설명하면 api버전이 낮아서 지원 하지 않는 신규 기능도 지원 라이브러리를 통해서 쓸 수 있는것이다. 대표적으로 리사이클러뷰가 있다.

여기까지만 들으면 굉장히 좋은건데 이게 왜? 라고 할 수 있다. 이 지원라이브러리의 가장 큰 문제는 17년 구글에서 발표한What's new in Android Support Library에 따라 api버전 14부터 지원하게 되면서 문제가 커졌다. 

What's new in Android Support Library (Google I/O '17) - YouTube

지원 라이브러리를 임포트하다보면implementation com.android.support:appcompat-v7 ~~ 뭐 이런식으로 v가 붙은게 있다.이게 무슨뜻이냐? api 7부터 지원 가능하다는 뜻이다.근데 구글측에서 너무 오래된 기기의 사용을 막으려고 적혀진 버전과 상관없이 api 14부터 지원하게 된것이다.지원 라이브러리는 많은 라이브러리가 통합되어있기에 내가 사용하지 않는 기능도 포함된다. 이는 굉장히 비효율적이다.

 

이러한 비효율을 해결하기 위해,안드로이드 지원 라이브러리를 안드로이드 익스텐션 라이브러리로 바꿈과 동시에 제트팩을 도입하게 된다.

(38) Android Jetpack: What’s new in Android Support Library (Google I/O 2018) - YouTube

(38) Android Jetpack: What’s new in Android Support Library (Google I/O 2018) - YouTube

주요 내용은 기존에는 a가 업데이트 되어도 전체를 업데이트 해야했지만 이제는 a만 업데이트가 가능하다.

내용 자체가 워낙 방대하고 나도 다 몰라서, 동영상 보기를 추천한다.

 


4.Android JetPack

그래서 제트팩이 뭔데?

드디어 오늘 얘기할 본 내용이다.

 

Jetpack은 개발자가 관심 있는 코드에 집중할 수 있도록 권장사항 준수, 상용구 코드 축소, 모든 Android 버전 및 기기에서 일관되게 작동하는 코드 작성을 돕는 라이브러리 모음입니다.

 

사실 이렇게 얘기하면 알 수 없다. 오늘부터 안드로이드 디밸로퍼 다큐멘테이션을 따라서 하나하나 이것이 무엇인지 알아가보려고 한다.


참고

Android Jetpack 시작하기  |  Android 개발자  |  Android Developers

 

Android Jetpack 시작하기  |  Android 개발자  |  Android Developers

Jetpack에 기반해 간단한 앱을 빌드하는 방법을 알아보세요.

developer.android.com

Support library와 Androidx와 Jetpack::Android Studio에서 Kotlin으로#39 - YouTube

Support Library와 Androidx와 Jetpack (cliearl.github.io)

 

Support Library와 Androidx와 Jetpack

Support Library Support Library의 필요성 구글에서는 Android API의 하위 호환성 문제를 해결하기 위해 Support Library를 만들었습니다. 예를들어 롤리팝(API 21)부터 도입된 RecyclerView는 API 21 이하의 기기에서

cliearl.github.io

 

코틀린 기반으로 쓰여짐


 

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

 

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

 

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 

 

코틀린 기반으로 쓰여짐


구글링을 많이 해보아도 deprecated 된 이전 코드만 가득해서, 직접 찾아보고 오류 겪어가보며 구현해본 다중 이미지 선택이다. 물론 deprecated된거 쓰면 작동도 잘 된다. 스틱코드에도 코드가 그대로 남아있기도 하고 그렇지만 언제 없어질지 모를 불안함을 겪는거 보다는 이렇게 기록을 남기는 편이 나은거 같아 포스팅한다.


 

MainActivity.kt

 

1) 레이아웃 만들기

 

이미지를 여러장 가져오려면 어쩔 수 없이 리사이클러뷰가 필수다. 

main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>
<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">

    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="다중 이미지 가져오기"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="24dp"
        android:layout_marginBottom="16dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toTopOf="@+id/getImage"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView4" />

    <Button
        android:id="@+id/getImage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="36dp"
        android:text="GET IMAGE"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

MultiImageAdapter.kt

<?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">

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:scaleType="center"
        android:layout_marginHorizontal="5dp"/>

</androidx.constraintlayout.widget.ConstraintLayout>

 

이미지만 띄워줄거기 때문에 간단하게 구성한다.

 

2)어댑터 연결만들기

 

MultiImageAdapter.kt

package com.example.gallery

import android.content.Context
import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide

class MultiImageAdapter(private val items: ArrayList<Uri>, val context: Context) :
    RecyclerView.Adapter<ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val inflatedVIEW =
            LayoutInflater.from(parent.context).inflate(R.layout.multi_image_item, parent, false)
        return ViewHolder(inflatedVIEW)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {

        val item = items[position]
        Glide.with(context).load(item)
            .override(500, 500)
            .into(holder.image)


    }

    override fun getItemCount(): Int {
        return items.size
    }

}
class ViewHolder(v:View) :RecyclerView.ViewHolder(v) {
    private var view:View = v
    var image = v.findViewById<ImageView>(R.id.image)

    fun bind(listener:View.OnClickListener,item:String) {
        view.setOnClickListener(listener)
    }
}

 

리사이클러뷰를 이용하기 위해 어댑터를 만든다.

 

3) 메인액티비티에서 처리해주기

 

package com.example.gallery

import android.app.Activity
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_GET_CONTENT
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.MediaStore
import android.webkit.MimeTypeMap
import android.widget.Button
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.gallery.databinding.ActivityMainBinding
import java.io.File

class MainActivity : AppCompatActivity() {

    private lateinit var binding : ActivityMainBinding
    var list = ArrayList<Uri>()
    val adapter = MultiImageAdapter(list, this)


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this,R.layout.activity_main)

        var getImage_btn = findViewById<Button>(R.id.getImage)

        var recyclerView = findViewById<RecyclerView>(R.id.recyclerView)

        val layoutManager = LinearLayoutManager(this)
        recyclerView.layoutManager = layoutManager
        recyclerView.adapter = adapter

        val selectImagesActivityResult =
            registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
                if (result.resultCode == Activity.RESULT_OK) {
                    val data: Intent? = result.data
                    //If multiple image selected
                    if (data?.clipData != null) {
                        val count = data.clipData?.itemCount ?: 0
                        for (i in 0 until count) {
                            val imageUri = data.clipData!!.getItemAt(i).uri
                            list.add(imageUri)
                        }
                    }
                    //If single image selected
                    else if (data?.data != null) {
                        val imageUri: Uri? = data.data
                        if(imageUri !=null) {
                            list.add(imageUri)
                        }
                    }
                }
                adapter.notifyDataSetChanged()

            }


        binding.getImage.setOnClickListener {

            val intent = Intent(ACTION_GET_CONTENT)
            intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
            intent.type = "*/*"
            selectImagesActivityResult.launch(intent)



        }



    }

}

 

registerForActivityResult를 이용하여 startActivityForResult를 대신해준다.

 

 

 

4)결과

 

 

 

 


아마 이 블로그 포스팅을 보러온 대부분의 사람들이 deprecated 된 버전을 쓰기 싫어서 온것일텐데

사실 지금 내가 만들어 놓은 이 버전이 완벽한지 나도 알 수가 없다. 

참고용으로만 쓰길 적극 권장한다.

https://howtodoandroid.com/pick-multiple-files-android/

 

java에서 kotlin으로 넘어오면서 한번 해야겠다는 생각이 있었는데 드디어 정리해본다.


mainActivity.kt

 

package com.example.gallery

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.databinding.DataBindingUtil
import com.example.gallery.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding : ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this,R.layout.activity_main)

        val image = registerForActivityResult(
            ActivityResultContracts.GetContent(),
            ActivityResultCallback {

                binding.imageArea.setImageURI(it)
            }
        )


        binding.pickimage.setOnClickListener {

            image.launch("image/*")



        }



    }
}

acitivitymain.xml

 

<?xml version="1.0" encoding="utf-8"?>
<layout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent">


    <ImageView
        android:id="@+id/imageArea"
        android:layout_width="match_parent"
        android:layout_height="100dp">
    </ImageView>

    <Button
        android:id="@+id/pickimage"
        android:text="선택"
        android:textSize="20sp"
        android:textStyle="bold"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        >

    </Button>
</LinearLayout>
</layout>

 

 

 

 

 

StartActivityForResult가 deprecated 되었는데 그 이유는 

 

https://todaycode.tistory.com/121

 

startActivityForResult는 왜 deprecated 되었는가?

1. startActivityForResult 2. onActivityResult 3. 왜 deprecated 되었는가? 1. startActivityForResult 예전에는 호출한 액티비티로부터 결과를 받아오기 위해 startActivityForResult를 사용했다. 하지만 2020..

todaycode.tistory.com

 

아래블로그에 매우 잘 정리되어있다.

그러므로 나는 어떻게 대체하는가를 기술하려한다.

 

우선 오늘 내가 해볼것은 ActivityResultContracts 를 통한 갤러리 불러오기이다.

 

앱 수준에서 데이터 바인딩을 위해 수정을 좀 해주고.

 

대충 이미지뷰와 버튼 하나 만들어준다.

뷰 바인딩을 위해 layout 추가 해주는거 잊지말길

기존에 startActivityForresult와 onAcitivtyResult를 대체하는 방법이다.

 

 

코드 자체는 매우매우 단순하고 쉽다.

 


 

결과

참고

https://developer.android.com/training/basics/intents/result?hl=ko 

 

 

활동에서 결과 가져오기  |  Android 개발자  |  Android Developers

활동에서 결과 가져오기 개발자 앱 내의 활동이든 다른 앱의 활동이든 다른 활동을 시작하는 것이 단방향 작업일 필요는 없습니다. 다른 활동을 시작하고 다시 결과를 받을 수도 있습니다. 예를

developer.android.com

 

onclick 이벤트 적용하는 방법 정리해둔다.

 


1. xml에 직접적으로 연결시키기

보면 알겠지만 deprecated 됐다.

쓸 수는 있지만 지원을 안하니까 api버전이 다르면 작동 안할 수도 있으니 더 이상 쓰지말자.

 

2.OnClickListener 인터페이스 상속받아 사용

 

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState)

{

BtnOnClick btnOnClick= = new BtnOnClick();

Button btn1 = (Button) findViewById(R.id.btn1);

btn1.setOnClickListener(btnOnClick);

Button btn2 = (Button) findViewById(R.id.btn2);

btn2.setOnClickListener(btnOnClick);

} // 클래스를 따로 생성하여 이벤트 리스너 상속 class BtnOnClick implements View.OnClickListener { @Override public void onClick(View v)

{ switch (v.getId())

{ case R.id.btn1:

// btn1 동작

break;

case R.id.btn2:

// btn2 동작

break; }

}

}

}

3.익명 클래스 객체

Button btn1 = (Button) findViewById(R.id.btn1);

btn1.setOnClickListener(click);

Button btn2 = (Button) findViewById(R.id.btn2);

btn2.setOnClickListener(click);

View.OnClickListener click = new View.OnClickListener() {

@Override

public void onClick(View v)

{

switch (v.getId())

{ case R.id.btn1:

// btn1 동작 break;

case R.id.btn2:

// btn2 동작

break; } } };

4. Listener Class를 익명Class로 정의

activity 전환 예제

click 이벤트가 어디서 처리되는지 쉽게 확인할 수 있고, 코드가 간결하기 때문에 자주 사용된다.

  • 이벤트가 일어나는 view(Button 등)의 개수가 적거나, 이벤트가 일어나는 view 간의 연관성이 적은 경우
  • 이벤트 핸들러 함수 내에서 익명 클래스 외부의 변수를 참조하지 않는 경우
  • 간단한 view 클릭 이벤트 테스트 코드를 작성하는 경우

https://boheeee.tistory.com/23

출처

 

[Android] 안드로이드 여러 가지 방법으로 OnClickListener 사용하기

안녕하세요. 오늘은 안드로이드 OnClickListener를 사용하는 여러 가지 방법에 대해 알아보겠습니다. ( OnClickListener를 처음 접하시는 분은 적합하지 않은 글입니다. ) 안드로이드에서는 여러 방법으

boheeee.tistory.com

 

+ Recent posts