5.1 ViewPager에서 프래그먼트 사용하기
ViewPager와 Adapter
뷰 페이저는 리사이클러뷰와 구현 방식이 비슷한데 한 화면에 하나의 아이템만 보여지는 리사이클러뷰라고 생각하면 됩니다. 페이저어댑터를 통해서 뷰페이저에서 보여질 화면들을 연결하는 구조도 리사이클러뷰와 동일합니다.
1. 뷰페이저 어댑터 생성 합니다.
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
2. 뷰페이저어댑터를 만듭니다.(뷰페이저에 프래그먼트를 보여주기 위해서) FragmentAdapter 클래스를 생성한 후에 FragmentPagerAdapter를 상속받습니다.
class FragmentAdapter : FragmentPagerAdapter {
}
3. FragmentPagerAdapter 뒤에서 [Alt+Enter] 클릭 한 후에 파라미터가 2개인 생성자로 선택하고 클래스 내부에 [ctrl+i]로 2개의 메소드를 구현합니다.
class FragmentAdapter(fm: FragmentManager, behavior: Int) : FragmentPagerAdapter(fm, behavior) {
override fun getItem(position: Int): Fragment {
TODO("Not yet implemented")
}
override fun getCount(): Int {
TODO("Not yet implemented")
}
}
FragmentPagerAdapter의 필수 메서드 getItem(): 현재 페이지의 position이 파라미터로 넘어옵니다. position에 해당하는 위치의 프래그먼트를 반환해야 합니다. getCount(): 어댑터가 화면에 보여줄 전체 프래그먼트의 개수를 반환해야 합니다. |
4. fragmentList 변수를 생성하고, 2개의 메서드를 구현합니다.
class FragmentAdapter(fm: FragmentManager, behavior: Int) : FragmentPagerAdapter(fm, behavior) {
var fragmentList = listOf<Fragment>()
override fun getItem(position: Int): Fragment {
return fragmentList.get(position)
}
override fun getCount(): Int {
return fragmentList.size
}
}
메인 액티비티에서 연결하기
1. MainActivity.kt에서 프래그먼트 목록을 생성하는 코드를 추가합니다.
2. adapter를 생성하고 viewPager에 adapter를 적용합니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 프래그먼트 목록 리스트 변수 생성
val fragmentList = listOf(FragmentA(), FragmentB(), FragmentC(), FragmentD())
// 어댑터 생성 및 프래그먼트 목록 변수를 적용
val adapter = FragmentAdapter(supportFragmentManager, 1)
adapter.fragmentList = fragmentList
// 뷰페이저 어댑터에 프래그먼트 어댑터 적용
var viewPager : ViewPager = findViewById<ViewPager>(R.id.viewPager)
viewPager.adapter = adapter
}
}
TabLayout 적용하기
1. activity_main.xml의 디자인 편집창을 열고 tabLayout을 추가합니다.
2. FragmentAdapter.kt를 열고 getPageTitle()메서드를 오버라이드 하여 코드를 작성합니다.
class FragmentAdapter(fm: FragmentManager, behavior: Int) : FragmentPagerAdapter(fm, behavior) {
var fragmentList = listOf<Fragment>()
override fun getItem(position: Int): Fragment {
return fragmentList.get(position)
}
override fun getCount(): Int {
return fragmentList.size
}
override fun getPageTitle(position: Int): CharSequence? {
return when (position) {
0 -> "A"
1 -> "B"
2 -> "C"
else -> "D"
}
}
}
5.2 View를 사용하는 뷰페이저 만들기
앞에서 프래그먼트를 사용해 뷰페이저를 구현했는데 이 방식은 각 프래그먼트의 생명주기를 따로 관리해야해서 번거롭습니다. 단순하게 화면에 텍스트나 이미지를 표시하는 용도를 넘어 실시간으로 데이터를 주고받고 갱신하거나 메뉴 간 이동이 잦으면 생명 주기 관리 문제로 앱이 종료됩니다.
이런 문제로 인해 프래그먼트 대신에 레이아웃을 인플레이트해서 사용합니다. 동일한 조건이라면 프래그먼트를 사용할 때보다 뷰가 시스템 자원(메모리, cpu, 등)을 덜 사용하기 때문에 더 효율적일 수 있습니다.
1. 4개의 레이아웃을 생성합니다.
2. 4개의 레이아웃 파일을 인프레이트해서 사용할 커스텀뷰 클래스를 생성합니다. 레이아웃 파일을 뷰로 만들어서 사용할 계획이므로 뷰를 삽입할 수 있는 레이아웃 클래스 중에 하나를 상속받아서 만듭니다. 그리고 layout_a.xml파일을 인플레이트해서 view변수에 담은 후 자기 자신인 CustomA에 addView 해줍니다. customA 클래스는 레이아웃을 상속받았으므로 정렬, 패딩과 같은 옵션을 추가할 수 있습니다. 다음과 동일하기 customB, customC, customD 클래스 파일도 생성합니다.
class customA(context: Context?) : LinearLayout(context) {
var view = LayoutInflater.from(context).inflate(R.layout.layout_a,
this, false)
}
[Ctrl + i], [Ctrl + o]키의 차이 [Ctrl + i] (implement) : 메서드명만 있는 인터페이스가 설계되어 있습니다. 메서드 내부에 코드를 작성해두면 부모 클래스에 작성되어 있는 코드에서 우리가 작성한 인터페이스 메서드를 호출해서 사용합니다. 인터페이스는 구현하지 않으면 컴파일되지 않습니다. [Ctrl + o] (Override) : 부모 클래스에 이미 만들어져 있는 메서드를 내 코드에 맞게 재정의하는 것입니다. 구현하지 않아도 컴파일되며, 부모 클래스에 있는 메서드가 호출되고 실행됩니다. |
CustomPagerAdapter 만들기
1. 생성한 뷰를 사용하는 커스텀 어댑터를 생성합니다. 프래그먼트를 사용할 때와는 다른 PagerAdapter를 상속받아서 사용합니다. 해당 CustomPagerAdapter 클래스 파일을 생성하고 instantiateItem(), destroyItem() 메서드를 추가합니다.
class CustomPagerAdapter : PagerAdapter() {
override fun isViewFromObject(view: View, `object`: Any): Boolean {
}
override fun getCount(): Int {
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
return super.instantiateItem(container, position)
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
super.destroyItem(container, position, `object`)
}
}
2. getCount() 메서드(뷰 리스트의 개수 return), instantiateItem(container, position): Any, destroyItem(container, position, object) 메서드를 작성합니다.
override fun getCount(): Int {
return views.size
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
// 현재 페이지에 맞는 view를 꺼내서 view 변수에 저장
var view = views.get(position)
// 뷰 페이저(container)에 해당 view를 추가
container.addView(view)
// 사용 view를 return합니다. 이유는 어댑터가 생성된 view를 가지고 있다가
// 필요 없을경우 삭제 등의 추가적인 관리를 위해 사용하므로 해당 view를 리턴합니다.
return view
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
// 마지막 파라미터(object)로 우리가 instantiateItem() 메서드에서 return 하는 뷰가 전달됩니다.
container.removeView(`object` as View?)
}
destroyItem 메서드의 역할 뷰페이저는 기본적으로 한 번에 3개의 페이지를 생성합니다. 예를 들어 A, B, C 페이지가 있는데 B 페이지를 호출하면 빠른 화면처리를 위해서 앞뒤의 A와 C 페이지도 미리 생성해둡니다. 그리고 3개의 페이지에 속하지 않는 페이지는 삭제를 해서 메모리 효율을 높이는데 그 역할을 destroyItem() 메서드가 합니다. |
3. isViewFromObject() 메서드는 instantiateItem() 메서드에서 생성된 오브젝트를 사용할지 여부를 판단합니다.
override fun isViewFromObject(view: View, `object`: Any): Boolean {
// instantiateItem() 메서드에서 생성된 object의 view 타입 체크
return view == `object` as View?
}
4. getPageTitle() 메서드를 오버라이드하고, 메서드 안에 메뉴명을 반환하는 코드를 작성합니다.
override fun getPageTitle(position: Int): CharSequence? {
return when(position) {
0-> "A"
1-> "B"
2-> "C"
else-> "D"
}
}
5. 전체코드 모습
class CustomPagerAdapter : PagerAdapter() {
// 뷰 목록 변수 생성
var views = listOf<View>()
override fun isViewFromObject(view: View, `object`: Any): Boolean {
// instantiateItem() 메서드에서 생성된 object의 view 타입 체크
return view == `object` as View?
}
override fun getCount(): Int {
return views.size
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
// 현재 페이지에 맞는 view를 꺼내서 view 변수에 저장
var view = views.get(position)
// 뷰 페이저(container)에 해당 view를 추가
container.addView(view)
// 사용 view를 return합니다. 이유는 어댑터가 생성된 view를 가지고 있다가
// 필요 없을경우 삭제 등의 추가적인 관리를 위해 사용하므로 해당 view를 리턴합니다.
return view
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
// 마지막 파라미터(object)로 우리가 instantiateItem() 메서드에서 return 하는 뷰가 전달됩니다.
container.removeView(`object` as View?)
}
override fun getPageTitle(position: Int): CharSequence? {
return when(position) {
0-> "A"
1-> "B"
2-> "C"
else-> "D"
}
}
}
레이아웃 파일에 viewPager와 TabLayout 추가하고 소스코드 연결하기
1. viewPager, tabLayout 레이아웃을 activity_main.xml파일에 추가합니다.
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.tabs.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Monday" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tuesday" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Wednesday" />
</com.google.android.material.tabs.TabLayout>
</androidx.viewpager.widget.ViewPager>
2. MainActivity.kt에 뷰페이저에서 사용할 뷰 클래스를 모두 생성해서 views 변수에 저장합니다.
val views : List<View> = listOf(customA(this), customB(this),
customC(this), customD(this))
3. 커스텀 어댑터를 생성하고 뷰 클래스 목록을 어댑터에 저장합니다. 그리고 viewPager에 adapter를 설정합니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 커스텀뷰 목록 변수 생성
val views : List<View> = listOf(customA(this), customB(this),
customC(this), customD(this))
// 커스텀 어댑터 생성 및 adapter의 뷰 클래스 목록에 커스텀뷰 목록 변수를 저장
val adapter: CustomPagerAdapter = CustomPagerAdapter()
adapter.views = views
// viewPager에 adapter를 연결
var viewPager : ViewPager = findViewById<ViewPager>(R.id.viewPager)
viewPager.adapter = adapter
}
}
- 미니 퀴즈 5-5 -
1. 뷰페이저와 탭 레이아웃으로 화면을 구성할 때 프래그먼트를 화면 아이템으로 사용한다면 어떤 어댑터를 사용할 수 있나요?
답 : FragmentPagerAdapter(FragmentManager) 해당 메서드 또는 이것을 상속받는 커스텀 어댑터
2. 뷰페이저와 탭 레이아웃을 연결할 때 메뉴명을 생성해주는 메서드의 이름은 무엇인가요?
답 : getPageTitle(position: Int): CharSequence?
3. 페이저어댑터에서 뷰를 생성하는 메서드는 무엇인가요?
답 : instantialItem(container: ViewGroup, position: Int): Any
'책 요약하기 > 이것이 안드로이드다' 카테고리의 다른 글
#6. 권한 2021-03-18 (0) | 2021.03.18 |
---|---|
#5-3. 프래그먼트 2021-03-12 (0) | 2021.03.12 |
#5-2. 컨테이너 2021-03-12 (0) | 2021.03.12 |
#5-1. 화면 구성하기 2021-03-12 (0) | 2021.03.12 |
#4. 위젯과 리소스 다루기 2021-03-06 (0) | 2021.03.06 |