농농씨 2023. 10. 27. 16:40

아이템xml에서 아이템 크기 wrap_content해서 너무 크지 않게

 

헷갈리는 부분-액티비티와 프래그먼트에서 binding.root 불러오는 차이

어느 메서드(onCreate, onCreateView 등)에서 binding.root 불러오는지

Feat. setContentView

 

 

<Adapter>

어댑터 메서드 역할 이해안됨

 

뭘 어떤 타입으로 불러오는지 매번 헷갈림

자꾸 뷰홀더를 상속받는데 형식이 눈에 안익었음

 

레이아웃, 액티비티 따로 만들었을때 binding 어떻게 하는지

 

이미지 리소스 안불러와질때 의욕상실

 

뷰홀더는~ inner class로서~~ implement하는 members와 다르게 따로 만들어줘야함~

 

뷰홀더는 아이템뷰 객체 가짐, 그래서 아이템레이아웃을 바인딩해야함.

그리고 이 뷰홀더는 RecyclerView의 ViewHolder를 상속받음. 이때(상속받을때) 필요한 정보는 뷰 객체이므로 binding.root를 인자로 전달함

 

그리고 RecyclerView의 Adapter를 상속받기 때문에 세가지 method를 필수로 override 해야 함.

 

ViewHolder는 아이템뷰를 관리함.

 

onBindViewHolder는 스크롤할때마다 어떤 동작하도록 함.

 

onBindViewHolder의 코드를 간소화 시키기 위해 ViewHolder에 함수를 별도로 선언할 수 있음.

(원래 코드는 뭐였지?)

왜 ViewHolder에 선언하는지는? 모르겠음.

ViewHolder에서 item 레이아웃 바인딩한 김에 텍스트뷰 수정하는 작업도 ViewHolder에 선언해놓자는 건가?

 

onCreateViewHolder는 inner class인 ViewHolder를 상속받고

onBindViewHolder는 inner class인 ViewHolder를 인자로 받는다.

 

 

onCreateViewHolder는 뷰홀더 객체가 생성될때 하는 작업 명시

ViewHolder도 item 레이아웃을 binding하고

그 ViewHolder를 상속받은 onCreateViewHolder는 binding.inflate로 뷰를 객체화함

onCreateViewHolder는 ViewHolder를 반환함(객체화했으니까?)=> return ViewHolder(binding) 그냥 외워야하나?

 

onBindViewHolder는 holder:ViewHolder 로 파라미터를 받는데…

class를 파라미터로 받는다는 게 이해가 되지 않음

아무튼 여기서 아까 ViewHolder에 선언한 bind라는 함수를 사용함

 

<MainActivity>

데이터를 만들어주구

하… 그냥 함수 새로 만드는거랑 아닌거랑 코드 친절하게 비교 매번 해주면 좋겠다

 

리스트 어디에 만드는지도 헷갈림

일단 메서드 밖에 String 타입으로 Array리스트 만들어주구(어댑터에 전달하는 리스트와 타입 동일해야함)

WordAdapter 타입의 wordAdapter?라는 변수도 메서드 밖에 선언함

 

onCreate안에 리스트 초기화하는 함수 호출 먼저 해놓고

메서드 밖에 함수 내용 선언함(리스트에 내용 넣는 과정)

 

데이터 여러개 넣을때 자동 줄나눔 단축키=>option+up방향키+Command+L ???

 

리스트 초기화하듯이

리사이클러 뷰 초기화하는 함수를

또 onCreate안에 호출을 먼저 해주고

메서드 밖에 함수 내용 선언함

 

아까 메서드 밖에 wordAdapter: WordAdapter? = null

이런식으로 어댑터파일을 상속받은 변수를 선언했었는데

리사이클러뷰 초기화하는 함수 내용에

wordAdapter = WordAdapter(wordList)

리스트를 전달한 어댑터 파일을 할당함

그리고

binding.recyclerView.adapter = wordAdapter

메인액티비티 binding한 것에서

recyclerView를 가져와서

그 adapter에

리스트 전달한 어댑터를 연결해줌

그리고 

binding.recyclerView.layoutManager

=LinearLayoutManager(applicationContext, LinearLayoutManager.VERTICAL, false)

 라고

마무리해줌

(이때 context를 얻는 방법은 activity와 fragment가 다름)

아무튼 여기까지의 구문을 리사이클러뷰초기화함수 안에 따로 넣어줘서 onCreate구문을 간소화?

(사실 다른 생명주기 메서드들이 필요한지? 잘 모르겠음)

 

[ClickListener 구현]

강의 내용 그대로

“recyclerVIew는 adapter를 통해 itemView를 만드는데,

이 itemView는 ViewHolder객체에 저장돼서 화면에 표시되고, 필요에 따라 생성 또는 재활용된다.”

“이때, itemClick 이벤트 Listener를 자신이 직접 다루지 않고 itemView에서 onClickListener를 통해 처리하게 만들어놓았다.”

 

<Adapter>: 인터페이스 만들기

사실 인터페이스가 뭔지 모르겠음

아무튼 adapter에 interface를 만들어서 선언해줌

인터페이스는 추상메서드를 구현할 수 있다.

한마디로 어댑터에서 메서드를 명시만 해놓고(구조도만 제공하는 역할)

액티비티나 프래그먼트에서 세부내용 구현

 

어댑터에서 인터페이스 위한 변수를 선언하는 부분부터 상당히 이해가 안감!

private latent var itemClickListener: OnItemclickListener

(내가 만든 On인지 on인지도 많이 헷갈렸음

그냥 싹다 On이고 상속받는 걸 의미했던건가?)

아무튼 OnItemClickListener를 상속받은 itemClickListener라는 변수를 새로 선언해줬음

 

이때, ViewHolder에 선언된 bind라는 함수는

OnBindViewHolder에서 호출함

스크롤할때마다 뷰홀더에 있는 데이터를 교체할 때 선언되는 함수임 ㅇㅇ

bind라는 함수 안에 텍스트 교체 구문이 있는데,

추가로 click 이벤트도 넣어주도록 하겠다.

 

❗️제일 헷갈리는 부분❗️

Private lateinit var itemClickListener: OnItemClickListener<== 내가 만든 인터페이스??

(⬆️변수선언)

Interface OnItemClickListener{

fun onItemClick(myWord: String)

}

(⬆️인터페이스 선언)

후… 액티비티/프래그먼트가 아닌 변수가 인터페이스를 상속받는다는게 무슨 뜻인지 잘 모르겠어…

OnItemClickListener 타입의 변수를 선언했다는데

인터페이스가 타입???? 77ㅑ 머리터져

그니까 A인터페이스 타입의 변수는 A인터페이스에 선언된 함수를 내장하고 있다는 말..!

 

다시 bind라는 함수 안에 텍스트 교체 구문이 있는데,

추가로 click 이벤트를 넣어줄 예정이다.

binding.wordTv.text = myWord(레이아웃 binding한 것에서 wordTv라는 id를 가진 텍스트뷰에 데이터를 넣어줬었다.)

+

binding.wordTv.setOnClickListener {

itemClickListener.onItemClick(myWord)

}

(레이아웃 binding한 것에서 wordTv라는 id를 가진 텍스트뷰에 Click 함수를 호출해서 그 안에

아까 그 인터페이스 타입의 변수.onItemClick 을 호출하면서 myWord라는 String타입의 list원소를 전달한다(이건 onBindViewHolder에서 wordList[position]을 bind에 전달함에 따라 myWord가 뭔지 결정됨.)

 

그니까 인터페이스에 선언한 함수를 객체를 통해 사용하기 위해 그 인터페이스 타입의 변수를 별도로 선언한거임(대박)

 

근데 사실 lateinit var로 itemClickListener: OnItemClickListener라는 변수를 선언하긴 했지만

아직까지 초기화하지 않은 상태임(미리 할순없는?)

그래서 함수를 별도로 메서드 밖에 선언해줌

itemClickListener의 setter 함수를 작성한다고 생각(사실 이해 안됨. 인터페이스 따로 있고 세터함수 따로 있구나…. 그렇구나… 응)

 

fun setOnItemClickListener(onItemClickListener: OnItemClickListener) {

itemClickListener = onItemClickListener

}

(아 내가 이래서 onItemClickListener랑 OnItemClickLIstener를 너무 헷갈렸구나…

OnItemclickListener라는 인터페이스 타입의 패러미터를 전달받음. 패러미터 이름이 onItemClickListener.)

 

(setOnItemClickListener라는 함수를 호출하면 외부에서 전달받은 OnItemClickListener 인터페이스 타입의 onItemClickListener라는 패러미터가 itemClickListener라는 변수에 저장됨

 

순서:

외부에서 “interface”인 OnItemClickListener의 내용을 구현하면

내부에 private으로 선언한 변수인 itemClickListener라는 변수에 그 내용이 저장됨.

그러면 Adapter 내부에서 그 변수를 사용함

 

<MainActivity>

WordAdapter의 인터페이스인 OnItemClickListener의 onItemClick의 동작을 자세하게 기술하고 연결할 차례

 

리사이클러뷰 초기화하는 함수인 initRecyclerView로 가서

(아까

wordAdapter = WordAdapter(wordList)

// 변수에 리스트 데이터 전달한 어댑터를 할당해주고

Binding.recyclerView.adapter = wordAdapter

// 바인딩한 레이아웃의 리사이클러뷰의 어댑터에 내가만든 어댑터 연결해주고

binding.recyclerView.layoutManager = LinearLayoutManager

// 레이아웃 매니저 설정해주기 까지 했었음.)

wordAdapter.setOnItemClickListener()

// 어댑터에 설정해준 인터페이스

// 그 내용 구현하려면 익명 클래스 필요함

// 익명클래스란? 클래스 정의 없이 객체를 생성하고 사용하는 방법

// 인터페이스 OnItemClickListener(의 세부내용)를 구현하는 게 지금 목적❓인데, 그 이유는 setOnItemClickListener의 매개변수로 넣어주기 위함.(넣어주면 adapter 안에서 itemClickListener에 변수로 인터페이스 내용이 저장돼서 객체로 쓰일 수 있음)

// 원래라면 새로 파일을 만들거나 클래스를 선언해서 그 클래스를 인자로 넣어줄 수 있지만, 그러지 않고 코드를 간결화할 수 있음.

🧐익명클래스 사용방법!

1. object 키워드를 사용하고,

2. 뒤에 콜론과 함께 상속받으려는 슈퍼클래스나 구현하려는 인터페이스를 지정함(여기선 내가 만든 WordAdapter의 WordAdapter.OnItemClic kListener를 지정함)

3. 그리고 인터페이스 바로 뒤에 중괄호 써서 익명클래스의 멤버나 매서드 정의함.

여기서는 Adapter파일에 OnItemClickListener 인터페이스 안에 onItemclick이라는 추상메서드를 정의해놨으므로 여기서 override하면 됨.

 

클릭할때 화면 넘어가게 하고 싶으니까 클릭 이벤트에 intent 선언함

Val intent = Intent(applicationContext, WordActivity::class.java)

startActivity(intent)

 

화면전환뿐만 아니라 클릭한 아이템에 따라 데이터를 보내고 싶으면

Intent.putExtra(“Key(임의)”, myWord)

 

// 인텐트를 받는 wordActiviity에서는

Val data = intent.getStringExtra(“Key”)

//로 불러오고

binding.tvWordContent.text = data

//바인딩한 액티비티의 텍스트뷰의 데이터에 내가 넣으려는 데이터의 키를 넣음

If (data == null) {binding.tvWordContent.text=“asdf”} 이런식으로 키 없는 데이터 들어갈 때의 널처리도 해줘야함

또는

Val data = intent.getStringExtra(“Key”) ?: “asdf” 

Cf. 엘비스 연산자

 

<4주차-실습 3>

MyWord라는 데이터 클래스 새로 만들고 신경써서 바꿔줘야 할 부분들

WordAdapter

1. WordAdapter에서 인자를 ArrayList<String>=>ArrayList<MyWord>로

2. WordAdapter의 뷰홀더의 bind 함수에서

Fun bind(myWord: String) -> fun bind(myWord: MyWord)

3. Bind 안에서

Binding.wordTv.text = myWord -> myWord.text

// 데이터 클래스라서 여러개 해줘야 함  

MainActivity

4. Private val wordList:Arrayist<String> -> <MyWord>

5. initWords 함수에서

arrayListOf(“item1”, “item2”, …)

->

arrayListOf(MyWord(“apple”, “사과’), MyWord(“banana”, “바나나”), …)

6. initRecyclerView 함수에서 

wordAdapter!!.setOnItemClickListener(object: WordAdapter.OnItemClickListener{}에서

어댑터로부터 MyWord 타입의 데이터를 전달받는 함수를 가진 인터페이스를 상속받기 때문에

Override fun onItemClick(myWord: String) -> (myWord: MyWord)

7. 그리고 fun onItemClick {} 안에서도

Toast.makeText(applicationContext, myWord, Toast.LENGTH_SHORT).show()

-> myWord.text

8. + intent로 보내는 값도

intent.putExtra(“key”, myWord)->(“key”, myWord.meaning)

WordActivity

9. 받는 데이터가 바뀌었기 때문에

binding.tvWordContent.text = data 확인

근데 여기서는 String 타입의 텍스트를 받는건 동일해서 안바꿔도 됨

 

이때 만약 별개의 String 값만 주고받는 게 아니라 MyWord만 주고받고자 한다면

MyWord 데이터 클래스에서

Data class MyWord(

) : Serializable

이렇게 Serializable 추가해서 상속받게 해주면

MainActivity에서

intent.putExtra(“Key”, myWord.text) 가 아닌

myWord를 그대로 넣어도 오류가 안생김

(Serializable이 putExtra가 지원하는 여러 타입 (Bundle, Float 등등) 중 하나

그러면 데이터 전달받는 WordActivity에서 

Val data = intent.getStringExtra(“Key”) ?: “asdf”

-> intent.getSerializableExtra(“Key”, MyWord::class.java) ?: “asdf”

근데 deprecated 돼서 if 문으로 버전 체크

Val data = if (Build.VERSION.SEK_INT >= Build.VERSION_CODES.TIRAMISU) {

intent.getSerializableExtra(“key”, MyWord::class.java) ?: “asdf”

} else { // 버전이 tiramisu보다 낮을 때

intent.getSerializableExtra(“key”) as MyWord

} ?: MyWord(“temp”, “temp”) // 데이터 없을 경우의 널처리

 

binding.tvWordContent.text = data.meaning

//이후 데이터쓰는건 동일(널체크 위에서 해줘야 data.meaning 오류 안뜸)

또는 data!!.meaning 해주기