-
TECHIT 앱 스쿨 2기: Android 33일차 (23.06.14)[THEC!T] 앱 스쿨2기 : Android 2023. 6. 15. 03:11728x90
자료 출처 : 안드로이드 앱스쿨 2기 윤재성 강사님 수업 내용
오늘의 시작은 어제 강사님이 내주신 문제에 대한 설명과 작성으로 시작하였습니다.
문제는 에디트 텍스트를 3개를 구성한 후 마지막에 배치된 에디트 텍스트에서 엔터를 누르면 해당 정보들이 리스트 뷰에 나오게 되며 에디트 텍스트들에 있는 내용들은 정리되게 만드는 문제였습니다.
즉 전 날 배운 내용 중 개발자가 만든 레이아웃 파일과 SimpleAdapter 를 이용하여 작성하는 문제였습니다.
주요 부분 코드
activityMainBinding.run{ listViewResult.run{ val keys = arrayOf( "name", "age", "korean" ) val ids = intArrayOf( R.id.textViewRowName, R.id.textViewRowAge, R.id.textViewRowKorean ) adapter = SimpleAdapter( this@MainActivity, listData, R.layout.row, keys, ids ) } editTextKorean.run{ setOnEditorActionListener { v, actionId, event -> val map = mutableMapOf<String,String>() map["name"] = editTextName.text.toString() map["age"] = editTextAge.text.toString() map["korean"] = editTextKorean.text.toString() listData.add(map) editTextName.setText("") editTextAge.setText("") editTextKorean.setText("") val adapter = listViewResult.adapter as SimpleAdapter adapter.notifyDataSetChanged() false } } }
다음으로는 어댑터 클래스를 커스텀하여 사용하는 방법을 알려주셨습니다.
강사님께서 MainActivity 안에 inner class 를 이용하여 작성하는 방식으로 알려주셨습니다.
어댑터 클래스를 원하는대로 작성하기 위해서는 BaseAdapter()을 상속받아 메서드를 구현해주는 방식으로 작성하면 됩니다.
작성 방법은 하기와 같습니다.
상속을 받으면 구현을 해야하는 메서드들을 오버라이딩 하여 구현해주면 됩니다.
inner class AdapterCustom :BaseAdapter(){ override fun getCount(): Int { TODO("Not yet implemented") } override fun getItem(position: Int): Any { TODO("Not yet implemented") } override fun getItemId(position: Int): Long { TODO("Not yet implemented") } override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { TODO("Not yet implemented") } }
편하게 설명하기 위해 리스트 뷰에 표시되는 레이아웃을 Item이라고 하겠습니다.
getCount() : 리스트 뷰의 Item 개수를 결정하는 메서드 -> 즉 몇 개의 Item 을 보여줄 리스트를 생성할 것 인지
getItem(position: Int) : 현재 몇 번째의 아이템 뷰를 반환하도록 만들어준다.
getItemId(position: Int) : 현재 i 번째의 Item 뷰의 Id를 반환하도록 만들어 준다.
getView(position: Int, convertView: View?, parent: ViewGroup?)
Item 으로 사용할 View를 생성하여 반환하는 메서드이며, 여기서 반환하는 View를 현재 i번째의 Item으로 사용한다.
position : 구성하고자 하는 Item의 순서 값 (0 부터 1씩 증가)
convertView : 재사용 가능한 View가 있다면 매개 변수로 들어온다.
parent : getView() 메서드에서 생성되는 아이템 뷰가 속할 부모 뷰그룹을 의미합니다. 리스트뷰의 경우 부모 뷰그룹은 리스트뷰 자체를 나타냅니다.
class MainActivity : AppCompatActivity() { lateinit var activityMainBinding: ActivityMainBinding val data1 = arrayOf( "데이터1", "데이터2", "데이터3", "데이터4", "데이터5" ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) activityMainBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(activityMainBinding.root) activityMainBinding.run{ listView.run{ adapter = CusAdapter() } } } inner class CusAdapter : BaseAdapter(){ override fun getCount(): Int { return data1.size } override fun getItem(position: Int): Any? { return null } override fun getItemId(position: Int): Long { return 0 } override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { //layout binding 객체를 담을 변수 var itemBinding : ItemBinding? = null // 항목 View를 담을 변수 var itemView = convertView if(itemView == null){ itemBinding = ItemBinding.inflate(layoutInflater) itemView = itemBinding.root itemView!!.tag = itemBinding } else { itemBinding = itemView!!.tag as ItemBinding } itemBinding.run{ textViewRow1.run{ text = data1[position] } buttonRow1.run{ setOnClickListener { activityMainBinding.textViewResult.text = "$position : 버튼1" } } buttonRow2.run{ setOnClickListener { activityMainBinding.textViewResult.text = "$position : 버튼2" } } } return itemView } } }
다음으로는 문제를 내주셨습니다.
지금까지 배운 내용을 바탕으로 작성할 수 있는 문제였으며, 사용자가 텍스트를 입력하면 텍스트가 리스트 뷰에 나오게 되며, 해당 뷰에 있는 버튼을 누르면 리스트 뷰에서 사라지게 만드는 문제 였습니다.
package com.test.android34_homework0614 import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter import com.test.android34_homework0614.databinding.ActivityMainBinding import com.test.android34_homework0614.databinding.RowBinding class MainActivity : AppCompatActivity() { lateinit var activityMainBinding: ActivityMainBinding val dataList = ArrayList<String>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) activityMainBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(activityMainBinding.root) activityMainBinding.run{ listViewMain.run{ adapter = CustomAdapter() } editTextMain.run{ setOnEditorActionListener { v, actionId, event -> dataList.add(text.toString()) setText("") val adapter = listViewMain.adapter as CustomAdapter adapter.notifyDataSetChanged() false } } } } inner class CustomAdapter : BaseAdapter(){ override fun getCount(): Int { return dataList.size } override fun getItem(position: Int): Any? { return null } override fun getItemId(position: Int): Long { return 0 } override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { var rowBinding : RowBinding var rowView = convertView if(convertView == null){ rowBinding = RowBinding.inflate(layoutInflater) rowView = rowBinding.root rowView.tag = rowBinding } else { rowBinding = rowView!!.tag as RowBinding } rowBinding.run { textViewRow.run{ text = dataList[position] } buttonRow.run{ setOnClickListener { dataList.removeAt(position) val adapter = activityMainBinding.listViewMain.adapter as CustomAdapter adapter.notifyDataSetChanged() } } } return rowView } } }
강사님과 저의 코드를 비교해보니 변수 명 및 힌트, 버튼 텍스트를 제외하고는 다른 점이 별로 없었습니다.
다음으로는 스피너에 대해서 알려주셨습니다.
Spinner
사용자에게 항목을 주고 선택 하게 할 수 있는 AdapterView 입니다.
다른 AdapterView 와는 다른점은 Spinner 가 펼쳐져 있을 때의 항목 모양과 접혀 있을 때의 모양을 지정할 수 있습니다.
package com.test.android35_spinner import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.View import android.widget.AdapterView import android.widget.AdapterView.OnItemSelectedListener import android.widget.ArrayAdapter import com.test.android35_spinner.databinding.ActivityMainBinding import java.util.Objects class MainActivity : AppCompatActivity() { lateinit var activityMainBinding: ActivityMainBinding val dataList = arrayOf( "항목1", "항목2", "항목3", "항목4", "항목5", "항목6", "항목7", "항목8", "항목9", "항목10", "항목11", "항목12", "항목13", "항목14", "항목15", "항목16", "항목17", "항목18", "항목19", "항목20", ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) activityMainBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(activityMainBinding.root) activityMainBinding.run{ spinner.run{ // 어댑터 설정 val a1 = ArrayAdapter<String>( this@MainActivity, // Spinner 가 접혀있을 때의 모양 android.R.layout.simple_spinner_item, dataList ) // Spinner가 펼쳐져 있을 때의 항목 모양 a1.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) adapter = a1 // Spinner의 항목을 코드로 선택한다. // 0부터 시작하는 순서 값을 넣어준다. setSelection(2) // 항목을 선택하면 동작하는 리스너 // 3번째 : 선택한 항목의 순서값(0부터..) onItemSelectedListener = object : OnItemSelectedListener{ // 항목을 선택했을 호출되는 메서드 // 3 번째 : 선택한 항목의 순서 값(0부터..) override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { // Spinner에서 선택된 항목의 순서값을 가져온다. val position = spinner.selectedItemPosition textView2.text = "${dataList[position]} 항목을 선택했습니다." } override fun onNothingSelected(parent: AdapterView<*>?) { // TODO("Not yet implemented") } } } button.run{ setOnClickListener { // Spinner에서 선택된 항목의 순서값을 가져온다. val position = spinner.selectedItemPosition textView.text = "선택한 항목 : ${dataList[position]}" } } } } }
마지막으로는 리사이클러 뷰를 알려주셨습니다.
강사님께서 자세하게 설명해주셨지만 매우 요약하고 간단하게 말하자면 리스트뷰의 경우 pool 이라는 메모리에 화면에서 안보이게 되거나 보이게 되는 아이템의 즉 뷰의 정보를 담고, 리사이클러의 뷰의 경우 pool 이라는 메모리에 뷰의 id 만 담아 메모리, 속도적으로 리스트뷰에 비해 매우 효율적이라고 말씀해 주셨습니다.
리사이클러뷰(RecyclerView)안드로이드에서 리스트와 그리드와 같은 컬렉션 데이터를 표시하기 위해 사용되는 고급 위젯입니다. 리사이클러뷰는 유연하고 효율적인 방식으로 아이템을 표시하며, 많은 아이템을 스크롤 가능한 리스트 형태로 표시할 수 있습니다. 리사이클러뷰는 다음과 같은 핵심 구성 요소로 구성됩니다:
LayoutManager: 리사이클러뷰의 아이템을 배치하는 방법을 결정하는 클래스입니다. 예를 들어, LinearLayoutManager는 선형적으로 아이템을 배치하고, GridLayoutManager는 그리드 형태로 아이템을 배치합니다. 리사이클러뷰는 여러 가지 레이아웃 매니저를 지원하여 다양한 아이템 배치 방식을 구현할 수 있습니다.
Adapter: 리사이클러뷰의 데이터를 관리하고 아이템 뷰를 생성하는 역할을 수행하는 클래스입니다. Adapter 클래스는 RecyclerView.Adapter를 상속받아 구현하며, onCreateViewHolder, onBindViewHolder, getItemCount 등의 메서드를 재정의하여 아이템과 데이터를 연결합니다.
ViewHolder: 아이템 뷰의 구성 요소를 보유하고 관리하는 클래스입니다. ViewHolder는 RecyclerView.ViewHolder를 상속받아 구현하며, onCreateViewHolder 메서드에서 아이템 뷰의 레이아웃을 inflate하고 뷰 홀더 객체를 생성합니다. 이후 onBindViewHolder 메서드에서 데이터를 뷰 홀더에 바인딩합니다.그리고 강사님께서 리사이클러 뷰의 어댑터 클래스를 쉽게 작성하는 법을 알려주셨습니다.
1. 아무것도 상속받지 않은 클래스를 만들어 준다.
2. ViewHolder 를 만들어 준다.
3. AdapterClass를 RecyclerView.Adapter를 상속받게 한다.
4. 필요한 메서드들을 구현한다.inner class RecycleAda { inner class ViewHolderClass(itemView: View) : ViewHolder(itemView) { } }
inner class RecycleAda : RecyclerView.Adapter<RecycleAda.ViewHolderClass>(){ inner class ViewHolderClass(itemView: View) : ViewHolder(itemView) { } }
inner class RecycleAda : RecyclerView.Adapter<RecycleAda.ViewHolderClass>(){ inner class ViewHolderClass(itemView: View) : ViewHolder(itemView) { } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderClass { TODO("Not yet implemented") } override fun getItemCount(): Int { TODO("Not yet implemented") } override fun onBindViewHolder(holder: ViewHolderClass, position: Int) { TODO("Not yet implemented") } }
inner class RecycleAda : RecyclerView.Adapter<RecycleAda.ViewHolderClass>(){ inner class ViewHolderClass(itemBinding: ItemBinding) : ViewHolder(itemBinding.root) { // 바인딩으로 변경 뒤 현재 홀더에 있는 아이템 뷰 내 객체에 접근하여 해당 객체의 id 를 저장한다. var text = TextView init{ text = itemBinding.itemText } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderClass { TODO("Not yet implemented") } override fun getItemCount(): Int { TODO("Not yet implemented") } override fun onBindViewHolder(holder: ViewHolderClass, position: Int) { TODO("Not yet implemented") } }
inner class RecycleAda : RecyclerView.Adapter<RecycleAda.ViewHolderClass>(){ inner class ViewHolderClass(itemBinding: ItemBinding) : ViewHolder(itemBinding.root) { // 바인딩으로 변경 뒤 현재 홀더에 있는 아이템 뷰 내 객체에 접근하여 해당 객체의 id 를 저장한다. var text = TextView init{ text = itemBinding.itemText } } // ViewHolder의 객체를 생성해서 반환한다. // 전체 행의 개수가 아닌 필요한 만큼만 행으로 사용할 View를 만들고 ViewHolder 도 생성한다. override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderClass { val itemBinding = ItemBinding.inflate(layoutInflater) val viewHolderClass = ViewHolderClass(itemBinding) return viewHolderClass } override fun getItemCount(): Int { return ItemList.size } override fun onBindViewHolder(holder: ViewHolderClass, position: Int) { TODO("Not yet implemented") } }
inner class RecycleAda : RecyclerView.Adapter<RecycleAda.ViewHolderClass>(){ inner class ViewHolderClass(itemBinding: ItemBinding) : ViewHolder(itemBinding.root) { // 바인딩으로 변경 뒤 현재 홀더에 있는 아이템 뷰 내 객체에 접근하여 해당 객체의 id 를 저장한다. var text = TextView init{ text = itemBinding.itemText } } // ViewHolder의 객체를 생성해서 반환한다. // 전체 행의 개수가 아닌 필요한 만큼만 행으로 사용할 View를 만들고 ViewHolder 도 생성한다. override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderClass { val itemBinding = ItemBinding.inflate(layoutInflater) val viewHolderClass = ViewHolderClass(itemBinding) return viewHolderClass } override fun getItemCount(): Int { return itemList.size } // viewHolder를 통해 View에 접근하여 View에 값을 설정한다. // 첫 번째 매개변수 : ViewHolder 객체 // 두 번째 매개변수 : 특정 행의 순서값 override fun onBindViewHolder(holder: ViewHolderClass, position: Int) { holder.itemView.text = itemList[position] } }
마지막으로는 지금까지 배운 내용을 바탕으로 리사이클러 뷰를 이용하여 어제 내주신 문제를 작성해보는 문제를 내주셨습니다.
package com.test.android36_recyclerviewstudy import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.recyclerview.widget.StaggeredGridLayoutManager import com.test.android36_recyclerviewstudy.databinding.ActivityMainBinding import com.test.android36_recyclerviewstudy.databinding.RowBinding import org.w3c.dom.Text data class Student( var name: String, var age: String, var korean: String ) class MainActivity : AppCompatActivity() { lateinit var activityMainBinding: ActivityMainBinding val stdList = ArrayList<Student>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) activityMainBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(activityMainBinding.root) activityMainBinding.run{ editTextKorean.run{ setOnEditorActionListener { v, actionId, event -> val name = editTextName.text.toString() val age = editTextAge.text.toString() val korean = editTextKorean.text.toString() stdList.add(Student(name, age, korean)) val adapter = rv.adapter as RecyclerAdapterClass adapter.notifyDataSetChanged() editTextName.setText("") editTextAge.setText("") editTextKorean.setText("") clearFocus() false } } rv.run{ adapter = RecyclerAdapterClass() layoutManager = GridLayoutManager(this@MainActivity,1) } } } inner class RecyclerAdapterClass: RecyclerView.Adapter<RecyclerAdapterClass.ViewHolderClass>() { inner class ViewHolderClass(rowBinding: RowBinding) : RecyclerView.ViewHolder(rowBinding.root) { var rowName : TextView var rowAge : TextView var rowKorean : TextView init { rowName = rowBinding.rowName rowAge = rowBinding.rowAge rowKorean = rowBinding.rowKorean } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderClass { val rowBinding = RowBinding.inflate(layoutInflater) val viewHolderClass = ViewHolderClass(rowBinding) val params = RecyclerView.LayoutParams( // 가로길이 RecyclerView.LayoutParams.MATCH_PARENT, // 세로길이 RecyclerView.LayoutParams.WRAP_CONTENT, ) rowBinding.root.layoutParams = params return viewHolderClass } override fun getItemCount(): Int { return stdList.size } override fun onBindViewHolder(holder: ViewHolderClass, position: Int) { holder.rowName.text = "이름 : ${stdList[position].name}" holder.rowAge.text = "나이 : ${stdList[position].age}" holder.rowKorean.text = "국어점수 : ${stdList[position].korean}점" } } }
마무리
리사이클러 뷰는 아직 실력이 부족하다고 느껴 좀 더 반복하면서 손에 익을정도로 연습해야 할 것 같습니다.
완성된 코드를 보며 흐름은 파악을 하였지만, 작성에 있어서는 아직 원활하게 나오지 않기 때문입니다.
이제 안드로이드 ui에 관한 강의는 내일 리사이클러 뷰 에 대한 문제 작성 및 설명을 이후로 종료되며,
안드로이드 메뉴와 4대 구성요소 에 관한 강의가 시작이 됩니다. 강의 주제가 바뀌는 만큼 지금까지 배운 내용을 복습하는 시간을 가져야 할 것 같습니다.
오늘의 마음가짐
주어진 일을 미루지 않는 사람이 되자.(복습 내용도 하루 밀렸을 뿐인데 엄청......)
'[THEC!T] 앱 스쿨2기 : Android' 카테고리의 다른 글
TECHIT 앱 스쿨 2기: Android 36일차 (23.06.19) (0) 2023.06.20 TECHIT 앱 스쿨 2기: Android 34일차 (23.06.15) (0) 2023.06.16 TECHIT 앱 스쿨 2기: Android 32일차 (23.06.13) (1) 2023.06.14 TECHIT 앱 스쿨 2기: Android 31일차 (23.06.12) (1) 2023.06.12 TECHIT 앱 스쿨 2기: Android 30일차 (23.06.09) (1) 2023.06.10