코틀린-안드로이드

48일차)알고리즘 문제(삼각 달팽이, 블록 게임), 숙련 1주차 강의(뷰바인딩, 어댑터뷰)

songyooho 2024. 7. 10. 21:01

>알고리즘 문제 

1. 삼각 달팽이

1)문제

정수 n이 매개변수로 주어집니다. 다음 그림과 같이 밑변의 길이와 높이가 n인 삼각형에서 맨 위 꼭짓점부터 반시계 방향으로 달팽이 채우기를 진행한 후, 첫 행부터 마지막 행까지 모두 순서대로 합친 새로운 배열을 return 하도록 solution 함수를 완성해주세요.


제한사항

  • n은 1 이상 1,000 이하입니다.

2)솔루션

class Solution {
    
    lateinit var arr:Array<IntArray>
    
    fun solution(n: Int): IntArray {
        
        arr=Array(n){IntArray(n)}
        
        var top=0
        var bot=n-1
        val left=IntArray(n)
        var right=IntArray(n){it}
        
        val len=(n*(n+1))/2
        
        var answer: IntArray = IntArray(len)
        
        find(top,bot,left,right,1)
        
        var idx=0
        for(i in 0..n-1){
            for(j in 0..n-1){
                if(arr[i][j]==0) break
                answer[idx++]=arr[i][j]
            }
        }
        
        return answer
    }
    
    fun find(top:Int, bot:Int,left:IntArray,right:IntArray,idxinit:Int){
        if(top>bot) return 
        var idx=idxinit
        for(i in top..bot){
            arr[i][left[i]++]=idx++
        }
        for(i in left[bot]..right[bot]){
            arr[bot][i]=idx++
        }
        for(i in bot-1 downTo top+1){
            arr[i][right[i]--]=idx++
        }
        find(top+2,bot-1,left,right,idx)
    }
}

-직각삼각형 형태로 숫자를 문제에 나온 방식으로 채워넣음

-우변을 따라 숫자를 채운뒤에 우변은 한칸씩 당김

-아래변을 따라 숫자를 채운뒤에 아래 변을 한칸 위로 당긴다

-좌변을 따라 숫자를 채운뒤에 좌변을 한칸 당김

-마지막으로 높이를 두칸 내린뒤에 새로 생긴 직각삼각형을 따라서 다시 채움

-위 과정을 반복하면 정답을 얻을 수 있음

 

 

2. 블록 게임

1)문제 문제 설명

블록게임

프렌즈 블록이라는 신규 게임이 출시되었고, 어마어마한 상금이 걸린 이벤트 대회가 개최 되었다.

이 대회는 사람을 대신해서 플레이할 프로그램으로 참가해도 된다는 규정이 있어서, 게임 실력이 형편없는 프로도는 프로그램을 만들어서 참가하기로 결심하고 개발을 시작하였다.

프로도가 우승할 수 있도록 도와서 빠르고 정확한 프로그램을 작성해 보자.

게임규칙

아래 그림과 같이 1×1 크기의 블록을 이어 붙여 만든 3 종류의 블록을 회전해서 총 12가지 모양의 블록을 만들 수 있다.

1 x 1 크기의 정사각형으로 이루어진 N x N 크기의 보드 위에 이 블록들이 배치된 채로 게임이 시작된다. (보드 위에 놓인 블록은 회전할 수 없다). 모든 블록은 블록을 구성하는 사각형들이 정확히 보드 위의 사각형에 맞도록 놓여있으며, 선 위에 걸치거나 보드를 벗어나게 놓여있는 경우는 없다.

플레이어는 위쪽에서 1 x 1 크기의 검은 블록을 떨어뜨려 쌓을 수 있다. 검은 블록은 항상 맵의 한 칸에 꽉 차게 떨어뜨려야 하며, 줄에 걸치면 안된다.
이때, 검은 블록과 기존에 놓인 블록을 합해 속이 꽉 채워진 직사각형을 만들 수 있다면 그 블록을 없앨 수 있다.

예를 들어 검은 블록을 떨어뜨려 아래와 같이 만들 경우 주황색 블록을 없앨 수 있다.

빨간 블록을 가로막던 주황색 블록이 없어졌으므로 다음과 같이 빨간 블록도 없앨 수 있다.

그러나 다른 블록들은 검은 블록을 떨어뜨려 직사각형으로 만들 수 없기 때문에 없앨 수 없다.

따라서 위 예시에서 없앨 수 있는 블록은 최대 2개이다.

보드 위에 놓인 블록의 상태가 담긴 2차원 배열 board가 주어질 때, 검은 블록을 떨어뜨려 없앨 수 있는 블록 개수의 최댓값을 구하라.

제한사항
  • board는 블록의 상태가 들어있는 N x N 크기 2차원 배열이다.
    • N은 4 이상 50 이하다.
  • board의 각 행의 원소는 0 이상 200 이하의 자연수이다.
    • 0 은 빈 칸을 나타낸다.
    • board에 놓여있는 각 블록은 숫자를 이용해 표현한다.
    • 잘못된 블록 모양이 주어지는 경우는 없다.
    • 모양에 관계 없이 서로 다른 블록은 서로 다른 숫자로 표현된다.
    • 예를 들어 문제에 주어진 예시의 경우 다음과 같이 주어진다.

2)솔루션

class Solution {
    fun solution(board: Array<IntArray>): Int {
        var answer = 0

        val head=Array(201){HashSet<Pair<Int,Int>>()}

        for(i in board.indices){
            for(j in board[0].indices){
                if(board[i][j]!=0) head[board[i][j]]+=Pair(i,j)
            }
        }


        val heads=HashSet<Int>()
        val upper=IntArray(board[0].size)
        for(i in board[0].indices){
            for(j in board.indices){
                if(board[j][i]!=0){
                    heads+=board[j][i]
                    upper[i]=j
                    break
                }
            }
        }
        while(true){
            val removed=HashSet<Int>()
            val added=HashSet<Int>()
            for(i in heads){
                val p=IntArray(8)
                var idx=0
                for(j in head[i]){
                    p[idx++]=j.first
                    p[idx++]=j.second
                }
                val blanks=blank(p)
                blanks?.let{
                    var flag=true
                    //반환받은 빈공간이나 그 위에 블럭이 있는지 체크

                    for(j in it){
                        if(j.first>=upper[j.second]) flag=false
                    }
                    if(flag){
                        answer++
                        //블럭 제거 후 해당열 upper와 heads갱신
                        for((x,y) in head[i]){
                            board[x][y]=0
                        }
                        for((x,y) in head[i]){
                            for(k in board.indices){
                                if(board[k][y]!=0){
                                    upper[y]=k
                                    added+=board[k][y]
                                    break
                                }
                            }
                        }
                        removed+=i
                    }
                }
            }
            if(removed.size==0) break
            
            //다른 head를 처리하면서 기존에 있으면서 사라져야 할 head가 added에 추가될 수 있음
            //그때문에 removeAll을 후처리
            heads.addAll(added)
            heads.removeAll(removed)
        }


        return answer
    }
    //가능한 타입:13,14,22,23,31
    //가능한 타입이면 빈공간을 반환, 아니면 널 반환
    fun blank(p:IntArray):Array<Pair<Int,Int>>?{
        //높이가 2가지 뿐이면 누운 타입
        val w=intArrayOf(p[1],p[3],p[5],p[7])
        val h=intArrayOf(p[0],p[2],p[4],p[6])

        //누운 타입
        if(h.distinct().size==2){
            //아래를 보는 누운타입이면 널 반환
            val up=h.minOrNull() as Int
            val down=h.maxOrNull() as Int
            var cnt=0
            h.forEach{if(it==down) ++cnt}
            if(cnt==1){
                return null
            }
            //타입에 따라 빈공간 반환
            val left=w.minOrNull() as Int
            val right=w.maxOrNull() as Int
            val center=w.filter{it!=left&&it!=right}[0]
            val upidx=h.indexOf(up)
            if(w[upidx]==left){
                return arrayOf(Pair(up,right),Pair(up,center))
            }else if(w[upidx]==right){
                return arrayOf(Pair(up,left),Pair(up,center))
            }else{
                return arrayOf(Pair(up,left),Pair(up,right))
            }
            //선 타입
        }else{
            //튀어나온 부분이 중앙이나 위에 있으면 널 반환
            val up=h.minOrNull() as Int
            val down=h.maxOrNull() as Int
            val center=h.filter{it!=up&&it!=down}[0]
            var cntCenter=0
            var cntUp=0
            h.forEach{if(it==up) ++cntUp else if(it==center) ++cntCenter}
            if(cntCenter==2||cntUp==2){
                return null
            }

            //타입에 따라 빈공간 반환
            val left=w.minOrNull() as Int
            val right=w.maxOrNull() as Int

            //좌측으로 튀어나온 경우
            if(w.filter{it==left}.size==1){
                return arrayOf(Pair(up,left),Pair(center,left))
                //우측으로 튀어나온 경우
            }else{
                return arrayOf(Pair(up,right),Pair(center,right))
            }
        }
    }
}

-단위블록: 정사각형 모양의 칸 / 블록: 같은 인덱스를 가진 단위블록끼리 모여 모양을 이루는 도형이라 하자

-우선 모든 블록을 head로 묶는다.

-처음에 모든 열을 체크해서 각 열 별로 가장 윗 단위블록과 head를 upper와 heads에 각각 저장해둔다.

-ㅡㅡㅡㅡㅡ이 아래 반복 ㅡㅡㅡㅡㅡ

-저장된 head들에 대해서 각각 해당 블록이 사라지기 위한 빈공간을 blank함수로 찾는다.

-단 blank함수 사용시 사라질 수 없는 블록에 대해서는 null를 반환받는다.

-upper를 체크하여 반환받은 빈 공간이나 그 위에 블록이 존재하는지 확인

-없으면 블록을 제거하고 answer++. 이후 블록이 사라진 열을 체크하여 새로 heads와 upper를 갱신한다.

- ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

-위 과정 반복을 하여 answer을 구한다.

 

 

>숙련 1주차 강의

1.  뷰 바인딩

<1>사용: findViewById를 대체

<2>장점

-널 안정성: 뷰가 아직 화면에 안 나타났는데 사용하려 할시 생기는 문제 예방

=>예시: 버튼이 생성되지 않았는데 버튼 사용하려는 경우

-타입 안정성: 잘못된 타입으로 사용되는것을 막음

=>예시: 이미지뷰에 텍스트를 설정하려 하는경우

<3>뷰 바인딩 설정 방법

-gradle설정

android{
	...
    
    // AndroidStudio 3.6 ~ 4.0
    viewBinding{
    	enabled = true
    }
    
    // AndroidStudio 4.0 ~
    buildFeatures{
    	viewBinding = true
    }
}

 

-Activity설정

class MainAcitivity: AppCompatActivity(){

	
    private lateinit var binding: ActivityMainBinding //lateinit으로 변수 설정
    
    override fun onCreate(saveInstanceState: Bundle?){
        super.onCreate(saveInstanceState)

        //이 아래부분 추가 및 수정
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
    
    }
}

-binding.root: root는 최상위 뷰를 말함. 즉 최상위뷰를 UI로 지정

-layoutInflater: 현재 액티비티 인스턴스에서 가져온 LayoutInflater 객체를 말함. Activity의 멤버.

<4>사용방법

binding.아이디.메소드

 

<5>바인딩 클래스 이름

-레이아웃 파일의 이름에 따라 달라짐

-예시:activity_main.xml 인 경우 ActivityMainBinding으로 이름이 지어짐

 

 

2. 어댑터뷰(리스트뷰, 그리드 뷰,리사이클러)

<1>어댑터 뷰

(1)어댑터 뷰란

-여러개 항목을 다양한 형식으로 나열 및 선택할 수 있도록 기능을 제공하는 

-어댑터뷰는 표시할 항목 데이터를 어댑터라는 객체로 부터 공급받음

 

(2)어댑터

-데이터 관리: 데이터 원본과 어댑터 뷰 사이의 중계역할

-데이터를 항목에 표시하는 방법

[1]데이터 원본에 어댑터 설정 & 어댑터 뷰에 어댑터 설정

[2]getCount()로 데이터 항목 총 개수 반환

[3]getView()로 화면에 표시할 항목뷰를 얻고 표시

-사용자와 상호작용: 사용자가 항목 선택시 어댑터 뷰는 어댑터로부터 getItem(), getItemId(), getView()로 항목 정보를 받아와 항목선택 이벤트 처리기로 넘김

 

(3)어댑터 종류

-BaseAdapter: 어댑터 클래스의 공통구현, 사용자 정의 어댑터 구현시 사용

-ArrayAdapter: 객체나 리소스에 정의된 배열로부터 데이터 공급받음

-CursorAdapter: 데이터베이스로부터 데이터 공급받음

-SimpleAdapter: 데이터를 Map으로 관리, 데이터를 XML파일에 정의된 뷰에 킴

 

<2>리스트 뷰

(1)형태: 리스트 형식으로 항목이 나열되는 어댑터 뷰

(2)만들기

[1]xml에 리스트뷰 생성

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

[2]어댑터 생성

-형식: ArrayAdapter(Context context, int resource,  T[] objects)

=>Context는 보통 this를 쓰면 되고, resource 항목으로 표시될 뷰의 아이디, object는 데이터 원본(보통 배열)

resource로 사용되는 기본 제공 뷰

[3]어댑터 뷰와 어댑터 연결

bindging.listView.adapter = adapter

 

 

<3>그리드 뷰

(1)형태: 격자 형식으로 나타내지는 뷰

(2)만들기:

[1]xml에 그리드뷰 생성

<GridView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/gridview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:columnWidth="100dp"
    android:numColumns="auto_fit"
    android:verticalSpacing="10dp"
    android:horizontalSpacing="10dp"
    android:stretchMode="columnWidth"
    android:gravity="center"/>

-columnWidth: 그리드 항목당 폭 길이 설정

-numColumns="auto_fit": 열과 화면의 폭을 바탕으로 열의 개수를 자동 계산

=>""안에 숫자를 쓰면 숫자만큼 열의 개수가 정해짐

-verticalSpacing: 항목 간의 수직 간격 설정

-horizontalSpacing: 항목 간의 수평 간격 설

-stretchMode="columnWidth": 열이 뷰의 너비에 맞게 확장되는 방식으로

=>이경우 열의  크기가 고정되어 뷰 내부에 일정간격으로 나열시킴. 또한 이 속성을 사용하려면 columnWidth가 필요함

@stretchMode의 다른 값들

-spacingWidthUniform: 열간격을 균일하게 유지하며 열이 확장됨=>화면 너비에 상관없이 열 간격 고

-spacingWidth:열 사이 간격을 유지하며 열이 확장=>화면 너비에 따라 열 간격이 변함

 

[2]이후 과정은 listview와 완전히 동일

 

(3)이미지 그리드뷰 만들기

[1]그리드 뷰 생성

[2]커스텀 어댑터 클래스 생성

class ImageAdapter : BaseAdapter() {
    override fun getCount(): Int {
        return mThumbIds.size
    }

    override fun getItem(position: Int): Any {
        return mThumbIds[position]
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        val imageView: ImageView
        if (convertView == null) {
            imageView = ImageView(parent!!.context)
            imageView.layoutParams = AbsListView.LayoutParams(200, 200)
            imageView.scaleType = ImageView.ScaleType.CENTER_CROP
            imageView.setPadding(8, 8, 8, 8)
        } else {
            imageView = convertView as ImageView
        }

        imageView.setImageResource(mThumbIds.get(position))
        return imageView
    }

    private val mThumbIds = arrayOf<Int>(
        R.drawable.sample_2, R.drawable.sample_3,
        R.drawable.sample_4, R.drawable.sample_5,
        R.drawable.sample_6, R.drawable.sample_7,
        R.drawable.sample_0, R.drawable.sample_1,
        R.drawable.sample_2, R.drawable.sample_3,
        R.drawable.sample_4, R.drawable.sample_5,
        R.drawable.sample_6, R.drawable.sample_7,
        R.drawable.sample_0, R.drawable.sample_1,
        R.drawable.sample_2, R.drawable.sample_3,
        R.drawable.sample_4, R.drawable.sample_5,
        R.drawable.sample_6, R.drawable.sample_7
    )
}

-getCount(): 항목의 총 개수

-getItem(): 항목 데이터

-getItemId(): 항목 데이터의 인덱스

-getView( position: Int, convertView: View?, parent: ViewGroup? ):

{1}position: 반환할 항목의 위치 인덱스

{2}convertView: 이전에 생성된 항목뷰. 처음만들어 지는 경우(convertView==null)라면 새로운 이미지뷰 객체를 만들고 크기, 스케일 타입, 패딩을 설정. 이전에 만들어졌다면 이를 재사용.

{3}parent:ViewGroup? :여기선 그리드 뷰를 나타냄

@ViewGroup을 상속받는 것으로는 리니어레이아웃, 컨스트레인트 레이아웃, 리사이클러뷰등이 있다

 

@ AbsListView.LayoutParams

- AbsListView는 리스트뷰와 그리드뷰의 부모 클래스

- AbsListView.LayoutParams는 해당 뷰들 내부 항목의 레이아웃 속성을 설정: 너비와 높이

@ ImageView(parent!!.context)

-이미지뷰가 리소스를 사용하기 위해서는 context가 필요. parent(뷰그룹)가 속한 context를 받아옴

@View.context

-뷰가 속한 context를 받아옴. 

@context

-실행중인 액티비티 혹은 서비스

-뷰가 포함된 레이아웃이나 컨테이너의 context를 의미

 

(4)어댑터뷰 클릭 이벤트

binding.gridview.setOnItemClickListener{ parent, view, position, id ->
    Toast.makeText(this@MainActivity,"" + (position + 1) + "번째 선택",           
        Toast.LENGTH_SHORT).show()
}

-parent: 클릭이 발생한 AdapterView

-view: 실제 클릭된 어댑터뷰 내의 view

-position: 어댑터내에서 클릭된 항목의 위치

-id: 클릭된 항목의 아이디

 

@ Annotation this@MainActivity

-this를 쓰면 자신이 포함된 객체를 나타낸다.

-단 객체가 중첩되어있는 경우 annotation을 이용하여 어느 객체를 가리키는지 명시할 수 있다.

-예시

class OuterClass {
    inner class InnerClass {
        fun doSomething() {
            println(this@InnerClass) // InnerClass 인스턴스
            println(this@OuterClass) // OuterClass 인스턴스
        }
    }
}

 

<4>리사이클러뷰

(1)리사이클러뷰란: 리스트형태 데이터를 표시하는 위젯

=>뷰를 재활용해서 사용하는 위젯

 

(2)리스트뷰와 리사이클러뷰의 차이

-리스트뷰는 스크롤마다 보이는 화면을 벗어난 아이템은 삭제하고 새로 보이는 아이템을 생성 반복=>성능이 안 좋음

=>100개 아이템에 대해 스크롤을 하면 100개를 전부 생성해야함

-리사이클러뷰는 스크롤시 화면에서 벗어난 아이템을 다시 스크롤방향에 가지고와 재활용=> 성능 비교적 우수

=>100개 아이템에 대해 스크롤하면 10개 정도 뷰를 만들어 재활용

 

(3)리사이클러뷰 사용

[1]Adapter: 데이터 테이블을 목록 형태로 보여주기위한 것으로 데이터와 리사이클러뷰 사이에 존재하는 객체

[2]ViewHolder: 화면에 표시될 데이터나 아이템을 저장하는 역할

=>리사이클러뷰는 스크롤해서 사라진 View를 재활용하기 위해 기억해야 하는데 ViewHolder가 그 역할을 

 

(4)사용법(예제)

[1]메인 화면 레이아웃에 리사이클러뷰 위젯 생성

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

 

 

[2]리사이클러뷰 아이템을 담을 레이아웃 생성:이름은 item_recyclerview.xml

 

[3]어댑터 클래스 정의

-전체코드

class MyAdapter(val mItems: MutableList<MyItem>) : RecyclerView.Adapter<MyAdapter.Holder>() {

    interface ItemClick {
        fun onClick(view : View, position : Int)
    }

    var itemClick : ItemClick? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        val binding = ItemRecyclerviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return Holder(binding)
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        holder.itemView.setOnClickListener {  //클릭이벤트추가부분
            itemClick?.onClick(it, position)
        }
        holder.iconImageView.setImageResource(mItems[position].aIcon)
        holder.name.text = mItems[position].aName
        holder.age.text = mItems[position].aAge
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

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

    inner class Holder(val binding: ItemRecyclerviewBinding) : RecyclerView.ViewHolder(binding.root) {
        val iconImageView = binding.iconItem
        val name = binding.textItem1
        val age = binding.textItem2
    }
}

 

-클래스 상속

class MyAdapter(val mItems: MutableList<MyItem>) : RecyclerView.Adapter<MyAdapter.Holder>()

{1}리사이클러뷰의 어댑터를 상속받아 구현

{2}뷰홀더로 사용된것이 inner class인 Holder이므로 타입에 MyAdapter.Holder가 들어감

 

@inner class와 nested class

{1}nested class:

-클래스내에 클래스를 정의하면 기본적으로 중첩 클래스로 정의됨

-외부 클래스를 참조하지 않기 때문에 외부 클래스의 멤버를 사용할 수 없다.

-선언 및 사용: 외부클래스명.내부클래스명()

-예시: 이 경우에 오류가 남

class Outer{
  val a=1
  class Nested{
    fun foo(){
      println(a) //a가 Unresolved reference로 오류가 뜬다.
    }
  }
}

 

fun main(){
	println(Outer.Nested().foo()) //에러!
}

{2}inner class

-앞에 inner을 추가해서 사용

-외부 클래스를 참조하기 때문에 외부 클래스의 멤버에 접근할 수 있다.

-선언 및 사용: 외부클래스명().내부클래스명()

=>외부클래스를 참조하고 있기때문에 먼저 외부클래스를 생성해 주어야 한다.

-예시

class Outer{
  val a=1
  inner class Inner{
    fun foo(){
      println(a) //오류 없음
    }
  }
}
fun main(){
	println(Outer().Inner().foo()) // 1 출력
}

 

-뷰홀더

inner class Holder(val binding: ItemRecyclerviewBinding) : RecyclerView.ViewHolder(binding.root) {
    val iconImageView = binding.iconItem
    val name = binding.textItem1
    val age = binding.textItem2
}

{1}뷰에 보여질 내용을 저장하는 역할

{2} 뷰 역할을 할 레이아웃이름이 item_recyclerview.xml이므로 바인딩 클래스 이름은 ItemRecyclerviewBinding

 

-뷰홀더 생성

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
    val binding = ItemRecyclerviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
    return Holder(binding)
}

{1}parent는 리사이클러뷰

{2}리사이클러뷰에 즉시 추가하지는 않고 최종적으로 뷰홀더를 생성시킴

 

@LayoutInflater

{1}inflate() 메소드를 통해 xml레이아웃 파일을 실제 뷰 객체로 변환함

val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.my_layout, parentView, false)

-context는 fragment나 activity

-위의 코드에서 inflater가 R.layout.my_layout이란 xml파일을 실제 뷰 객체로 변환시켜(인플레이션) 인플레이션된 뷰를 parentView에 추가.

-이때 false는 부모에 즉시 추가하지는 않음을 나타낸다.

=>true면 즉시 추가된다.

{2}바인딩 클래스에서의 inflate

Binding.inflate(레이아웃인플레이터,부모뷰,bool)

-레이아웃 인플레이터(LayoutInflater.from(context)):

=>context는 인플레이션에 필요한 리소스 액세스(ex-inflate시 부모뷰를 참조하기 위함) 시스템 서비스 호출에 필요

=>즉 부모뷰가 포함된 context가 필요함. 그 때문에 위에서 뷰홀더 생성시 LayoutInflater가 parent의 context를 받은것

 

-부모뷰:바인딩클래스가 나타내는 뷰가 어느뷰에 추가될지 결정되는 것이 부모뷰

-bool:true면 즉시 추가되고 false면 즉시 추가하지는 않음을 나타냄.

 

@부모뷰와 bool부분이 생략되는 경우

-bool은 생략되면 자동으로 false처리 되지만 부모뷰 부분은 null처리됨. 

-바인딩 클래스에서 inflate()는 inflate(layoutInflater)는 inflate( layoutInflater, null, false)로 처리되기 때문.

-이렇게 null처리된 것은 다른 뷰에 추가하거나 setContentView()로 직접 화면 설정 할 수 있다. 

 

 

-뷰홀더와 데이터를 바인드

override fun onBindViewHolder(holder: Holder, position: Int) {
    holder.itemView.setOnClickListener {  //클릭이벤트추가부분
        itemClick?.onClick(it, position)
    }
    holder.iconImageView.setImageResource(mItems[position].aIcon)
    holder.name.text = mItems[position].aName
    holder.age.text = mItems[position].aAge
}

=>생성시킨 Holder를 받아와 각각의 데이터를 연결시킴

 

-아이템의 아이디와 총 개수

override fun getItemId(position: Int): Long {
    return position.toLong()
}

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

 

@ init { setHasStableIds(true) }

-adapter안에 넣어서 구현

-동일 id의 아이템에 대해서는 onBindViewHolder가 호출되지않고 기존에 생성된 뷰홀더를 사용한다

-위에서 id를 필요로 하기 때문에 getItemId를 오버라이드 해줘야함.(단 각 아이템이 고유한 아이디를 가질 수 있도록)

 

-클릭 이벤트 구현

interface ItemClick {
    fun onClick(view : View, position : Int)
}

var itemClick : ItemClick? = null

=>클릭에 대한 인터페이스 생성

 

adapter.itemClick = object : MyAdapter.ItemClick {
    override fun onClick(view: View, position: Int) {
        val name: String = dataList[position].aName
        Toast.makeText(this@MainActivity," $name 선택!", Toast.LENGTH_SHORT).show()
    }
}

{1}Main에서 클릭에 대한 인터페이스를 상속받는 오브젝트를 생성

{2}클릭 이벤트에 대한 내용을 오버라이드해서 어댑터 내의 itemClick 프로퍼티에 할당

 

override fun onBindViewHolder(holder: Holder, position: Int) {
    holder.itemView.setOnClickListener {  //클릭이벤트추가부분
        itemClick?.onClick(it, position)
    }
    holder.iconImageView.setImageResource(mItems[position].aIcon)
    holder.name.text = mItems[position].aName
    holder.age.text = mItems[position].aAge
}

=>onBindViewHolder에서 클릭이벤트 감지시 itemClick에 저장된 클릭 이벤트를 실행시킴

 

-MainActivity

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