반응형

어댑터 (구글 검색 결과)

 

어댑터... 

나는 어댑터가 싫지만, Kotlin하는 사람으로 이 어댑터를 뺄래야 뺄 수가 없다.. 

작업하면서 신기방기 도라방스 한 기능을 실제로 만들어봤을 때 그 뿌듯함은 참 보람찬데 하기싫은것 ^^

 

지금까지 Spinner, RecyclerView, ViewPager2 이렇게 어댑터를 사용해봤는데

얼추 장맛은 본 것 같으니, 제대로 댕장꿍 한 번 거하게 끓여서 공부해야 할 것 같은 것

 

아무튼 지금까지 이 어댑터를 이해한 부분을 전부 적어보겠숨..


먼저 Adapter가 어느 포지션인지 ? 

 

쉽게 말해서 layout을 짜다가 저런 view(안이 텅텅 비어있는 것들)안에 채워야할 때! 내가 만든 layoutadapter를 통해서 공장 돌려서 원하는 수만큼 만들어서 화면에 보이게 한다는 거죠.. 


1. 사실 뷰페이저나 스피너나 솔직히 쩌리고, 우리가 가장 많이 사용하는 리싸이클러 뷰만 일단 뿌시다가 가려구요

 

기본 구조 

 

RecyclerView Adapter

class FavoriteRVAdapter(private val fragment: Fragment, private val favorites: MutableList<FavoriteVO>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    
    ...
    inner class favoritesViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        ...
        
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = RvFavoriteItemBinding.inflate(inflater, parent, false)
        return  favoriteViewHolder(binding.root)
    }

    override fun getItemCount(): Int {
        return favorites.size
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    	...    
    }
}

저는 일단 이 클래스의 반환값을 inner class(내가 선언한 특정 이름의 ViewHolder)로 특정 지어서 쓰면 더 불편하더라구요..

 

그래서 저는 RecyclerView.Adpater<RecyclerView.ViewHolder>() 이렇게 반환값을 정합니다. 그냥 다른 RecyclerView의 Adpater도 동일한 형식을 정하기 때문임니다 (그냥 신입일 때 이거라도 확실히 잡고 가려고 이렇게 씀)

암튼 이렇게 이 어댑터 클래스의 반환 형식을 정했으면 이제 Alt + I (대문자) 를 누르면 자동으로 오버라이드 함수가 추가 된다는 점.

그러면 이렇게

나오는데요..

 

자 여기까지 틀이 만들어졌고? 내가 여기다가 쓸 layout을 만들어주고 옵니다. 

저는 이렇게 만들었구요.

실제로 값이 변하면서 들어갈 것들은 저렇게 ID값을 특정 지었습니다. 

-> 당연히 안지어도 상관없긴 함 ㅋㅋ

 

아무튼 이렇게 하면? 

inner class favoriteViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val tvFIDirection: TextView = view.findViewById(R.id.tvFIDirection)
        val tvFIName : TextView = view.findViewById(R.id.tvFIName)
        val clFI : ConstraintLayout = view.findViewById(R.id.clFI)
    }
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = RvFavoriteItemBinding.inflate(inflater, parent, false)
        return  favoriteViewHolder(binding.root)
    }
    
    override fun getItemCount(): Int {
        return favorites.size
    }

onCreateViewHolder에 내가 만든 rv_favorite_item.xml 이라는 파일을 inflate(부풀리기) 할 수 있습니다.

 

inflater라는 변수는 ActivityFragment에서 onCreate, onCreateView 등의 inflate와 동일한 기능을 하는 역할을 비슷하게 수행하는 듯합니다. (공식문서를 참고한게 아니니 아마 세분화 한다면 다를 수도..)

 

inner class안에는 내가 사용할 xml을 view.findViewById를 통해 선언해야 추후에 이 view들의 동작을 구현할 때 사용할 수 있습니당 ! 

 

왜 이렇게 viewHolder가  반한되는 inner class 를 사용하냐.. 도 똑같은 스타일이었습니다. RecyclerView.ViewHolder 타입의 범용성 있는 데이터를 사용해서 쓰려고 했어요

 

getItemCountAdapter에서 매개변수로 받았던 데이터의 갯수를 반환합니다.

다르게 말해서 RecyclerView에 몇개의 목록을 만들건지 이 곳에서 세는 것 .. 

 

같은 데이터 형식으로 다른 layout을 사용할 때.

override 함수를 살펴보면 getItemViewType이 있습니다.

 

이 오버라이드 함수를 사용해서 제가 원하는 양의 layout을 붙일 수 있죠. 2개를 붙인다고 치면

 

저는 처음에 adapter의 매개변수에 xmlname이라는 String값을 받았습니다.

그리고 이 adapter를 사용하는 곳에서 원하는 xmlname을 사용해서 다른 layout을 사용하는 거죠. 

class FavoriteRVApapter(private val fragment: Fragment , ... , private val xmlname: String): RecyclerView.Adpater ... {
...
override fun getItemViewType(position: Int): Int {
        return when (xmlname) {
            "main" -> 0
            "recommend" -> 1
            else -> throw IllegalArgumentException("invalied view type")
        }
    }
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            0 -> {
                val binding = RvExerciseItemBinding.inflate(inflater, parent, false)
                mainViewHolder(binding.root)
            }
            1 -> {
                val binding = RvRecommendPTnItemBinding.inflate(inflater, parent, false)
                recommendViewHolder(binding.root)
            }
            else -> throw IllegalArgumentException("invalid view type binding")
        }
    }

그 다음 대망의 bindViewHolder 부분

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val currentItem = favorites[position]
        sharedPreferencesHelper = SharedPreferencesHelper(fragment.requireContext())

        (holder as favoriteViewHolder).tvFIName.text = currentItem.name
        holder.tvFIDirection.text = currentItem.direction

여기서는 ViewHolder와 어댑터 클래스에서 받아왔던 데이터 리스트의 인덱스?를 넣을 수 있습니다.

마치 for 반복문인거죠 

val currentItem = favorites[position]

 

private val favorites: MutableList<String>

ex)

favorites = mutableListOf<String>()

    favortite.add("대성여고")

    favortite.add("동성고")

    favortite.add("살레시오여고")

    favortite.add("인성고")

currentItem을 통해서 이 각각의 "대성여고", "동성고", "살레시오여고", "인성고"에 접근할 수 있는 거죠 ! 

이렇게 근데 만약 이렇게 position이 index가 2인 항목을

when (position) {
    2 -> {
      holder.tvFIDirection.text = "나는 뚱이다"
    }
}

이렇게 bind를 했을 때 RecyclerView의 3번째 항목의 TextView에만 나는 뚱이다 ⭐️ 라고 적히는 거죠 

 

살레시오여고 -> 나는  뚱이다

 

아 그리고 특성상 onBindViewHolder에서도 내가 선언한 inner class를 지목하는게 아니다보니 

가장 처음 코드를 작성할 때, (holder as FavoriteViewHolder) 라는 부분이 없으면 이 놈들이 내가 ViewHolder라고 적었을 때

ViewHolder인지 모릅니다(당연한 말임 ㅋㅋ)

그래서 if (ViewHolder is FavoriteViewHolder)라고 해야 

holder를 통해 선언해놓은 변수를 가져올 때 정상적으로 가져와집니다.

 

 

 

아무튼 이렇게 간단하게 onBindViewHolder를 살펴봤는데 

 

 


코드를 작성하다보면 Adapter에서 context에 접근하지 못하는게 짜증날 때가 있습니다. 그럴 때를 위해서 

class FavoriteRVAdapter(private val fragment: Fragment, private val favorites: MutableList<FavoriteVO>):

여기를 보시면 Fragment를 매개변수로 받는겁니다 ㅋㅋ 

 

저 같은 문과, 비전공자, 신입 개발자는 이런것도 혼자 깨닫고 하는 과정이 있어야 하기 때문에 뿌듯하며 올려봅니다.. 사실 함수 구조, 클래스 구조를 잘 안다면 당연히 할 일.. 

 

아무튼 fragment를 매개변수로 넣는다면 onBindViewHolder에서 intent를 사용한다거나 supportFragmentManager를 사용한다거나 하는 맥락(context)이 필요할 때 유용하게 사용이 가능합니다.

 

이렇게 간단하게 살펴봤는데 사실 onCreateViewHolder에서 parent나 ViewGroup이 뭘까에 대한 궁금증을 해결하기 위해서 글을 시작했습니다. 당연히 이 주제는 나오지도 않았기 때문에 수정을 통해 보완 하려고 합니다 ^^ 개졸리네 수고

 

 

+ 0919 String이나 Int 로만 이루어진 값들이 Adapter에 들어간다거나 array에 간단한 String, Pair, Int, Triple 같은 형식으로만들어간다면 저는 그냥 StringRecyclerViewAdpater, IntRecyclerViewAdapter로 만드는게 좋더라구요

 

layout만 추가적으로 연결해서 viewType으로만 해당 item연결해서 그냥 써버리기.. 

 


그리고

Viewpager2에 fragment들을 연결할 때. 쓰는 뷰페이저 어댑터

 

정말간단하죠? viewpager2를 연결할 때는 연결할 fragment를 초기화해서 담고, FragmentStateAdapter(fragmentManager, lifecycle)로 연결. 

 

사실 viewpager2는 adapter가 문제가 아니라, layout짜면서 match_parent같은 부분 제대로 안줬다고 에러가 더많이 나기 때문에... 수고

    class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fragmentManager, lifecycle){
    private val fragments = listOf(Example1Fragment(), Example2Fragment())
    override fun getItemCount(): Int {
        return fragments.size
    }

    override fun createFragment(position: Int): Fragment {
        return fragments[position]
    }
}

 

 


스피너 어댑터

@Suppress("UNREACHABLE_CODE")
class SpinnerAdapter(context:Context, resId: Int, private val list: List<String>) : ArrayAdapter<String>(context, resId, list) {

    @SuppressLint("ViewHolder")
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        val binding = ItemSpinnerBinding.inflate(LayoutInflater.from(parent.context), parent, false)

        binding.root.setPadding(0, binding.root.paddingTop, 0, binding.root.paddingBottom)

        binding.tvSpinner.text = list[position]
        return binding.root
    }

    override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
        val binding = ItemSpinnerBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        binding.tvSpinner.text = list[position]
        return binding.root
    }

    override fun getCount(): Int {
        return super.getCount()
        return list.size
    }
}
Spinner는  getView와 getDropDownView가 나눠집니다.

getView는 Spinner에서 dropdown을 열지 않았을 떄

getDropDownView는 dropdown을 열었을 때

스피너는 String말고  시도해본적도 없고 그렇게 자주 쓰이는 view는 아니다 보니 그냥 구글에 널린 코드를 사용하면 된다는 점. 
근데 이 Spinner를 쓸지, AutoCompleteTextView를 쓸지 스타일 차인데요.. UI 기능 구현할 때 개인적으로는 AutoCompleteTextView가 깔끔한데 유지보수는 Spinner가 훨 쉽다는 것.. 클릭했을 때 필터링하는 항목이 나오는 Spannble?한 그 목록까지 원하는 디자인으로 커스텀 하고 싶으면 ACTV를 쓰는게 좋다고 느꼈어요. 

두 개다 했는데 걍 그저 그럼(그렇게 주목받는 기능은 아니니) ㅋㅋ 암튼 더 쓸게 많은데 (일할 때 생각남) 이상하게 생각이 안난다는점 나중에 추가할 예정 ! 

 

반응형