마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

안드로이드 코틀린 이미지 intent 질문합니다.

0 추천

안녕하세요 프래그먼트에서 리사이클러뷰를 사용중인데

리사이클러뷰 데이터는 리스트 형식으로 하고있습니다. 리사이클러뷰 아이템 클릭 시

아이템 클릭 화면으로 이동하는데 텍스트들은 putExtra 사용해서 잘 나오는데

이미지를 어떻게 해야할지 모르겠습니다 ㅠㅠ 

리스트 내의 이미지를 아이템 클릭시 화면에 연결하고 싶습니다.

코드는 아래와 같습니다.

Adapter -> onBindViewHolder


itemList


jjh1530 (120 포인트) 님이 2022년 1월 22일 질문

1개의 답변

0 추천

질문하신 내용은 많은 초보분들이 지속적으로 하는 내용입니다. 먼저 이해하셔야 할 것은 서버나 데이터베이스같은 데이터소스에서 가져와야 하는 데이터와 화면에 보여줘야 하는 데이터의 형태가 다른 경우가 거의 대부분이라는 점을 염두에 두셔야 합니다. 따라서 다른 화면과의 커뮤니케이션은 이 데이터소스에 있는 데이터나 키값을 가지고 하도록 설계해야 님이 겪고 있는 문제점이 생기지 않습니다. 

다른 한가지는 어댑터나 뷰홀더는 데이터를 보여주는 역할이 전부입니다. 여기에서 발생하는 이벤트, 아이템 클릭 같은 것들은 지금 님이 하신 것처럼 뷰홀더 내부에서 처리하시면 좋지 않습니다. 한가지 주된이유는 이 어댑터는 해당 화면에서만 사용이 가능하고 다른 화면에서는 사용할 수가 없습니다. 마치 텍스트뷰가 한 화면에서만 사용할 수 있는 것과 마찬가지가 됩니다. 따라서 이런 종속적인 코드는 밖으로 빼내는 것이 좋습니다. 그게 ItemClickListener 같은 인터페이스의 역할입니다.

해결방법은 어댑터에 아이템클릭에 대한 이벤트를 사용하는 겁니다.

먼저, 님이 어댑터의 아이템 클래스로 사용하고 계신 클래스를  Bar라고 칭하겠습니다. Bar 클래스를 Serializable 이나 Kotlin Parcelize 플러그인을 이용해서 Parcelable로 만드세요. 이건 추후에 화면 간에 데이터를 Bar 인스턴스로 넘기기 위한 겁니다.
이 부분도 초보 분들이 대부분 모르시는 내용인데, 안드로이드에서는 클래스 타입의 객체를 넘길 때는 Serializable이나 Parcelable이라는 인터페이스 타입을 사용합니다. 오브젝트를 압축해서 효율적으로 사용하기 위한 것이라고 보시면 됩니다. 

class Bar : Serializable

@Parclelize
class Bar: Parcelable

 

class BarAdapter (
    private val onBarClicked: (Bar) -> Unit
): RecyclerView.Adapter<BarAdapter.CustomerViewHolder> {

   override fun onCreateViewHolder(....): BarAdapter.CustomerViewHolder {
      val itemView = ...
      return CustomerViewHolder(itemView, onBarClicked)
   }
    
    override fun onBindViewHoler(holder: BarAdapter.CustomerViewHolder, position: Int) {
          val bar = barList[position]
          holder.bind(bar);
    }

    class CustomerViewHolder(
         itemView: View, 
         private val onBarClicked: (Bar) -> Unit) : RecyclerView.ViewHolder(itemView) {
          // 기존에 onBindViewHolder에 있던 코드
          fun bind(bar: Bar) {
              barnum.text = bar.num
              barPicture.setImageResource(...)
              barName.text(...)
              barInfo.text(...)
              itemView.setOnClickListener { onBarClicked(bar) }
          }
    }
}

  간단히 부연설명을 하면,

class BarAdapter (
    private val onBarClicked: (Bar) -> Unit
): RecyclerView.Adapter<BarAdapter.CustomerViewHolder> {

 

BarAdapter에 onBarClicked라는 lambda 함수를 넘김니다. 코틀린 사용하고 계시므로, 람다함수는 아주 자주 쓰이기 때문에, 기본적으로 아시리라 생각합니다. 모르시면 공부를 하시길 권장합니다. 람다는 함수를 타입으로 만든 겁니다. 즉 Bar타입을 파라미터로 받아서 Unit을 리턴하는 함수라는 타입을 동적으로 선언한 겁니다. 이건 내부적으로 자바의 Function 인터페이스를 생성합니다. 

class CustomerViewHolder(
         itemView: View, 
         private val onBarClicked: (Bar) -> Unit) : RecyclerView.ViewHolder(itemView)

 

onBarClicked 람다를 onCreateViewHolder에서 넘겨줍니다. 님의 경우는 어댑터와 뷰홀더가 한 클래스에 있기 때문에 어댑터에 있는 onBarClicked를 직접 가져다 쓰셔도 될 것 같은데, 개인적으로는 선호하는 스타일이 아니라서 생성자에 넘겨주었습니다.

fun bind(bar: Bar) {
              barnum.text = bar.num
              barPicture.setImageResource(...)
              barName.text(...)
              barInfo.text(...)
              itemView.setOnClickListener { onBarClicked(bar) }
          }

어댑터에서 뷰홀더에 접근해서 바인딩을 하지말고 뷰홀더 내에서 하는 것이 더 좋은 접근방법입니다. 이유는 데이터를 바인딩하는 로직은 뷰홀더가 알아야하는 것이지, 어댑터가 알 필요가 없기 때문에 분리하는 것입니다. 자기의 역할이 아닌 것은 해당 역할을 하는 곳에 위치하는 것이 좋습니다. 이런 패턴을 Separation of Concern(관심사의 분리?)라고 부릅니다.

어댑터를 사용하는 곳에서는 onBarClicked를 넘겨주고 onBarClicked를 호출받으면 액티비티를 띄우면 됩니다.

class BarActivity : AppCompatActivity() {

     private val barAdapter by lazy { 
          BarAdapter { bar -> barClicked(bar) } 
     }

     override fun onCreate(...)  {
        super.onCreate(...)
        setContentView(...)

        setupRecyclerView()
     }
    
     private fun setupRecyclerView() {
          recyclerView.adapter = barAdapter
     }

     private fun barClicked(bar: Bar) {
          val intent = BarItemActivity.barItemIntent(bar)
          startActivity(intent)
     }
}


// 제가 볼 때는 이 클래스는 Activity이지 Item이 아니므로, BarActivityItem보다는 BarItemActivity가  더 적합한 이름입니다.
class BarItemActivity: AppCompatActivity() {
 
    companion object 
           private val BAR_ITEM = "키값으로 아무 문자열이나 사용해도 됩니다"

           fun barItemIntent(bar: Bar, newTask: Boolean = false): Intent {
                // apply: Kotlin scope function. 모르시면 꼭 공부하시길 추천 함.
                Intent(this, BarItemActivity::class.java).apply {
                      // 저는 Serializable 을 사용할게요. Parcelable도 별반 다르지 않습니다.
                     intent.putSerializable(BAR_ITEM, bar)
                     if (newTask) {
                         // Service, BroadcastReceiver 같은데서 액티비티를 띄울 때 필요한 Flag
                         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                     }
                } 
           }
     }

    // Kotlin Lazy Property :apply만큼 너무 자주 쓰이는 기본적인 코틀린 프로퍼티 읽는 방식
    private val bar : Bar by lazy { intent.getSerializableExtra(BAR_ITEM) as Bar }

     override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          ...

         
         bindBar()
     }

    private fun bindBar() {
        barnum.text = bar.num
        barPicture.setImageResource(...)
        barName.text(...)
        barInfo.text(...) 
    }
}

넘겨주는 쪽에서 Serializable을 넘겨주고 받는 쪽에서도 Serializable로 넘겨주면 됩니다. Parcelable과 Serializable 의 차이점은 Serializable 은 자바에서 지원하는 범용적인 타입이고 Parcelable 은 안드로이드 전용 타입인데, 복잡한 오브젝트의 경우는 Parcelable 이 퍼포먼스가 더 좋습니다. 위의 경우는 Serializable도 별반 차이가 없으므로 Serializable을 사용했습니다.

spark (227,470 포인트) 님이 2022년 1월 23일 답변
spark님이 2022년 1월 23일 수정
...