https://coding-juuwon2.tistory.com/287
[Android/Kotlin] Multi View Type 리사이클러뷰 구현하기
일반적인 리사이클러뷰는 하나의 뷰형태만 보여주고 데이터만 달라지지만,리사이클러뷰 멀티뷰타입을 사용하면 다수의(다른) 뷰형태를 가지는 아이템을 보여줄 수 있다 이런식으로 멀티뷰타
coding-juuwon2.tistory.com
이때 만들었던 멀티뷰타입 리사이클러뷰를 MVVM패턴으로 수정해볼것이다
근데 아직 LiveData를 쓰지는 않고, Observer Pattern을 사용해서 수정해볼것이다
# 상수정의
// enum class로 열거형 클래스로 만들기 (코드 단순, 가독성 up)
enum class MultiViewEnum(val viewType : Int) {
BlUE(0),
LIGHTBLUE(1),
ORANGE(2)
}
CardData.kt
# 데이터
@Parcelize
data class CardData(
val id : Int,
val name : String, //이름
val cardName : String, //카드이름
val number : String, //카드번호
val date : String, //유효기간
val price : Double, //가격
val type : MultiViewEnum // 멀티뷰타입 -> enum class로 만든 클래스 선언
): Parcelable
CardData.kt
// 싱글톤
class DataSource {
companion object{
private var INSTANCE : DataSource? = null
fun getDataSoures() : DataSource{
// DatoSource::class 객체에 lock을 걸어 한번에 한 스레드에서만 실행 되도록 함
return synchronized(DataSource::class) {
// 싱글톤 객체를 한번 호출하고 없으면 DataSource반환, 있으면 생성된 인스턴스 반환
val newInstance = INSTANCE ?: DataSource()
INSTANCE = newInstance
newInstance
}
}
}
// MVVM패턴에서 Model에 해당한다고 볼 수 있음
fun getCardList() : List<CardData>{
// 만들어놓은 데이터클래스 리턴
return CardDataList()
}
}
DataSource.kt
fun CardDataList() : ArrayList<CardData>{
return arrayListOf(
CardData(
id = 1,
name = "Juwon",
cardName = "A Debit Card",
number = "2423 3581 9503",
date = "21/27",
price = 3100.30,
MultiViewEnum.BlUE
),
CardData(
id = 2,
name = "Minju",
cardName = "A Hybrid Card",
number = "5423 3581 9503",
date = "07/25",
price = 4100.30,
MultiViewEnum.LIGHTBLUE
),
CardData(
id = 3,
name = "Hemin",
cardName = "A Hi Card",
number = "9423 3581 9503",
date = "23/29",
price = 4170.30,
MultiViewEnum.ORANGE
),
)
}
CardDataList.kt
# Adapter
class CardViewAdpater(var cardList : List<CardData>, private val onClick : (CardData) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
// 레이아웃 연결
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when(viewType){
MultiViewEnum.BlUE.viewType -> {
val binding = RecyclerviewItem1Binding.inflate(inflater, parent, false)
BlueTypeViewHolder(binding)
}
MultiViewEnum.LIGHTBLUE.viewType -> {
val binding = RecyclerviewItem2Binding.inflate(inflater, parent, false)
LightBlueTypeViewHolder(binding)
}
MultiViewEnum.ORANGE.viewType -> {
val binding = RecyclerviewItem3Binding.inflate(inflater, parent, false)
OrangeTypeViewHolder(binding)
}
else -> throw IllegalAccessException("Invalid view type")
}
}
// 아이템 개수 리턴
override fun getItemCount(): Int {
return cardList.size
}
// 멀티뷰타입은 getItemViewType을 오버라이딩 해줘야함
// postion에 따라 어떤 뷰타입을 가져야되는지 연결해줘야함
override fun getItemViewType(position: Int): Int {
return when(position){
0 -> MultiViewEnum.BlUE.viewType
1 -> MultiViewEnum.LIGHTBLUE.viewType
2 -> MultiViewEnum.ORANGE.viewType
else -> throw IllegalAccessException("Invalid view type")
}
}
// 데이터 연결
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val cardlist = cardList[position]
when(holder.itemViewType){
MultiViewEnum.BlUE.viewType -> {
val blueHolder = holder as BlueTypeViewHolder
blueHolder.bind(cardlist)
// MULTI_VIEWTYPE1 클릭 이벤트 처리 (멀티뷰타입마다 데이터가 완전 다른 경우도 있기 때뮨에 그런경우에는 이렇게 클릭이벤트를 주는게 유용함)
holder.itemView.setOnClickListener {
onClick(cardlist)
}
}
MultiViewEnum.LIGHTBLUE.viewType -> {
val lightBlueHolder = holder as LightBlueTypeViewHolder
lightBlueHolder.bind(cardlist)
// MULTI_VIEWTYPE2 클릭 이벤트 처리
holder.itemView.setOnClickListener {
onClick(cardlist)
}
}
MultiViewEnum.ORANGE.viewType -> {
val orangeHolder = holder as OrangeTypeViewHolder
orangeHolder.bind(cardlist)
// MULTI_VIEWTYPE3 클릭 이벤트 처리
holder.itemView.setOnClickListener {
onClick(cardlist)
}
}
}
}
class BlueTypeViewHolder(private val binding : RecyclerviewItem1Binding) : RecyclerView.ViewHolder(binding.root){
fun bind(card : CardData){
with(binding){
AndersonTv.text = card.name
debitCardTv.text = card.cardName
cardNumberTv.text = card.number
cardDateTv.text = card.date
cardPriceTv.text = "$" + DecimalFormat("#,##0.00").format(card.price).toString()
}
}
}
class LightBlueTypeViewHolder(private val binding : RecyclerviewItem2Binding) : RecyclerView.ViewHolder(binding.root){
fun bind(card : CardData){
with(binding){
AndersonTv2.text = card.name
debitCardTv2.text = card.cardName
cardNumberTv2.text = card.number
cardDateTv2.text = card.date
cardPriceTv2.text = "$" + DecimalFormat("#,##0.00").format(card.price).toString()
}
}
}
class OrangeTypeViewHolder(private val binding : RecyclerviewItem3Binding) : RecyclerView.ViewHolder(binding.root){
fun bind(card : CardData){
with(binding){
AndersonTv3.text = card.name
debitCardTv3.text = card.cardName
cardNumberTv3.text = card.number
cardDateTv3.text = card.date
cardPriceTv3.text = "$" + DecimalFormat("#,##0.00").format(card.price).toString() // 확장함숧 뽑아보기
}
}
}
}
CardViewAdpater.kt
# ViewModel
위에까지는 저번에 작성했던것과 완전히 똑같다
저번 코드와 가장 큰 차이가 ViewModel을 사용했다는 것인데, 원래는 DataSoure로 만들어서 바로 가져왔다면 이걸 ViewModel로 만들어서 적용해준다.
일단 viewModel 사용하려면 build.gradle에 의존성 등록해줘야한다
// viewModel 추가!!
implementation("androidx.activity:activity-ktx:1.8.2")
build.gradle
class CardViewModel(dataSource: DataSource) : ViewModel() {
// data list observing을 사용하는 방법! (LiveData 사용안하고)
// dataSource.getCardList() 가져오기
val cardData = dataSource.getCardList()
fun getCardModel(cardData: CardData) : CardData{
return cardData
}
}
class CardViewModelFactory : ViewModelProvider.Factory{
// 어떤 타입의 뷰모델이 와도 가능하게끔 (데이터타입도 어떤 데이터타입이든지 가능하도록)
override fun <T : ViewModel> create(modelClass: Class<T>): T {
// modelClass(CardViewModel)가 유효한지 검사 (값이 있는지 체크)
// 즉, CardViewModel::class.java가 modelClass와 동일한 클래스인지를 검사
if (modelClass.isAssignableFrom(CardViewModel::class.java)){
// 값이 있다면, CardViewModel객체를 생성 / 이때, CardViewModel은 dataSource를 인자로 받아 초기화된다
// CardViewModel을 제네릭T타입으로 바꿔주겠다
return CardViewModel(dataSource = DataSource.getDataSoures()) as T
}
throw IllegalAccessException("Unknown ViewModel Class")
}
}
CardViewModel.Kt
# MainActivity
MainActivity에서 아까 만들어준 ViewModel을 선언해서 가져와주고,
원래 DataSource를 사용해서 가져왔던 부분을 ViewModel을 사용해서 가져오도록 수정해준다
그리고 Intent로 이동할때 Intent부분을 확장함수로 따로 빼서 사용할 수 있도록 수정해줬다
class MainActivity : AppCompatActivity() {
private val binding : ActivityMainBinding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}
// onClick메소드가 실행되면 람다식이 바로 실행되도록
private val cardAdapter : CardViewAdpater by lazy {
CardViewAdpater(cardList = ArrayList<CardData>()) { card ->
// DetailActivity로 이동하는 함수 실행
adapterClick(card)
}
}
// ViewModel 선언
private val cardViewModel by viewModels<CardViewModel>{
CardViewModelFactory()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
// DataSource를 통해 가져왔던부분을 ViewModel을 사용해서 받아와줌
val cardLists = cardViewModel.cardData // val dataSource = DataSource.getDataSoures().getCardList()
cardAdapter.cardList = cardLists // cardAdapter.cardList = dataSource
with(binding.recyclerview){
adapter = cardAdapter // 리사이클러뷰와 어뎁터 연결
layoutManager = LinearLayoutManager(this@MainActivity)
}
binding.priceTv.text = "$ ${DecimalFormat("#,##0.00").format(285856.20)}"
}
private fun adapterClick(card : CardData){
// Intent부분을 확장함수로 빼서 DetailActivity로 이동하도록 구현
launchActivity<DetailActivity>(
DetailActivity.EXTRA_CARD to card
)
}
}
MainActivtiy.kt
# Intent 확장함수로 빼기
Intent부분과 데이터 전달하는 부분까지 확장함수로 뽑아서 MainActivity에서 더 편리하게 사용할 수 있도록해주었다
// Intent부분 확장함수로 뽑기
inline fun <reified T : Any> newIntent(context: Context) : Intent =
// Intent(this@MainActivity, DetailActivity::class.java) 에 해당하는 부분
Intent(context, T::class.java)
// 데이터 bundle로 전달하고, startActivity하는 부분도 확장함수로 뽑기
inline fun <reified T : Any> Context.launchActivity(
// key값은 String , value값은 어떤타입이든 올수있도록 지정
// vararg(가변인자)로 설정하면 인자를 여러개 넣을 수 있음
vararg pair: Pair<String, Any>){
val intent = newIntent<T>(this)
intent.putExtras(bundleOf(*pair))
startActivity(intent)
}
Intent.kt
# DetailActivity
DetailActivity에서도 ViewModel을 선언해서 가져와주고, 그 ViewModel을 사용해서 데이터를 받아와준다
class DetailActivity : AppCompatActivity() {
private val binding: ActivityDetailBinding by lazy {
ActivityDetailBinding.inflate(layoutInflater)
}
// 어디서나 이 키값을 사용할수있게끔 지정!
companion object{
const val EXTRA_CARD : String = "extra_card"
}
// ViewModel 선언
private val cardViewModel by viewModels<CardViewModel>{
CardViewModelFactory()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
// viewModel 사용해서 데이터 받아옴
val cardItem = intent?.getParcelableExtra<CardData>(EXTRA_CARD) //데이터 받아오기
val cardData = cardItem?.let { cardViewModel.getCardModel(it) }
with(binding) {
detailCardnameTv.text = "이름: ${cardData?.name}"
detailCardnunberTv.text = "카드번호: ${cardData?.number}"
detailDateTv.text = "유효기간: ${cardData?.date}"
detailPriceTv.text = "가격: $${DecimalFormat("#,##0.00").format(cardData?.price)}"
}
}
}
DetailActivity.kt
'Android > Android 핵심기술' 카테고리의 다른 글
[Android/Kotlin] Room DB 활용 예제 (0) | 2024.05.01 |
---|---|
[Android/Kotlin] SharedPreferences 사용해서 데이터 저장하기 (0) | 2024.04.30 |
[Android/Kotlin] Multi View Type 리사이클러뷰 구현하기 (0) | 2024.04.14 |
[Android/Kotlin] Text에 밑줄 표시하기 (0) | 2024.04.14 |
[Android/Kotlin] 뒤로가기 버튼 클릭이벤트 구현 - OnBackPressedCallback() 사용 (0) | 2024.04.12 |