2024.01.03(수) TIL

quinones·2024년 1월 3일

View Binding

findViewById대체자이다. 번거롭게 위젯들을 하나하나씩 초기화 시켜주지 않아도 한번에 초기화시켜주는걸 도와준다.

findViewById와의 차이점? Null안전성, 타입 안전성이 있다는데 결론만 말하자면 더 안전하고, 더 사용하기 편리한 방법이 뷰 바인딩이다. 안쓸이유가 없다!

사용법

gradle설정

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

나는 안드로이드 버전이 4.0이상인데 위에 방식을 사용해서 오류가났다.. 조심!

Activity설정

딱 4줄만 있으면 된다.
1.먼저 전역변수로 선언

private lateinit var binding: ActivityMainBinding
  1. onCreate()안에서 설정
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)

이렇게 해주면 모든 위젯들에 대해서 binding.(아이디이름)으로 대체해줄수 있다.

예를 들어 텍스트뷰하나와 버튼하나가 있고, 각각 아이디는 button, textView이다. 버튼 클릭시 택스트뷰의 내용을 "뷰바인딩 성공"이라고 바꾸기 위해서는 위 과정들을 거친후에

binding.button.setOnClickListener {
     binding.textView.text = "바인딩 성공!"
}

간단하다.. 인생의 절반 손해봤다 애용할 예정이다..

+추가

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

원래 setContentView(R.layout.activity_main.xml)이런식으로 되어있는데 그대신 이렇게 넣어주면 더 간결하게 사용가능하다!

Adapter

여기부터 앞으로는 위에서 배운 viewBinding을 활용할 예정이다.

ListView

  1. xml에 ListViewt생성
<ListView
        android:id="@+id/listView"
        android:layout_width="409dp"
        android:layout_height="710dp"/>
  1. activity에서 활용
// 데이터 원본 준비
val items = arrayOf<String?>("item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item5", "item6", "item7", "item8", "item5", "item6", "item7", "item8", "item5", "item6",  "item7", "item8")

//어댑터 준비 (배열 객체 이용, simple_list_item_1 리소스 사용
val adapter = ArrayAdapter(this, R.layout.simple_list_item_1, items)

// 어댑터를 ListView 객체에 연결
binding.listView.adapter = adapter

이렇게하면

이렇게 리스트뷰를 생성할 수 있다.

GridView

  1. xml에 gridView를 생성
<GridView
        android:gravity="center"
        android:id="@+id/gridView"
        android:columnWidth="100dp"
        android:numColumns="auto_fit"
        android:verticalSpacing="10dp"
        android:stretchMode="columnWidth"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
  • android:columnWidth=“100dp” : 그리드 항목 하나의 폭을 100dp로 설정
  • android:numColumns=“auto_fit”: 열의 폭과 화면 폭을 바탕으로 자동 계산
  • android:verticalSpacing="10dp": 항목 간의 간격을 10dp로 설정
  • android:stretchMode=“columnWidth”: 열 내부의 여백을 폭에 맞게 채움

여기서 단순 값을 넣기, 사진넣기 두가지 종류로 나뉜다.

  1. 단순값일 경우
// 데이터 원본 준비
val items = arrayOf<String?>("item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item5", "item6", "item7", "item8", "item5", "item6", "item7", "item8", "item5", "item6",  "item7", "item8")

//어댑터 준비 (배열 객체 이용, simple_list_item_1 리소스 사용
val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, items)

// 어댑터를 GridView 객체에 연결
binding.gridView.adapter = adapter
//여기서 gridView는 xml에서 만든 gridView의 id값을 넣어준다.

이렇게하면 gridView형식으로 items배열에 담겨있는 값들이 나오게 된다.

  1. 사진일경우
binding.gridView.adapter = ImageAdapter()

사진을 넣을땐 추가로 ImageAdapter클래스를 만들어준다.

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
    )
}

이미지는 알아서 추가, BaseAdapter의 getCount(), getItem(), getItemId(), getView()메서드를 재정의함.

  • getCount()는 항목의 총 개수를 반환하기 위해서 mThumbIds배열의 크기를 반환
  • getItem()는 특정 위치의 항목을 반환하기 위해 mThumbIds 배열의 지정된 위치의 항목을 반환
  • getItemId()는 특정 위치의 항목 아이디를 반환하는 것인데, 여기서는 배열의 위치(순서)를 항목의 아이디로 간주함
  • getView()는 getView메소드는 첫번째 파라미터로 주어진 위치의 항목 뷰를 반환하는 것이므로, mThumbIds 배열의 position 위치에 있는 이미지 리소스를 ImageView의 이미지로 설정하고, 이 설정된 ImageView 객체를 그리드 뷰의 항목뷰로 반환한다.

이렇게하면 gridView형식으로 이미지뷰에 담겨있는 값들이 나오게 된다.

RecyclerView

아주 중요!!★★★★ 하다고 한다. 현업에서 많이 사용하는 뷰이다. 제대로 알아두자.

안드로이드 앱에서 리스트 형태의 데이터를 표시하는데 사용되는 위젯이다. 여러 아이템을 스크롤 가능한 리스트로 표현하며, 많은 아이템을 효율적으로 관리하고 보여준다
리스트뷰랑 차이점-
리스트뷰는 사용자가 스크롤 할때마다 위에있던 아이템은 삭제되고 아래의 아이템은 생성되는걸 반복한다. 하지만 리사이클러뷰는 스크롤 할때 위에있던 아이템은 재활용돼서 아래로 이동하여 재사용돼서 효율성이 높아진다.

xml

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

아이디와 함께 선언해준다.

mainActivity

//데이터 원본 준비
        val dataList = mutableListOf<MyItem>()
        dataList.add(MyItem(R.drawable.sample_0, "Bella", "1"))
        dataList.add(MyItem(R.drawable.sample_1, "Charlie", "2"))
        dataList.add(MyItem(R.drawable.sample_2, "Daisy", "1.5"))
        dataList.add(MyItem(R.drawable.sample_3, "Duke", "1"))
        dataList.add(MyItem(R.drawable.sample_4, "Max", "2"))
        dataList.add(MyItem(R.drawable.sample_5, "Happy", "4"))
        dataList.add(MyItem(R.drawable.sample_6, "Luna", "3"))
        dataList.add(MyItem(R.drawable.sample_7, "Bob", "2"))
        
        val adapter = MyAdapter(dataList)
        binding.recyclerView.adapter = adapter
        binding.recyclerView.layoutManager = LinearLayoutManager(this)
        
        //여기부분은 뒤에서 다시나옴. 클릭시 이벤트
        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()
            }
        }

여기서는 데이터를 직접 넣어줬다. MyItem이라는 Data Class를 만들어서 형식을 정해줬다.

Data Class

data class MyItem(
    val aIcon: Int,
    val aName: String,
    val aAge: String
)

데이터클래스를 만들어준다. 데이터를 넘길때 사진, 이름, 나이를 같이 넘기려고 해서 데이터타입을 지정해준거다.

item_recyclerview.xml

어떤 형식으로 사진,이름,나이를 사용할지 xml파일을 하나더 만들어준다.

<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:id="@+id/iconItem"
        android:padding="8dp"
        android:layout_gravity="center_vertical"
        android:layout_weight="1"
        android:scaleType="centerInside"
        android:src="@drawable/sample_1"/>
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/textItem1"
            android:textSize="20sp"
            android:padding="4dp"
            android:hint="Name"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/textItem2"
            android:textSize="16sp"
            android:padding="4dp"
            android:hint="Age"/>
    </LinearLayout>
</LinearLayout>

이런 모양으로 저장했다.

MyAdapter

MyAdapter를 만들어준다.

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)
            /*
            다른 액티비티로 이동하고싶다? 인텐트 사용해서 이동.
            하지만 여기선 해당값의 이름을 그대로 mainActivity로 가져와서 사용하기때문에
            ItemClick interface를 만들어서 사용중이다.
            */
            //만약 클릭시 DetailActivity로 이동을 원한다? 아래내용.
            //val intent = Intent(holder.itemView.context, DetailActivity::class.java)
            //holder.itemView.context.startActivity(intent)
        }
        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
    }
}

해당 뷰가 클릭시 다른페이지로 이동 혹은 다른 기능을 holder.itemView.setOnClickListener안에 넣어주면된다. 여기서는 화면전환을 하지 않고, 단순히 해당 값의 이름을 불러와 Toast메세지를 띄우기 위해서 ItemClick 인터페이스를 만들어주고 값을 다시 MainActivity로 넘겨주었다.

해당 부분은 MainActivity부분에 아래에 씌여있는 부분이다.

결과화면

강아지 사진을 넣어야 하는데 편의상 아무 아이콘을 다운받았다 ㅎㅎ..

위 값들을 활용해서 다양한 시도를 해보면서 recyclerview를 익혀나가보면 될거같다.

recyclerView 활용

위 부분에서 객체 클릭시 Toast메세지 띄우기 대신 DetailActivity로 이동하고, 해당 화면에서 선택된 사진,이름,나이를 가져와서 띄워보려고 한다.

activity_detal.xml

<ImageView
     android:layout_marginTop="100dp"
     android:id="@+id/detail_img"
     android:layout_gravity="center"
     android:layout_width="200dp"
     android:layout_height="200dp"/>
<TextView
     android:id="@+id/text_name"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:hint="이름"
     android:textSize="30sp"
     android:textStyle="bold"
     android:layout_gravity="center"/>
<TextView
     android:id="@+id/text_age"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:hint="나이"
     android:textSize="30sp"
     android:textStyle="bold"
     android:layout_gravity="center"/>

화면 가운데에 사진을 띄울 이미지뷰하나, 이름과 나이를 띄울 텍스트뷰 두개를 만들어줬다.

data class 수정

@Parcelize
data class MyItem(
    val aIcon: Int,
    val aName: String,
    val aAge: String
): Parcelable

값을 다음 액티비티로 보내주기 위해서 Parcelabel을 사용해줬다.

myAdapter수정

override fun onBindViewHolder(holder: Holder, position: Int) {
        holder.itemView.setOnClickListener {  //클릭이벤트추가부분
            //메인액티비티로 값의 이름 가져가기
            //itemClick?.onClick(it, position)
            val intent = Intent(holder.itemView.context, DetailActivity::class.java).apply {
                putExtra("data", mItems[position])
            }.run{holder.itemView.context.startActivity(this)}
        }
        holder.iconImageView.setImageResource(mItems[position].aIcon)
        holder.name.text = mItems[position].aName
        holder.age.text = mItems[position].aAge
    }

intent를 이용해서 DetailActivity로 이동하고 리스트에 있는 값을 putExtra를 이용해서 같이 넘겨준다.
intent를 생성할때는 'context'가 필요하며, 어댑터 클래스 자체는 이를 가지고있지 않기때문에 RecyclerView의 context를 사용하기 위해서 this대신 holder.itemView.context를 사용해줬다.

DetailActivity에서 값 받아주기

val selected = intent.getParcelableExtra<MyItem>("data")
        val myImage = selected?.aIcon
        val myName = selected?.aName
        val myAge = selected?.aAge
        if (myImage != null) {
            binding.detailImg.setImageResource(myImage)
        }
        binding.textName.text = myName
        binding.textAge.text = myAge

getParcelableExtra를 사용해서 키값을 받아와 이미지뷰와 텍스트뷰에 값을 넣어준다.


정말 자주 사용될 내용! 이다. 다음주까지 과제에도 필요한 내용이니 잘 봐두면 좋을거같다.

profile
이우진

0개의 댓글