
매번 메인 액티비티로 돌아올때 onResume에서 리사이클러뷰를 새로고침해줄 수 있음
하지만 onCreate에 어댑터 설정을 해놨을때 onResume에서도 어댑터 설정이 동작하므로 액티비티를 맨 처음 동작 시 어댑터 설정이 두번 중복으로 일어나게 됨
onResume에서 리사이클러 새로고침 코드를 한번만 작성하든가
런처별로 새로고침 코드를 매번 작성하든가 방법은 선택하면 됨
※ RMI 기술 참고
객체지향프로그래밍언어에서 일반적으로 어떤 객체를 사용하고싶을때 객체의 주소를 전달받아 사용함
그러나 안드로이드에서 intent를 통해 객체의 주소값을 직접 전달할 수는 없음
A 액티비티에서 B 액티비티로 이동할때 객체의 주소값을 전달해버리면
B 액티비티를 보여주는 시점에서 A 액티비티를 종료해버리는 경우 생성한 객체 또한 같이 소멸할 수 있기 때문에 B 액티비티에서는 전달받은 객체의 주소값을 사용할 수가 없다 따라서 이러한 위험성을 원천 차단하고 있음
Parcelable 인터페이스를 구현하면 해당 객체가 가지고 있는 값을 담은 Parcel객체를 전달 가능
Parcel객체를 전달받아 이동한 액티비티에서 새로운 객체를 생성해서 사용함
Lion객체를 생성하고 변수에 담아서 프로퍼티를 설정하고 animalList에 객체의 주소값을 담음
이번 프로젝트에서는 항목의 순서값만 전달하면 등록,수정,삭제 모두 해결이 가능하지만 Parcelable도 사용할 것임
리사이클러뷰 항목 클릭 이벤트 리스너는 ViewHolder클래스의 init블럭에 작성해도 되고
// ViewHolder
inner class ViewHolderMain(rowMainBinding: RowMainBinding) : RecyclerView.ViewHolder(rowMainBinding.root){
val rowMainBinding: RowMainBinding
init {
this.rowMainBinding = rowMainBinding
this.rowMainBinding.root.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
// 항목을 누르면 ShowActivity를 실행
this.rowMainBinding.root.setOnClickListener {
val showIntent = Intent(this@MainActivity, ShowActivity::class.java)
showIntent.putExtra("position",adapterPosition)
showActivityLauncher.launch(showIntent)
}
}
}
onBindViewHolder 메서드 안에서 작성해도 상관없음
override fun onBindViewHolder(holder: ViewHolderMain, position: Int) {
// 항목을 누르면 ShowActivity를 실행
holder.rowMainBinding.root.setOnClickListener {
val showIntent = Intent(this@MainActivity, ShowActivity::class.java)
showIntent.putExtra("position",position)
showActivityLauncher.launch(showIntent)
}
}
동물 타입별 프로퍼티를 가져올 때 명시적 형변환 대신 스마트 캐스팅을 이용할 수 있다.
when(animal.type){
// 사자
AnimalType.ANIMAL_TYPE_LION -> {
// 사자면 Lion 타입으로 형변환한다.
val lion = animal as Lion
append("털의 개수 : ${lion.hairCount}개\n")
append("성별 : ${lion.gender.str}\n")
}
// 호랑이
AnimalType.ANIMAL_TYPE_TIGER -> {
// 호랑이면 Tiger 타입으로 형변환한다.
val tiger = animal as Tiger
append("줄무늬 개수 : ${tiger.lineCount}개\n")
append("몸무게 : ${tiger.weight}kg\n")
}
// 기린
AnimalType.ANIMAL_TYPE_GIRAFFE -> {
// 기린이면 Giraffe 타입으로 형변환한다.
val giraffe = animal as Giraffe
append("목의 길이 : ${giraffe.neckLength}cm\n")
append("달리는 속도 : 시속 ${giraffe.runSpeed}km\n")
}
}
// 사자
if(animal is Lion){
append("털의 개수 : ${animal.hairCount}개\n")
append("성별 : ${animal.gender.str}\n")
}
// 호랑이
else if(animal is Tiger){
append("줄무늬 개수 : ${animal.lineCount}개\n")
append("몸무게 : ${animal.weight}kg\n")
}
// 기린
else if(animal is Giraffe){
append("목의 길이 : ${animal.neckLength}cm\n")
append("달리는 속도 : 시속 ${animal.runSpeed}km\n")
}
무조건 String만 넣어야 하므로 int인 경우에는 문자열로 변경해줘야 한다. 안그러면 오류가 발생한다.
textFieldModifyAge.setText("${animal.age}")
slider뷰의 value값을 넣어줄때는 Float값으로 넣어줘야 한다.
sliderModifyWeight.value = animal.weight.toFloat()
메인액티비티에서 가져온 position으로 객체에 접근한다.
val animal = Util.animalList[position]
접근한 객체의 프로퍼티를 직접 수정하면 된다.
// 수정 처리
fun modifyData(){
// 위치 값을 가져온다.
val position = intent.getIntExtra("position",0)
// position번째 객체를 가져온다.
val animal = Util.animalList[position]
activityModifyBinding.apply {
// 공통
animal.name = textFieldModifyName.text.toString()
animal.age = textFieldModifyAge.text.toString().toInt()
// 클래스 타입별로 분기
// 사자
if(animal is Lion){
animal.hairCount = textFieldModifyHairCount.text.toString().toInt()
animal.gender = when(buttonGroupModifyGender.checkedButtonId){
R.id.buttonModifyGender1 -> LION_GENDER.LION_GENDER1
R.id.buttonModifyGender2 -> LION_GENDER.LION_GENDER2
else -> LION_GENDER.LION_GENDER1
}
}
// 호랑이
else if(animal is Tiger){
animal.lineCount = textFieldModifyLineCount.text.toString().toInt()
animal.weight = sliderModifyWeight.value.toInt()
}
// 기린
else if(animal is Giraffe){
animal.neckLength = textFieldModifyNeckLength.text.toString().toInt()
animal.runSpeed = textFieldModifyRunSpeed.text.toString().toInt()
}
}
}
class ShowActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
// 다른곳에 갔다 왔을 경우 출력 내용을 다시 구성해준다.
setView2()
}
}
레이아웃 파일 작성 필요 없이 코드로 구현 가능하다.
setItems

setMultiChoiceItems

setSingleChoiceItems

필터 타입을 enum클래스로 정리
// 필터 타입
enum class FilterType(var num:Int, var str:String){
FILTER_TYPE_ALL(0,"전체"),
FILTER_TYPE_LION(0,"사자"),
FILTER_TYPE_TIGER(0,"호랑이"),
FILTER_TYPE_GIRAFFE(0,"기린")
}
필터 사용을 위한 리스트 2개와 필터타입 셋팅
// MainActivity.kt
class MainActivity : AppCompatActivity() {
// RecycerView를 구성하기 위한 리스트
val recyclerViewList = mutableListOf<Animal>()
// 현재 항목을 구성하기 위해 사용한 객체가 Util.animalList의 몇번째 객체인지를 담을 리스트
val recyclerViewIndexList = mutableListOf<Int>()
// 현재 선택되어 있는 필터 타입
var filterType = FilterType.FILTER_TYPE_ALL
}
필터 초기값 셋팅
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
setLauncher()
setToolbar()
setView()
setEvent()
}
리사이클러뷰 어댑터 셋팅도 변경
override fun getItemCount(): Int {
return recyclerViewList.size
}
override fun onBindViewHolder(holder: ViewHolderMain, position: Int) {
// position번째 객체를 추출
val animal = recyclerViewList[position]
...
}
onResume에서 필터 타입에 맞는 데이터 담아주기
override fun onResume() {
super.onResume()
activityMainBinding.apply {
// 필터 타입에 맞는 데이터를 담는다.
setRecyclerViewList()
// 리사이클러 뷰 갱신
recyclerView.adapter?.notifyDataSetChanged()
}
}
다이얼로그 항목을 클릭 시 선택한 항목에 대한 필터 값을 설정해주기
// 필터 다이얼로그를 띄우는 메서드
fun showFilterDialog(){
val dialogBuilder = MaterialAlertDialogBuilder(this@MainActivity)
dialogBuilder.setTitle("필터 선택")
// 항목
val itemArray = arrayOf("전체","사자","호랑이","기린")
dialogBuilder.setItems(itemArray){ dialogInterface: DialogInterface, i: Int ->
// 리스너의 두 번째 매개변수(i)에는 사용자가 선택한 다이얼로그의 항목의 순서값이 전달된다.
// 선택한 항목에 대한 필터 값을 설정해준다.
filterType = when(i){
0 -> FilterType.FILTER_TYPE_ALL
1 -> FilterType.FILTER_TYPE_LION
2 -> FilterType.FILTER_TYPE_TIGER
3 -> FilterType.FILTER_TYPE_GIRAFFE
else -> FilterType.FILTER_TYPE_ALL
}
// 데이터를 새로 담는다.
setRecyclerViewList()
// 리사이클러뷰를 갱신한다.
activityMainBinding.recyclerView.adapter?.notifyDataSetChanged()
}
dialogBuilder.setNegativeButton("취소",null)
dialogBuilder.show()
}
리사이클러뷰 항목 선택시 intent에 담아줄 position을 원래의 animalList에서 몇번째였는지를 담아준다
// 항목을 누르면 ShowActivity를 실행
holder.rowMainBinding.root.setOnClickListener {
val showIntent = Intent(this@MainActivity, ShowActivity::class.java)
// showIntent.putExtra("position",position)
// 사용자가 선택한 항목을 구성하기 위해 사용한 객체가
// Util.animalList 리스트에 몇번째에 있는 값인지를 담아준다.
showIntent.putExtra("position",recyclerViewIndexList[position])
showActivityLauncher.launch(showIntent)
}
다이얼로그 설정
검색 필터에 따라 리스트에 데이터를 담아주는 메서드
// 검색 필터에 따라 리스트에 데이터를 담아주는 메서드
fun setRecyclerViewList(){
// 기본 다이얼로그용
setRecyclerViewListBasic()
}
// 기본 다이얼로그용
fun setRecyclerViewListBasic(){
// 리스트 초기화
recyclerViewList.clear()
recyclerViewIndexList.clear()
// 필터에 따라 분기한다.
when(filterType){
// 전체
FilterType.FILTER_TYPE_ALL -> {
// 모든 객체를 담아준다.
var index = 0
Util.animalList.forEach {
recyclerViewList.add(it)
recyclerViewIndexList.add(index)
index++
}
}
// 사자
FilterType.FILTER_TYPE_LION -> {
// 사자만 담아준다.
var index = 0
Util.animalList.forEach {
if(it.type==AnimalType.ANIMAL_TYPE_LION){
recyclerViewList.add(it)
recyclerViewIndexList.add(index)
}
index++
}
}
// 호랑이
FilterType.FILTER_TYPE_TIGER -> {
// 호랑이만 담아준다.
var index = 0
Util.animalList.forEach {
if(it.type==AnimalType.ANIMAL_TYPE_TIGER){
recyclerViewList.add(it)
recyclerViewIndexList.add(index)
}
index++
}
}
// 기린
FilterType.FILTER_TYPE_GIRAFFE -> {
// 기린만 담아준다.
var index = 0
Util.animalList.forEach {
if(it.type==AnimalType.ANIMAL_TYPE_GIRAFFE){
recyclerViewList.add(it)
recyclerViewIndexList.add(index)
}
index++
}
}
}
}
// 현재 선택되어 있는 필터 타입 - MultiChoice
var filterTypeMulti = booleanArrayOf(true, true, true)
fun showFilterDialogMultiChoice(){
val dialogBuilder = MaterialAlertDialogBuilder(this@MainActivity)
dialogBuilder.setTitle("필터 선택")
// 항목, 전체 선택이 가능하므로 전체 항목은 없애준다.
val itemArray = arrayOf("사자","호랑이","기린")
// 두 번째 : 체크상태가 변경된 항목의 순서값
// 세 번째 : 체크 상태
dialogBuilder.setMultiChoiceItems(itemArray,filterTypeMulti){ dialogInterface: DialogInterface, i: Int, b: Boolean ->
// 체크가 변경된 항목 번째의 값을 변경한다.
filterTypeMulti[i] = b
}
dialogBuilder.setNegativeButton("취소",null)
dialogBuilder.setPositiveButton("확인"){ dialogInterface: DialogInterface, i: Int ->
// 데이터를 새로 담는다.
setRecyclerViewList()
// 리사이클러뷰를 갱신한다.
activityMainBinding.recyclerView.adapter?.notifyDataSetChanged()
}
dialogBuilder.show()
}
// 검색 필터에 따라 리스트에 데이터를 담아주는 메서드
fun setRecyclerViewList(){
// 기본 다이얼로그용
// setRecyclerViewListBasic()
// MultiChoice 다이얼로그용
setRecyclerViewListMulti()
}
// MultiChoice 다이얼로그용
fun setRecyclerViewListMulti() {
// 리스트 초기화
recyclerViewList.clear()
recyclerViewIndexList.clear()
// animalList에 담긴 객체의 수 만큼 반복한다.
var index = 0
Util.animalList.forEach {
// 동물 타입이 사자이고 사자 필터가 true라면 담아준다.
if(it.type == AnimalType.ANIMAL_TYPE_LION && filterTypeMulti[0] == true){
recyclerViewList.add(it)
recyclerViewIndexList.add(index)
}
// 동물 타입이 호랑이이고 호랑이 필터가 true라면 담아준다.
if(it.type == AnimalType.ANIMAL_TYPE_TIGER && filterTypeMulti[1] == true){
recyclerViewList.add(it)
recyclerViewIndexList.add(index)
}
// 동물 타입이 기린이고 기린 필터가 true라면 담아준다.
if(it.type == AnimalType.ANIMAL_TYPE_GIRAFFE && filterTypeMulti[2] == true){
recyclerViewList.add(it)
recyclerViewIndexList.add(index)
}
index++
}
}

setOnMenuItemClickListener {
when(it.itemId){
// 필터 메뉴
R.id.menu_item_main_filter -> {
// 필터 선택을 위한 다이얼로그를 띄운다.
// 기본 다이얼로그
// showFilterDialog()
// MultiChoice 다이얼로그
// showFilterDialogMultiChoice()
// SingleChoice 다이얼로그
showFilterDialogSingleChoice()
}
}
true
}
// 필터 다이얼로그를 띄우는 메서드 - singleChoice
fun showFilterDialogSingleChoice(){
val dialogBuilder = MaterialAlertDialogBuilder(this@MainActivity)
dialogBuilder.setTitle("필터 선택")
// 항목
val itemArray = arrayOf("전체","사자","호랑이","기린")
dialogBuilder.setSingleChoiceItems(itemArray, filterType.num){ dialogInterface: DialogInterface, i: Int ->
// 리스너의 두 번째 매개변수(i)에는 사용자가 선택한 다이얼로그의 항목의 순서값이 전달된다.
// 선택한 항목에 대한 필터 값을 설정해준다.
filterType = when(i){
0 -> FilterType.FILTER_TYPE_ALL
1 -> FilterType.FILTER_TYPE_LION
2 -> FilterType.FILTER_TYPE_TIGER
3 -> FilterType.FILTER_TYPE_GIRAFFE
else -> FilterType.FILTER_TYPE_ALL
}
}
dialogBuilder.setNegativeButton("취소",null)
dialogBuilder.setPositiveButton("확인"){ dialogInterface: DialogInterface, i: Int ->
// 데이터를 새로 담는다.
setRecyclerViewList()
// 리사이클러뷰를 갱신한다.
activityMainBinding.recyclerView.adapter?.notifyDataSetChanged()
}
dialogBuilder.show()
}
// 검색 필터에 따라 리스트에 데이터를 담아주는 메서드
fun setRecyclerViewList(){
// 기본 다이얼로그, SingleChoice 용
setRecyclerViewListBasic()
// MultiChoice 다이얼로그용
// setRecyclerViewListMulti()
}
// 기본 다이얼로그용
fun setRecyclerViewListBasic(){
// 리스트 초기화
recyclerViewList.clear()
recyclerViewIndexList.clear()
// 필터에 따라 분기한다.
when(filterType){
// 전체
FilterType.FILTER_TYPE_ALL -> {
// 모든 객체를 담아준다.
var index = 0
Util.animalList.forEach {
recyclerViewList.add(it)
recyclerViewIndexList.add(index)
index++
}
}
// 사자
FilterType.FILTER_TYPE_LION -> {
// 사자만 담아준다.
var index = 0
Util.animalList.forEach {
if(it.type==AnimalType.ANIMAL_TYPE_LION){
recyclerViewList.add(it)
recyclerViewIndexList.add(index)
}
index++
}
}
// 호랑이
FilterType.FILTER_TYPE_TIGER -> {
// 호랑이만 담아준다.
var index = 0
Util.animalList.forEach {
if(it.type==AnimalType.ANIMAL_TYPE_TIGER){
recyclerViewList.add(it)
recyclerViewIndexList.add(index)
}
index++
}
}
// 기린
FilterType.FILTER_TYPE_GIRAFFE -> {
// 기린만 담아준다.
var index = 0
Util.animalList.forEach {
if(it.type==AnimalType.ANIMAL_TYPE_GIRAFFE){
recyclerViewList.add(it)
recyclerViewIndexList.add(index)
}
index++
}
}
}
}
setItems 메서드는 항목을 클릭하면 다이얼로그가 닫히지만
singleChoice와 multiChoice 메서드는 항목을 클릭해도 다이얼로그가 닫히지 않는다
따라서 확인 버튼을 눌렀을 때 데이터를 새로 담고 리사이클러뷰를 갱신해주는 작업을 해준다.
부모 클래스인 Animal이 아닌 자식클래스에만 Parcelable 인터페이스를 구현했다.
writeToParcel 메서드 오버라이딩 시 부모클래스 값이 빠져있다.
class Lion() : Animal(), Parcelable {
// 털의 개수
var hairCount = 0
// 성별
var gender = LION_GENDER.LION_GENDER1
constructor(parcel: Parcel) : this() {
hairCount = parcel.readInt()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(hairCount)
}
따라서 부모클래스에 parcel객체에 부모클래스의 데이터를 담아줄 메서드를 추가해준다.
open class Animal {
// 동물 타입
var type = AnimalType.ANIMAL_TYPE_LION
// 이름
var name = ""
// 나이
var age = 0
// parcel 객체에 데이터를 담는다.
fun addToParcel(parcel:Parcel){
parcel.writeValue(type)
parcel.writeString(name)
parcel.writeInt(age)
}
// parcel로부터 데이터를 추출하여 담아준다.
fun getFromParcel(parcel: Parcel){
type = parcel.readValue(AnimalType::class.java.classLoader) as AnimalType
name = parcel.readString()!!
age = parcel.readInt()
}
}
각 동물 클래스에 해당 메서드를 추가해준다.
사자의 경우 gender값도 직접 추가해줘야 한다.
class Lion() : Animal(), Parcelable {
// 털의 개수
var hairCount = 0
// 성별
var gender = LION_GENDER.LION_GENDER1
constructor(parcel: Parcel) : this() {
getFromParcel(parcel)
hairCount = parcel.readInt()
gender = parcel.readValue(LION_GENDER::class.java.classLoader) as LION_GENDER
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
addToParcel(parcel)
parcel.writeInt(hairCount)
parcel.writeValue(gender)
}
class Tiger() : Animal(), Parcelable {
// 줄무늬 개수
var lineCount = 0
// 몸무게
var weight = 0
constructor(parcel: Parcel) : this() {
getFromParcel(parcel)
lineCount = parcel.readInt()
weight = parcel.readInt()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
addToParcel(parcel)
parcel.writeInt(lineCount)
parcel.writeInt(weight)
}
class Giraffe() : Animal(), Parcelable {
// 목의 길이
var neckLength = 0
// 달리는 속도
var runSpeed = 0
constructor(parcel: Parcel) : this() {
getFromParcel(parcel)
neckLength = parcel.readInt()
runSpeed = parcel.readInt()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
addToParcel(parcel)
parcel.writeInt(neckLength)
parcel.writeInt(runSpeed)
}
이대로는 리스트에 담긴 객체값의 주소를 담은 변수의 타입이 Animal이고 Parcelable이 아니기 때문에 intent에 담을수가 없다
이부분을 해결할 방법은 강사님이 고민해보시고 내일 이어서 할 예정
※ 출처 : 멋쟁이사자 앱스쿨 2기, 소프트캠퍼스