Unit 3: Navigation (8)

quokka·2022년 7월 8일
0

Android Basics in Kotlin

목록 보기
17/25

적응형 레이아웃

안드로이드 기기는 다양한 모양과 크기, 폼 팩터(하드웨어의 규격)로 출시된다. 앱에서 다양한 화면을 지원하면 더 많은 사용자가 다양한 기기에서 앱을 사용하도록 할 수 있다.

앱은 레이아웃이 유연해야 한다. 가로세로 비율과 길이를 고정하는 것이 아니라 다양한 화면에서 크기와 방향에 적절히 맞출 수 있는 레이아웃이어야 한다.

Sports App

이번 코드랩에서 사용할 스타터 코드는 여기에서 받을 수 있다.

앱 초기 상태는 다음과 같다.

안드로이드 스튜디오 device manager에서 태블릿 에뮬레이터를 만들고 앱을 실행하면 화면이 다음과 같이 뜬다. 태블릿의 넓은 화면을 효율적으로 사용하려면 이렇게 목록만 띄우는 것 보다는 목록과 세부정보를 한 화면에 표시하는 것이 좋다.
이번 코드랩을 통해 SlidingPanLayout 패턴으로 목록-세부정보 패턴을 사용해보자.

라이브러리 종속 항목

build.gradle (Module)의 dependencies 영역에 추가한다.

implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0"

SlidingPaneLayout / FragmentContainerView

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>
  • FrameLayout은 SlidingPaneLayout으로 변경한다.
  • 세부정보 화면이 될 FragmentContainerView을 추가한다.
  • name 속성에 NewsDetailsFragment를 할당한다.
  • layout_weight 값을 "1"로 두어 RecyclerView가 차지하고 남는 오른쪽 영역 너비만큼 확장되도록 한다.

openPane()

현재 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
    }
}
  • SportsListFragment 바깥에 SportsListOnBackPressedCallback 클래스를 하나 생성한다.
  • OnBackPressedCallback 클래스를 확장하면 onBackPressed 콜백을 처리할 수 있다.
  • slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen 소형 화면에서 두 번째 창을 슬라이드 하거나 완전히 연 경우에만 콜백을 사용하도록 조건을 설정한다.
  • handleOnBackPressed에 세부정보 창을 닫고 목록 창으로 돌아가도록 설정한다.
  • 세부정보 창을 슬라이드하거나 열거나 닫을 때 호출되는 SlidingPaneLayout.PanelSlideListener의 추상 메서드을 추가한다.
  • init 블록을 만들어서 slidingPaneLayout.addPanelSlideListener()를 호출한다.
  • SportsListOnBackPressedCallback 클래스가 완성되었다면 onViewCreated()에서 onBackPressedDispatcher로 콜백을 등록한다.

0개의 댓글