티스토리 뷰

Android App Coding

탭레이아웃 2편 TabLayout

IT Knowledge Share 2021. 7. 9. 14:22
반응형

이전에 작성한 탭레이아웃은 프래그먼트를 이용해서 만들었던 탭레이아웃입니다. 뷰를 프래그먼트로 이용해서 그려줄 경우, 탭의 갯수가 많은 경우 그만큼 리소스를 많이 잡아먹고 관리포인트가 많아집니다.

 

프래그먼트를 사용하지 않고 탭레이아웃을 코딩하는 법을 알아봅니다.

 

먼저 액티비티 파일을 생성합니다. 생성된 액티비티 파일에 우선 어댑터를 만들어줍니다. NonFragPageAdapter 클래스에서 받는 변수는 layoutInflater이고, 이 클래스는 PagerAdapter( )를 상속 받습니다.

class NonFragPageAdpater(
    val layoutInflater: LayoutInflater
) : PagerAdapter() { }

 

메소드 오버라이딩을 위해 implement 해주면, getCount, isViewFromObject 이렇게 두 개의 메소드만 구현하라고 자동으로 나오게 됩니다. 나머지 instantiateItem 및 destroyItem 메소드는 직접 찾아서 override로 가져옵니다. 2가지를 추가로 더 가져오는 이유는 프래그먼트 없이 탭레이아웃 기능을 잘 수행해내기 위해서 입니다.

 

instantiateItem은 실제적으로 뷰를 그리는 역할을 합니다. when 조건절을 사용해서, 0 ~ 2, else 경우에 따라 view를 리턴하도록 작성합니다. container.addView(view) 처럼, 컨테이너에 뷰를 붙여주는 방식으로 탭이 보이도록 합니다. 리턴되는 뷰는 나중에 isViewFromObject 콜백에서 동일한지 그 여부를 검사 받습니다.

 

반응형
    override fun instantiateItem(container: ViewGroup, position: Int): Any {
        when (position) {
            0 -> {
                val view = layoutInflater.inflate(R.layout.fragment_one, container, false)
                container.addView(view)
                return view
            }
            1 -> {
                val view = layoutInflater.inflate(R.layout.fragment_two, container, false)
                container.addView(view)
                return view
            }
            2 -> {
                val view = layoutInflater.inflate(R.layout.fragment_three, container, false)
                container.addView(view)
                return view
            }
            else -> {
                val view = layoutInflater.inflate(R.layout.fragment_one, container, false)
                container.addView(view)
                return view
            }
        }
    }

 

destroyItem은 탭 클릭 시 페이지가 넘겨지면서 뷰가 가려질 때, 어댑터에서 내부적으로 뷰를 없애야 하는 경우 호출되는 콜백 함수입니다. 이 콜백 함수가 호출되면, 컨테이너에서 뷰를 떼어줘야 하므로, removeView를 사용합니다. removeView 함수는 인자로 View 타입을 받기 때문에, container.removeView(`object` as View) 처럼 `object`의 타입이 Any이므로, 이를 View로 캐스팅한 후, removeView 메소드로 떼어냅니다.

 override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
        container.removeView(`object` as View)
    }

 

getCount는 페이저의 갯수를 리턴해주면 됩니다. 보여지는 페이지 뷰는 3개이므로 return 3을 하도록 합니다.

 override fun getCount(): Int {
        return 3
    }

 

isViewFromObject는 뷰페이저 내부적으로 관리되는 키 객체(key object)와 페이지뷰가 연관되는지 여부를 확인하는 역할을 수행합니다. 일반적으로 instantiateItem( )에서 페이지뷰의 View 객체를 리턴하고, isViewFromObject는 리턴된 View와 Object가 동일한지 여부를 검사하여, 그 결과를 리턴하도록 구현합니다. 해당 부분은 return view === `object` as View로 작성하여 Boolean 값을 리턴합니다.

 override fun isViewFromObject(view: View, `object`: Any): Boolean {
        return view === `object` as View
    }

 

[ Tip 1: 'a == b'는 단지 a와 b의 값이 같다는 것만을 의미할 뿐, 램의 주소값까지는 같지 않을 수 있습니다. 비교 대상의 값과 주소값까지 같은지 비교하려면 a === b로 작성해야 합니다. ]

[ Tip 2: `object` 이렇게 백틱(` `) 사이에 object가 선언된 이유는 자바와 다르게 코틀린에서 object가 키워드로 사용되지 때문입니다. ]

 

이렇게 어댑터를 완성합니다. 전체적인 코드는 다음과 같습니다.

* TabActivity2.kt

class NonFragPageAdpater(
    val layoutInflater: LayoutInflater
) : PagerAdapter() {

    override fun instantiateItem(container: ViewGroup, position: Int): Any {
        when (position) {
            0 -> {
                val view = layoutInflater.inflate(R.layout.fragment_one, container, false)
                container.addView(view)
                return view
            }
            1 -> {
                val view = layoutInflater.inflate(R.layout.fragment_two, container, false)
                container.addView(view)
                return view
            }
            2 -> {
                val view = layoutInflater.inflate(R.layout.fragment_three, container, false)
                container.addView(view)
                return view
            }
            else -> {
                val view = layoutInflater.inflate(R.layout.fragment_one, container, false)
                container.addView(view)
                return view
            }
        }
    }

    override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
        container.removeView(`object` as View)
    }

    override fun getCount(): Int {
        return 3
    }

    override fun isViewFromObject(view: View, `object`: Any): Boolean {
        return view === `object` as View
    }
}

어댑터가 완성되면, 이후에는 onCreate 부분에 탭을 더해주고, 페이저와 연결시켜주는 작업을 하면 됩니다. 

기존에 어댑터에서 프래그먼트로 리스트뷰를 보여줬던 쪽에서 작성했던 코드처럼, 똑같이 작성해줍니다.

class TabActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_tab)
      
        //탭 만들기
        tab_layout.addTab(tab_layout.newTab().setText("Number1"))
        tab_layout.addTab(tab_layout.newTab().setText("Number2"))
        tab_layout.addTab(tab_layout.newTab().setText("Number3"))
        
        //어댑터 만들기
        val adapter = NonFragAdpater(LayoutInflater.from(this@TabActivity2))
        view_pager.adapter = adapter
        
        //탭과 페이저 연결
        view_pager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tab_layout))
        
        //탭 클릭시마다 페이저가 변하도록 구현  
        tab_layout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            override fun onTabReselected(tab: TabLayout.Tab?) {
            }

            override fun onTabUnselected(tab: TabLayout.Tab?) {
            }

            override fun onTabSelected(tab: TabLayout.Tab?) {
                view_pager.setCurrentItem(tab!!.position)
            }
        })
    }
}

이렇게 탭레이아웃에 관한 설명은 여기서 마치겠습니다.

반응형
댓글