뷰바인딩은 findViewById 대신에 쓰는 코드이고 대부분 findViewById대신 뷰바인딩을 쓴다고 한다.
위와 같은 이유로 뷰바인딩을 주로 쓴다.
프로젝트 첫 시작시 몇가지 설정만 해 놓으면 복잡한 코드 없이 간단히 id를 찾고 일을 수행하는게 편해진다.
먼저 gradle에서
//안드로이드 스튜디오 4.0이상에서는
android{
buildFeatures {
viewBinding true
}
}
//그 이하에서는
viewBinding{
enabled = true
}
를 입력해 설정 해 주고
Activity에서
package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.myapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
}
이렇게 설정 해 두면
binding.button.text = "안뇽"
binding.button.setBackgroundColor(Color.BLACK)
이렇게 findViewById로 일일이 찾을필요없이 점 하나로 아이디에 접근이된다.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.myButton.setOnClickListener{
binding.myTextView.text = "바인딩이 잘 되네요!"
}
}
}
이런식으로 setOnClickListener와 같은 다양한 일을 수행하는 것도 가능하다.
어댑터뷰는
코드와 데이터 원본 중간역할이다.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 데이터 원본 준비
val items = arrayOf<String?>("아이템1", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item5", "item6", "item7", "item8", "item5", "item6", "item7", "item8", "item5", "item6", "item7", "아이템8")
//어댑터 준비 (배열 객체 이용, simple_list_item_1 리소스 사용
val adapter = ArrayAdapter(this, R.layout.simple_list_item_1, items)
//어댑터 연결
binding.listView.adapter = adapter
}
}
이렇게 간단히 테스트로 값을 넣어보면
데이터를 잘 적용이 된 것을 볼 수 있다.
Gridview는 사진이나 두줄 이상의 스크롤 가능한 그리드에 항목을 표시한다.
--layout
<?xml version="1.0" encoding="utf-8"?>
<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"
//
android:columnWidth=“100dp”** : 그리드 항목 하나의 폭을 100dp로 설정
android:numColumns=“auto_fit”**: 열의 폭과 화면 폭을 바탕으로 자동 계산
android:verticalSpacing**: 항목 간의 간격 설정
android:stretchMode=“columnWidth”**: 열 내부의 여백을 폭에 맞게 채움
/>
kt파일
package com.example.myapplication
import android.R
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.Toast
import com.example.myapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 데이터 원본 준비
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
}
}
마찬가지로 잘 출력이 된다.
위와같이 이미지와 문자열이나 이미지를포함하는 위젯을 항목에 넣을 수 있다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<ImageView
android:id="@+id/iconItem"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:scaleType="centerCrop"
android:padding="@dimen/icon_padding"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:src="@drawable/sample_0"
/>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2">
<TextView
android:id="@+id/textItem1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="@dimen/list_item_text_size1"
android:padding="@dimen/list_item_padding"
android:hint="Name"
/>
<TextView
android:id="@+id/textItem2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="@dimen/list_item_text_size2"
android:padding="@dimen/list_item_padding"
android:hint="Age"
/>
</LinearLayout>
</LinearLayout>
data class MyItem(val aIcon:Int, val aName:String, val aAge:String) {}
data클래스는
equals(), hashCode(), toString(), copy(), componentN() 5가지 유용한 함수들을 내부적으로 자동으로 생성한다.
package com.android.customitemview
class MyAdapter(val mContext: Context, val mItems: MutableList<MyItem>) : BaseAdapter() {
// MyAdapter 클래스가 관리하는 항목의 총 개수를 반환
override fun getCount(): Int {
return mItems.size
}
// MyAdapter 클래스가 관리하는 항목의 중에서 position 위치의 항목을 반환
override fun getItem(position: Int): Any {
return mItems[position]
}
// 항목 id를 항목의 위치로 간주함
override fun getItemId(position: Int): Long {
return position.toLong()
}
// position 위치의 항목에 해당되는 항목뷰를 반환하는 것이 목적임
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
var convertView = convertView
if (convertView == null) convertView = LayoutInflater.from(parent?.context).inflate(R.layout.item, parent, false)
val item : MyItem = mItems[position]
// convertView 변수로 참조되는 항목 뷰 객체내에 포함된 객체를 id를 통해 얻어옴
val iconImageView = convertView?.findViewById<View>(R.id.iconItem) as ImageView
val tv_name = convertView.findViewById<View>(R.id.textItem1) as TextView
val tv_age = convertView.findViewById<View>(R.id.textItem2) as TextView
// 어댑터가 관리하는 항목 데이터 중에서 position 위치의 항목의 객체를 헤딩 힝목에 설정
iconImageView.setImageResource(item.aIcon)
tv_name.text = item.aName
tv_age.text = item.aAge
return convertView
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
MainActivity.kt 파일에서 어댑터 객체에서 관리할 항목 데이터의 ArrayList 객체를 준비하고
MyAdapter 객체를 생성하고 초기화 한다.
생성된 MyAdapter 객체를 어댑터뷰인 리스트뷰에 연결한다.
package com.android.customitemview
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 데이터 원본 준비
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"))
// 어댑터 생성 및 연결
binding.listView.adapter = MyAdapter(this, dataList)
// 항목 클릭 이벤트 처리
binding.listView.setOnItemClickListener{ parent, view, position, id ->
val name: String = (binding.listView.adapter.getItem(position) as MyItem).aName
Toast.makeText(this," $name 선택!", Toast.LENGTH_SHORT).show()
}
}
}
실행화면
가장 중요하다는 RecyclerView는 View를 계속 생성하는 것이 아닌 재활용해서 사용하는 View이다.
그렇기 때문에 다른 View와 달리 Adapter뿐 아니라 저장할 수 있는 ViewHolder가 필요하다.
동일한 예제에서 메인 레이아웃과 Adapter.kt 파일, Main.kt파일만 수정하면 된다.
레이아웃은 ListView위젯을 RecyclerView위젯으로 변경한다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
어댑터 클래스는
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 MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 데이터 원본 준비
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"))
binding.recyclerView.adapter = MyAdapter(dataList)
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()
}
}
}
}
생긴건 거의 동일하지만, 기능은 꽤 다른 리사이클뷰가 완성이됐다.
interface는 setOnClickListner처럼 내가 커스텀해서 쓸 수 있는것
내가 interface 어쩌고 하고 뒤에 기능을 쓰면
어쩌고는 앞으로 이기능을 쓰는거야 라는것
object는 원래 상속받는 class를 써야하는데 임의로 object라는 클래스에 넣어놓을게 라는뜻
예시 -
adapter.itemClick = object : MyAdapter.ItemClick {
override fun onClick(view: View, position: Int) {
val name: String = dataList[position].aName
Toast.makeText(this@MainActivity, "선택!", Toast.LENGTH_SHORT).show()
}
}
}
}
class Test : MyAdapter.ItemClick{
override fun onClick(view: View, position: Int) {
TODO("Not yet implemented")
}
}
//
itemClick : ItemClick? = null 은 무얼 의미하는지
ItemClick타입의 변수를 만들었고 일단 ? = null 할당하겠다.
nullable
parent: ViewGroup, viewType: Int
매개변수prrent와 ViewGroup은 클래스지만 자료View상위
inflate(LayoutInflater.from(parent.context), parent, false)
override fun getItemId(position: Int): Long {
return position.toLong()
getItemId가 Long값을 받기로 약속
inflate - xml이랑 연결돼있는 객체. 바인딩 생성 시 inflate bind함수와 inflate
object 인터페이스에 대한 익명의 이름을 지정. 원래 클래스를 상속받아야 하는데 object
getItemId 해당값의 고유값을 알고 싶을 때
고생 많으셨어요
앞으로 2주가 될지, 3주가 될지 모르겠지만
잘 부탁드립니다 형님