안드로이드 개발하면서 화면 구성에 TabLayout을 자주 사용하게 된다. 또한 여러 개의 뷰를 좌우로 넘기면서 확인할 수 있는 ViewPager도 사용한다. Fragment 구성을 하면 자주 쓰는 두 Layout을 서로 연결하는 방법을 알아보도록 하겠다.
방법을 간단히 설명하면, ViewPager의 Listener 중 OnPageChangeListener가 있는데 메서드가 3개 있다.
onPageScrollStateChanged(state: Int)
ViewPager의 스크롤 상태가 변경될 때 호출되는 메서드며, 총 3가지 상태가 있다.
- SCROLL_STATE_IDLE : Indicates that the pager is in an idle, settled state. The current page is fully in view and no animation is in progress.
- SCROLL_STATE_DRAGGING : Indicates that the pager is currently being dragged by the user.
- SCROLL_STATE_SETTLING : Indicates that the pager is in the process of settling to a final position.
onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int)
onPageSelected(position: Int)
먼저 DataBinding을 사용하기 위해 build.gradle(app)에 아래의 코드를 추가해준다.
android {
...
dataBinding {
enabled = true
}
...
}
ViewPager를 사용하기 위해 FragmentStatePagerAdapter를 상속해 PagerAdapter를 생성한다.
Fragment를 다루는 PagerAdapter는 FragmentStatePagerAdapter와 FragmentPagerAdapter가 있는데 두 클래스의 차이점은 FragmentPagerAdapter의 경우 생성된 Fragment를 메모리에 담고 있기 때문에 리소스 사용이 그만큼 늘어나게 되고, FragmentStatePagerAdapter는 사용자에게 보여지는(곧 보여질 Fragment 포함) 몇 개의 상태만 유지하고 나머지 Fragment는 메모리에서 제거한다. 즉 View를 제거하는 것이다. 상황에 맞게 적절한 PagerAdapter를 사용하는 것이 중요하다.
...
class ViewPagerAdapter(fragmentManager: FragmentManager) :
FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getItem(position: Int): Fragment {
return when (position) {
0 -> FragmentA()
1 -> FragmentB()
else -> FragmentC()
}
}
override fun getCount() = 3
}
BindingAdapter 구현하여 바인딩에 필요한 메서드 생성한다.
setTapContents() : TabLayout 구현 및 리스너 생성
setViewPager() : ViewPager에 ViewPagerAdapter 지정 및 리스너 생성
setViewPosition()
TabLayout 및 ViewPager 모두 position 값을 가져올 수 있기 때문에 해당 포지션(사용자가 선택한 위치)을 ViewModel로 전달하여 해당 값으로 TabLayout 및 ViewPager의 포지션을 지정해 줄 수 있다.
...
object BindingAdapter {
...
@BindingAdapter("setTapContents", "setVm")
@JvmStatic
fun setTapContents(tabLayout: TabLayout, items: List<String>?, mainVm: MainViewModel?) {
items?.forEach {
with(tabLayout) {
newTab().let { tab ->
tab.text = it
addTab(tab)
}
addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) {
//Nothing.
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
//Nothing.
}
override fun onTabSelected(tab: TabLayout.Tab?) {
tab?.position?.let { position ->
mainVm?.selectPosition(position)
}
}
})
}
}
}
@BindingAdapter("setFsm", "setVm")
@JvmStatic
fun setViewPager(
viewPager: ViewPager,
fragmentManager: FragmentManager?,
mainVm: MainViewModel?
) {
if (!items.isNullOrEmpty())
viewPager.adapter?.run {
if (this is ViewPagerAdapter) {
//do something.
}
} ?: kotlin.run {
if (fragmentManager != null)
viewPager.adapter = ViewPagerAdapter(fragmentManager, items)
viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
//Nothing.
}
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
//Nothing.
}
override fun onPageSelected(position: Int) {
mainVm?.selectPosition(position)
}
})
}
}
@BindingAdapter("setViewPosition")
@JvmStatic
fun setViewPosition(view: View, position: Int?) {
if (position != null)
when (view) {
is ViewPager -> {
view.currentItem = position
}
is TabLayout -> {
view.run {
getTabAt(position)?.let { tab ->
selectTab(tab)
}
}
}
}
}
...
}
ViewModel을 생성한다.
tabItems : tablayout의 Name
position : 사용자가 선택한 혹은 보여지는 View의 position
...
class MainViewModel : ViewModel() {
val tabItems: LiveData<List<String>> get() = _tabItems
val position: LiveData<Int> get() = _position
private val _tabItems: MutableLiveData<List<String>> = MutableLiveData()
private val _position: MutableLiveData<Int> = MutableLiveData()
companion object {
private val TAB_ITEMS = listOf(/** Tab Name */)
}
init {
_tabItems.postValue(TAB_ITEMS)
}
fun selectPosition(position: Int) {
_position.postValue(position)
}
}
생성한 ViewModel을 Activity에서 바인딩한다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main).run {
mainVm = ViewModelProvider.NewInstanceFactory().create(MainViewModel::class.java)
fragmentManager = supportFragmentManager
lifecycleOwner = this@MainActivity
}
}
}
이제 layout.xml에 바인딩을 하여 마무리해주면 된다.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="mainVm"
type="com.kobbi.project.coronamask.ui.viewmodel.MainViewModel" />
<variable
name="fragmentManager"
type="androidx.fragment.app.FragmentManager" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.activity.MainActivity">
<com.google.android.material.tabs.TabLayout
android:id="@+id/lo_tab"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:setTapContents="@{mainVm.tabItems}"
app:setViewPosition="@{mainVm.position}"
app:setVm="@{mainVm}"
app:tabGravity="center"
app:tabMode="auto" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/lo_tab"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:setFsm="@{fragmentManager}"
app:setPagerCount="@{mainVm.tabItems}"
app:setViewPosition="@{mainVm.position}"
app:setVm="@{mainVm}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
'안드로이드' 카테고리의 다른 글
Android - Xml 파싱(XmlPullParser) (0) | 2020.04.06 |
---|---|
Android - 화면 당겨서 새로고치는 방법(SwipeRefreshLayout 사용 방법) (0) | 2020.04.03 |
안드로이드 크롤링(Crawling)하기 (Jsoup Library 활용) (0) | 2020.03.20 |
안드로이드에서 주소를 좌표로 변환 하기(GeoCoder) (0) | 2020.03.19 |
Android RecyclerView 갱신이 안되는 원인 정리 (0) | 2020.03.17 |