안드로이드 기기는 다양한 모양과 크기, 폼 팩터(하드웨어의 규격)로 출시된다. 앱에서 다양한 화면을 지원하면 더 많은 사용자가 다양한 기기에서 앱을 사용하도록 할 수 있다.
앱은 레이아웃이 유연해야 한다. 가로세로 비율과 길이를 고정하는 것이 아니라 다양한 화면에서 크기와 방향에 적절히 맞출 수 있는 레이아웃이어야 한다.
이번 코드랩에서 사용할 스타터 코드는 여기에서 받을 수 있다.
앱 초기 상태는 다음과 같다.
안드로이드 스튜디오 device manager에서 태블릿 에뮬레이터를 만들고 앱을 실행하면 화면이 다음과 같이 뜬다. 태블릿의 넓은 화면을 효율적으로 사용하려면 이렇게 목록만 띄우는 것 보다는 목록과 세부정보를 한 화면에 표시하는 것이 좋다.
이번 코드랩을 통해 SlidingPanLayout 패턴으로 목록-세부정보 패턴을 사용해보자.
build.gradle (Module)의 dependencies 영역에 추가한다.
implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0"
Framelayout 안에 RecyclerView가 있는 fragment_sports_list.xml에 세부정보 화면을 추가한다.
<androidx.slidingpanelayout.widget.SlidingPaneLayout
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"
android:id="@+id/sliding_pane_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SportsListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="550dp"
android:layout_height="match_parent"
android:clipToPadding="false"
android:padding="8dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/detail_container"
android:name="com.example.android.sports.NewsDetailsFragment"
android:layout_width="300dp"
android:layout_height="match_parent"
android:layout_weight="1">
</androidx.fragment.app.FragmentContainerView>
</androidx.slidingpanelayout.widget.SlidingPaneLayout>
현재 RecyclerView의 항목을 선택하면 SportsNewsFragment로 이동하도록 되어있다. 세부 Fragment로 이동하는 것이 아니라 오른쪽 FragmentContainerView의 내용이 바뀌도록 하려면 navigate() 코드를 openPane()으로 변경한다.
// SportsListFragment.kt 에서
// 아래 코드를 삭제
val action = SportsListFragmentDirections.actionSportsListFragmentToNewsFragment()
this.findNavController().navigate(action)
// 아래 코드를 추가
binding.slidingPaneLayout.openPane()
이제 태블릿 실행 시 목록-세부정보로 표시되고, 스마트폰 실행 시 목록 화면으로 표시된다.
SlidingPaneLayout은 두 창의 너비를 감안해 창을 나란히 표시할지 결정한다. 예를 들어 위에서 작성한 코드처럼 목록 창의 최소 크기가 550dp이고 세부정보 창에 300dp가 필요하다고 하면 SlidingPaneLayout이 최소 850dp의 너비를 사용할 수 있을 때 나란히 표시한다.
너비가 600dp 미만인 기기는 목록만 표시하는 것이 좋고, 너비가 840dp 이산이면 나란히 표시하는 것이 좋다.
태블릿에서는 상세 fragment 페이지로 이동하지 않기 때문에 정상 작동한다. 하지만 너비가 작은 스마트폰 기기에서는 뒤로가기가 정상 작동하지 않는다. 이를 해결하려면 custom back navigation을 구현해서 SlidingPaneLayout에 연결해야 한다.
SportsListFragment에서 주요 코드는 아래와 같다.
class SportsListFragment : Fragment() {
. . .
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
. . .
val slidingPaneLayout = binding.slidingPaneLayout
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
SportsListOnBackPressedCallback(slidingPaneLayout)
)
}
}
class SportsListOnBackPressedCallback(
private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen), SlidingPaneLayout.PanelSlideListener{
init {
slidingPaneLayout.addPanelSlideListener(this)
}
override fun handleOnBackPressed() {
slidingPaneLayout.closePane()
}
override fun onPanelSlide(panel: View, slideOffset: Float) {
}
override fun onPanelOpened(panel: View) {
isEnabled = true
}
override fun onPanelClosed(panel: View) {
isEnabled = false
}
}