1. 시작하기 전에
Android 기기는 다양한 모양과 크기, 폼 팩터로 출시됩니다. 앱을 설계할 때는 소형 화면 기기에서 대형 화면 기기에 이르기까지 다양한 유형의 기기에서 실행되도록 해야 합니다. 프로덕션 환경에서 사용 가능한 앱을 작성하는 개발자는 Android Wear, Android Auto, Android TV를 지원할 수 있지만 이러한 주제는 이 과정에서 다루지 않습니다. 앱에서 다양한 화면을 지원하면 최대한 많은 사용자가 다양한 기기에서 앱을 사용하도록 할 수 있습니다.
앱은 레이아웃이 유연해야 합니다. 특정 가로세로 비율과 화면 크기를 가정하는 고정 크기로 레이아웃을 정의하는 대신, 다양한 화면 크기와 방향에 적절하게 맞출 수 있는 레이아웃이어야 합니다. 앱 실행 도중 화면 크기와 가로세로 비율이 변경될 수 있는 폴더블 기기에서 앱이 실행되는 경우에도 동일한 원칙이 적용됩니다. 이 Codelab 끝에서 폴더블 기기에 관해 간단히 소개합니다.
기본 요건
- Android 스튜디오에 코드를 다운로드하고 실행하는 방법
- Android 아키텍처 구성요소
ViewModel
및LiveData
숙지 - 탐색 구성요소에 관한 기본 지식
학습할 내용
- 앱에
SlidingPaneLayout
을 추가하는 방법
빌드할 항목
- Sports 앱을 큰 화면에 맞도록 업데이트합니다.
필요한 항목
- Android 스튜디오가 설치된 컴퓨터
- Sports 앱의 시작 코드
이 Codelab의 시작 코드 다운로드
이 Codelab은 시작 코드를 제공합니다. 이 Codelab에서 학습한 기능을 사용하여 시작 코드를 확장할 수 있습니다. 시작 코드에는 이전 Codelab을 통해 익숙한 코드와 이후 Codelab에서 학습할 익숙하지 않은 코드가 포함되어 있을 수 있습니다.
GitHub에서 이 Codelab의 코드를 가져와 Android 스튜디오에서 열려면 다음을 실행합니다.
- Android 스튜디오를 시작합니다.
- Welcome to Android Studio 창에서 Get from VCS를 클릭합니다.
- Get from Version Control 대화상자에서 Version control에 Git이 선택되어 있는지 확인합니다.
- 제공된 코드 URL을 URL 상자에 붙여넣습니다.
- 필요한 경우 Directory를 제안된 기본값과 다른 것으로 변경합니다.
- Clone을 클릭합니다. Android 스튜디오에서 코드를 가져오기 시작합니다.
- Android 스튜디오가 열릴 때까지 기다립니다.
- Codelab 시작 코드나 앱, 솔루션 코드에 적합한 모듈을 선택합니다.
- Run 버튼 을 클릭하여 코드를 빌드하고 실행합니다.
2. 코드 따라 하기 동영상 시청(선택사항)
교육 과정 강사가 Codelab을 완료하는 모습을 보려면 아래 동영상을 재생하세요.
동영상을 전체 화면으로 펼쳐(동영상 하단의 오른쪽 모서리에 있는 아이콘 사용) Android 스튜디오와 코드를 더 선명하게 보는 것이 좋습니다.
이 단계는 선택사항입니다. 이 동영상을 건너뛰고 Codelab 안내를 바로 시작할 수도 있습니다.
3. 시작 앱 개요
Sports 앱은 두 화면으로 구성됩니다. 첫 번째 화면에는 스포츠 목록이 표시됩니다. 사용자가 특정 스포츠 항목을 선택하면 두 번째 화면이 표시됩니다. 두 번째 화면은 선택한 스포츠 뉴스를 표시하는 세부정보 화면입니다. 세부정보 화면에는 구현을 간소화하기 위한 자리표시자 텍스트가 표시됩니다.
시작 코드 둘러보기
다운로드한 시작 코드에는 목록 화면과 세부정보 화면 레이아웃이 미리 디자인되어 있습니다. 이 과정에서는 앱이 대형 화면에 맞게 조정되도록 만드는 데에만 중점을 둡니다. 대형 화면을 이용하기 위해 SlidingPaneLayou
t을 사용하게 됩니다. 시작하는 데 도움이 되는 다음과 같은 몇 가지 파일을 둘러보겠습니다.
fragment_sports_list.xml
- Design 뷰에서
res/layout/fragment_sports_list.xml
을 엽니다. - 여기에는 앱의 첫 번째 화면인 스포츠 목록의 레이아웃이 포함되어 있습니다.
- 이 레이아웃은 스포츠 뉴스 목록을 표시하는 Recyclerview로 구성됩니다.
sports_list_item.xml
- Design 뷰에서
res/layout/sports_list_item.xml
을 엽니다. - 여기에는 Recyclerview에 있는 각 항목의 레이아웃이 포함되어 있습니다.
- 이 레이아웃은 스포츠의 썸네일 이미지와 뉴스 제목 그리고 간단한 스포츠 뉴스의 자리표시자 텍스트로 구성됩니다.
fragment_sports_news.xml
- Design 뷰에서
res/layout/fragment_sports_news.xml
을 엽니다. - 여기에는 앱의 두 번째 화면 레이아웃이 포함되어 있습니다. 이 화면은 사용자가 Recyclerview에서 스포츠를 선택하면 표시됩니다.
- 이 레이아웃은 스포츠 이미지 배너와 스포츠 뉴스의 자리표시자 텍스트로 구성됩니다.
main_activity.xml 및 content_main.xml
이 두 요소는 단일 프래그먼트가 있는 기본 활동 레이아웃을 정의합니다.
navigation/nav_graph.xml
탐색 그래프에는 두 개의 대상이 포함되어 있습니다. 하나는 스포츠 목록을 위한 것이고 다른 하나는 스포츠 뉴스를 위한 것입니다.
res/values 폴더
이 폴더의 리소스 파일에 관해서는 잘 알고 있을 것입니다.
colors.xml
에는 앱에서 사용되는 테마 색상이 있습니다.strings.xml
에는 앱에 필요한 모든 문자열이 있습니다.themes.xml
에는 앱의 UI 맞춤설정이 있습니다.
MainActivity.kt
여기에는 활동의 콘텐츠 뷰를 main_activity.xml
로 설정하기 위한 기본 템플릿 생성 코드가 있습니다. onSupportNavigateUp()
메서드는 앱 바에서 위로 탐색하는 기본 동작을 처리하도록 재정의됩니다.
model/Sport.kt
스포츠 목록 Recyclerview의 각 행에 표시할 데이터를 보유한 데이터 클래스입니다.
data/SportsData.kt
이 파일에는 getSportsData()
라는 함수가 포함되어 있습니다. 하드코딩된 스포츠 데이터로 미리 채워진 ArrayList
를 반환하는 함수입니다.
SportsViewModel.kt
앱의 공유된 ViewModel
입니다. ViewModel
은 스포츠 목록을 포함한 첫 번째 화면 SportsListFragment
와 자세한 스포츠 뉴스를 포함한 두 번째 화면 NewsDetailsFragment
에 의해 공유됩니다.
_currentSport
속성은MutableLiveData,
유형으로, 사용자가 선택한 현재 스포츠를 저장합니다.currentSport
속성은_currentSport
에 관한 지원 속성으로, 다른 클래스의 읽기 전용 공개 버전으로 노출됩니다._sportsData
속성에는 스포츠 데이터의 목록이 포함되어 있습니다. 이전 속성과 마찬가지로sportsData
는 이 속성의 읽기 전용 공개 버전입니다.- 이니셜라이저
init{}
블록은_currentSport
및_sportsData
를 초기화합니다._sportsData
는data/SportsData.kt
의 전체 스포츠 목록으로 초기화됩니다._currentSport
는 목록의 첫 번째 항목으로 초기화됩니다. updateCurrentSport()
함수는Sports
인스턴스를 가져와 전달된 값으로_currentSport
를 업데이트합니다.
SportsAdapter.kt
RecyclerView
의 어댑터입니다. 생성자에서 클릭 리스너가 전달됩니다. 이 파일의 코드는 대부분 이전 Codelab에서 익숙한 상용구 코드입니다.
SportsListFragment.kt
스포츠 목록이 표시되는 첫 번째 화면 프래그먼트입니다.
onCreateView()
함수는 결합 객체를 사용하여fragment_sports_list
레이아웃 XML을 확장합니다.onViewCreated()
함수는RecyclerView
어댑터를 설정합니다. 그리고 사용자가 선택한 스포츠를 공유된ViewModel
인SportsViewModel
의 현재 스포츠로 업데이트합니다. 또한 스포츠 뉴스가 포함된 세부정보 화면으로 이동하고submitList(List)
를 사용하여 표시할 스포츠 목록을 어댑터에 제출합니다.
NewsDetailsFragment.kt
앱의 두 번째 화면으로, 스포츠 뉴스의 자리표시자 텍스트가 표시되는 곳입니다.
onCreateView()
함수는 결합 객체를 사용하여fragment_sports_news
레이아웃 XML을 확장합니다.onViewCreated()
함수는 데이터가 변경되면 UI를 자동으로 업데이트하도록SportsViewModel
의 속성인currentSport
에 관찰자를 연결합니다. 관찰자 내에서 스포츠 제목, 이미지, 뉴스가 업데이트됩니다.
앱 빌드 및 실행
- 에뮬레이터 또는 기기에서 앱을 빌드하고 실행합니다. 스포츠 목록에서 항목을 선택하면 앱이 뉴스 자리표시자 텍스트가 있는 두 번째 화면으로 이동합니다.
4. 목록-세부정보 패턴
현재 시작 앱은 태블릿 같은 큰 기기의 화면 공간을 충분히 활용하지 못합니다. 이 문제를 해결하기 위해 이 Codelab에서 배울 목록-세부정보 패턴을 사용하여 앱 UI를 표시할 것입니다.
태블릿에서 앱 실행
이 작업에서는 태블릿 프로필을 사용하는 에뮬레이터를 만듭니다. 에뮬레이터가 생성되면 Sports 앱 시작 코드를 실행하고 UI를 살펴봅니다.
- Android 스튜디오에서 Tools > AVD Manager로 이동합니다.
- Android Virtual Device Manager 창이 표시됩니다. 하단에 표시되는 + Create New Virtual Device...를 클릭합니다.
- Virtual Device Configuration 창이 표시됩니다. 여기에서 에뮬레이터 하드웨어와 OS를 구성하게 됩니다. 왼쪽 창에서 Tablet을 클릭합니다. 가운데 창에서 Pixel C 또는 기타 유사한 하드웨어 프로필을 선택합니다.
- Next를 클릭합니다.
- 최신 시스템 이미지를 선택합니다. 이 Codelab 작성 시점의 최신 버전은 R(API 수준 30)입니다.
- Next를 클릭합니다.
- 이제 가상 기기의 이름을 바꿀 수 있습니다(선택사항).
- Finish를 클릭합니다.
- Android Virtual Device Manager 창으로 다시 이동합니다. 새로 만든 가상 기기 옆에 있는 시작 아이콘 을 클릭합니다.
- 태블릿 프로필을 사용하는 에뮬레이터가 실행됩니다. 이 작업에는 다소 시간이 걸릴 수 있으니 기다려 주시기 바랍니다.
- Android Virtual Device Manager 창을 닫습니다.
- 새로 만든 에뮬레이터에서 Sports 앱을 실행합니다.
앱이 큰 기기의 전체 화면을 제대로 활용하지 못함을 알 수 있습니다. 큰 화면에서는 목록-세부정보가 목록보다 더 효율적입니다. 항목-세부정보 패턴(마스터-세부정보 패턴이라고도 함)은 레이아웃의 한쪽에 항목 목록을 표시합니다. 항목을 탭하면 항목 옆에 세부정보가 표시됩니다. 일반적으로 이러한 보기는 태블릿 같은 큰 화면에만 표시됩니다. 큰 화면의 공간이 더 넓어 콘텐츠를 더 많이 표시할 수 있기 때문입니다.
다음 이미지는 목록-세부정보 패턴의 예입니다.
위의 목록-세부정보 패턴에서 왼쪽에는 항목 목록이, 오른쪽에는 선택한 항목의 세부정보가 표시됩니다.
마찬가지로 sports 앱에서 위 패턴을 사용할 경우 뉴스 프래그먼트가 세부정보 화면이 됩니다.
이 Codelab에서는 SlidingPaneLayout
을 사용하여 목록-세부정보 UI를 구현하는 방법을 알아봅니다.
5. SlidingPaneLayout 패턴
목록-세부정보 UI는 화면 크기에 따라 다르게 작동해야 할 수 있습니다. 대형 디스플레이에는 목록 창과 세부정보 창을 나란히 배치할 수 있는 충분한 공간이 있습니다. 목록 항목을 클릭하면 세부정보 창에 세부정보가 표시됩니다. 하지만 작은 화면에서는 이들이 복잡하게 보입니다. 한 번에 창 두 개를 표시하는 대신 한 번에 창 하나씩 표시하는 것이 더 좋습니다. 처음에는 화면이 목록 창으로 채워집니다. 항목을 탭하면 목록 창이 그 항목의 세부정보 창으로 바뀌고, 화면도 세부정보 창으로 채워집니다.
현재 화면 크기에 따라 적절한 사용자 환경을 선택하기 위해 SlidingPaneLayout을 사용하여 로직을 관리하는 방법을 알아봅니다.
작은 화면에서 세부정보 창이 어떻게 목록 창 위로 슬라이드되는지 알 수 있습니다.
다음은 SlidingPaneLayout
이 작은 화면에 어떻게 표시되는지 보여주는 이미지입니다. 목록에서 항목이 선택되면 세부정보 창이 목록 창과 어떻게 겹치는지 관찰합니다. 실제로 두 창이 항상 표시됩니다.
따라서 SlidingPaneLayout
을 사용하면 대형 기기에서는 창 두 개를 나란히 표시하고 휴대전화와 같은 소형 기기에서는 창을 한 번에 한 개만 표시하도록 자동 조정할 수 있습니다.
6. 라이브러리 종속 항목 추가
build.gradle (Module: Sports.app)
을 엽니다.- 앱에서
SlidingPaneLayout
을 사용하려면dependencies
섹션에서 다음 종속 항목을 포함합니다.
dependencies {
...
implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0-beta01"
}
7. 스포츠 목록 프래그먼트 xml 구성
이 작업에서는 fragment_sports_list
의 루트 레이아웃을 SlidingPaneLayout
으로 변환합니다. 이미 알아본 것처럼, SlidingPaneLayout
은 UI의 최상위 수준에서 사용할 수 있는 두 개의 가로 창 레이아웃을 제공합니다. 이 레이아웃은 첫 번째 창을 콘텐츠 목록 또는 브라우저로 사용합니다. 이 창은 다른 창에 콘텐츠를 표시하는 기본 세부정보 뷰에 종속됩니다.
Sports 앱에서 첫 번째 창은 스포츠 목록을 표시하는 RecyclerView
이고 두 번째 창은 스포츠 뉴스를 표시합니다.
SlidingPaneLayout 추가
fragment_sports_list.xml
을 엽니다. 루트 레이아웃이FrameLayout
임을 알 수 있습니다.FrameLayout
을androidx.slidingpanelayout.widget.SlidingPaneLayout.
으로 변경합니다.
<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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SportsListFragment">
<androidx.recyclerview.widget.RecyclerView...>
</androidx.slidingpanelayout.widget.SlidingPaneLayout>
android:id
속성을SlidingPaneLayout
에 추가하고 그 항목에@+id/sliding_pane_layout
의 값을 지정합니다.
<androidx.slidingpanelayout.widget.SlidingPaneLayout
...
android:id="@+id/sliding_pane_layout"
...>
SlidingPaneLayout에 두 번째 창 추가
이 작업에서는 SlidingPaneLayout
에 두 번째 하위 요소를 추가합니다. 이 요소는 오른쪽 콘텐츠 창으로 표시됩니다.
fragment_sports_list.xml
의RecyclerView
아래에 두 번째 하위 요소인androidx.fragment.app.FragmentContainerView
를 추가합니다.- 필수 속성
layout_height
와layout_width
를FragmentContainerView
에 추가합니다. 두 속성에match_parent
의 값을 지정합니다. 이 값은 나중에 업데이트합니다.
<androidx.fragment.app.FragmentContainerView
android:layout_height="match_parent"
android:layout_width="match_parent"/>
android:id
속성을FragmentContainerView
에 추가하고 그 항목에@+id/detail_container
의 값을 지정합니다.
android:id="@+id/detail_container"
android:name
속성을 사용하여FragmentContainerView
에NewsDetailsFragment
를 추가합니다.
android:name="com.example.android.sports.NewsDetailsFragment"
layout_width 속성 업데이트
SlidingPaneLayout
은 두 창의 너비를 감안해 창을 나란히 표시할지 결정합니다. 예를 들어 목록 창이 최소 크기가 300dp
로 측정되고 세부정보 창에 400dp
가 필요한 경우 SlidingPaneLayout
은 최소 700dp
의 너비를 사용할 수 있다면 두 창을 자동으로 나란히 표시합니다.
합산 너비가 SlidingPaneLayout
에 사용 가능한 너비를 초과하는 경우 하위 뷰가 겹칩니다. 이 경우 하위 뷰는 SlidingPaneLayout
에 사용 가능한 너비를 채우도록 확장됩니다.
하위 뷰의 너비를 확인하려면 기기 화면 너비에 관한 몇 가지 기본 정보를 알아야 합니다. 다음 표에는 크기 조절이 가능한 애플리케이션 레이아웃을 설계하고 개발하며 테스트할 수 있는 체계적인 중단점 목록이 나와 있습니다. 특히 고유의 사례에 맞춰 앱을 최적화하도록 레이아웃의 단순성과 유연성의 균형을 이루기 위해 이러한 클래스를 선택했습니다.
너비 | 중단점 | 기기 표현 |
좁은 너비 | 600dp 미만 | 세로 모드 휴대전화의 99.96% |
중간 너비 | 600dp 이상 | 세로 모드 태블릿 중 93.73% 세로 모드의 펼친 대형 내부 디스플레이 |
확장 후 너비 | 840dp 이상 | 가로 모드 태블릿 중 97.22% 가로 모드의 펼친 대형 내부 디스플레이 |
너비가 600dp
미만인 기기에서 Sports 앱을 사용할 경우 휴대전화에는 단일 창, 즉 스포츠 목록 창만 표시하는 것이 좋습니다. 태블릿에 두 창을 모두 표시하려면 합산 너비가 840dp
보다 커야 합니다. 첫 번째 하위 요소 recycler 뷰에는 너비 550dp
를, 두 번째 하위 요소 FragmentContainerView
에는 300dp
를 사용할 수 있습니다.
fragment_sports_list.xml
에서RecyclerView
의 레이아웃 너비를550dp
로 변경하고FragmentContainerView
의 레이아웃 너비를300dp
로 변경합니다.
<androidx.recyclerview.widget.RecyclerView
...
android:layout_width="550dp"
.../>
<androidx.fragment.app.FragmentContainerView
...
android:layout_width="300dp"
.../>
- 태블릿 프로필을 사용하는 에뮬레이터와 휴대전화 프로필을 사용하는 에뮬레이터에서 앱을 각각 실행합니다.
태블릿에서는 두 창이 표시됩니다. 이후 단계에서 태블릿의 두 번째 창의 너비를 수정할 것입니다.
- 휴대전화 프로필을 사용하는 에뮬레이터에서 앱을 실행합니다.
layout_weight 추가
이 작업에서는 두 번째 창이 남은 전체 공간을 차지하도록 태블릿의 UI를 수정합니다.
SlidingPaneLayout
을 사용하면 뷰가 겹치지 않는 경우에는 하위 뷰에 레이아웃 매개변수 layout_weight
를 사용하여 측정 완료 후 남은 공간을 어떻게 분할할지 정의할 수 있습니다. 이 매개변수는 너비에만 적용됩니다.
fragment_sports_list.xml
에서layout_weight
를FragmentContainerView
에 추가하고 값을1
로 지정합니다. 이제 목록 창이 측정된 후 남은 공간을 채우도록 두 번째 창이 확장됩니다.
android:layout_weight="1"
- 앱을 실행합니다.
축하합니다. SlidingPaneLayout
을 추가했습니다. 하지만 아직 완료되지는 않았습니다. 뒤로 탐색을 구현하고, 목록에서 항목이 선택되면 두 번째 창을 업데이트해야 합니다. 이러한 작업은 이후 작업에서 구현하게 됩니다.
8. 세부정보 창 변경
에뮬레이터에서 태블릿 프로필을 사용하여 앱을 실행합니다. 스포츠 목록에서 목록 항목을 선택합니다. 앱이 세부정보 창으로 이동하는 것을 알 수 있습니다.
이 작업에서는 이 문제를 해결할 것입니다. 현재 이중 창 콘텐츠가 선택된 스포츠로 업데이트된 다음, 앱이 NewsDetailsFragment
로 이동합니다.
SportsListFragment
파일의onViewCreated()
함수에서 세부정보 화면으로 이동하는 다음 줄을 찾습니다.
// Navigate to the details screen
val action = SportsListFragmentDirections.actionSportsListFragmentToNewsFragment()
this.findNavController().navigate(action)
- 위의 줄을 아래 코드로 바꿉니다.
binding.slidingPaneLayout.openPane()
SlidingPaneLayout
에서 openPane()
을 호출하여 두 번째 창을 첫 번째 창으로 전환합니다. 태블릿 같이 두 창이 모두 표시되는 경우에는 이렇게 해도 가시적인 영향이 없습니다.
- 태블릿 및 휴대전화 에뮬레이터에서 앱을 실행합니다. 이중 창 콘텐츠가 올바르게 업데이트되는 것을 확인할 수 있습니다.
이제 다음 작업에서는 앱에 맞춤 뒤로 탐색 기능을 추가합니다.
9. 맞춤 뒤로 탐색 추가
목록 창과 세부정보 창이 겹치는 소형 기기에서는 시스템 뒤로 버튼을 누르면 세부정보 창에서 목록 창으로 다시 이동해야 합니다. 이렇게 하려면 맞춤 뒤로 탐색을 제공하고 OnBackPressedCallback
을 SlidingPaneLayout
의 현재 상태에 연결하면 됩니다.
뒤로 탐색
뒤로 탐색 기능은 사용자가 이전에 방문한 화면 기록을 통해 뒤로 이동하는 기능입니다. 모든 Android 기기는 이 같은 유형의 탐색을 위해 뒤로 버튼을 제공합니다. 사용자의 Android 기기에 따라 이 버튼은 물리적 버튼 또는 소프트웨어 버튼이 될 수 있습니다.
맞춤 뒤로 탐색
Android는 사용자가 애플리케이션을 탐색할 때 대상의 백 스택을 유지합니다. 이를 통해 일반적으로 Android를 사용하면 뒤로 버튼을 누를 때 이전 대상으로 적절하게 이동할 수 있습니다. 하지만 최상의 사용자 환경을 제공하기 위해 앱에서 뒤로 이동하는 동작을 자체적으로 구현해야 하는 경우도 있습니다.
예를 들어, Chrome 브라우저 같은 WebView를 사용할 때는 기본 뒤로 버튼 동작을 재정의하여 사용자에게 앱의 이전 화면 대신 웹 방문 기록을 통해 뒤로 이동하도록 하는 것이 좋습니다.
마찬가지로 SlidingPaneLayout
에 맞춤 뒤로 탐색을 제공하고 세부정보 창에서 목록 창으로 다시 앱을 이동해야 합니다.
맞춤 뒤로 탐색 구현
Sports 앱에서 맞춤 뒤로 탐색을 구현하려면 다음 작업이 필요합니다.
- 뒤로 키 누름을 처리하는 맞춤 콜백을 정의합니다. 이 콜백이
OnBackPressedCallback
보다 우선 적용됩니다. - 콜백 인스턴스를 등록하고 추가합니다.
먼저 맞춤 콜백을 정의합니다.
SportsListFragment
파일의SportsListFragment
클래스 정의 아래에 새 클래스를 추가합니다. 그 이름을SportsListOnBackPressedCallback
으로 지정합니다.SlidingPaneLayout
의private
인스턴스를 생성자 매개변수로 전달합니다.
class SportsListOnBackPressedCallback(
private val slidingPaneLayout: SlidingPaneLayout
)
OnBackPressedCallback
에서 클래스를 확장합니다.OnBackPressedCallback
클래스는onBackPressed
콜백을 처리합니다. 곧 생성자 매개변수 오류를 수정합니다.
class SportsListOnBackPressedCallback(
private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback()
OnBackPressedCallback
의 생성자는 초기 사용 설정 상태를 나타내는 부울 값을 사용합니다. 콜백이 사용 설정된 때만(즉, isEnabled()
가 true를 반환) 디스패처가 콜백의 handleOnBackPressed()
를 호출하여 뒤로 버튼 이벤트를 처리합니다.
slidingPaneLayout.
isSlideable
*&& slidingPaneLayout.isOpen
*을 생성자 매개변수로OnBackPressedCallback
에 전달합니다. (소형 화면에서) 두 번째 창이 슬라이드 가능하고 단일 창이 표시되는 경우에만 부울isSlideable
이 true입니다. 두 번째 창 즉, 콘텐츠 창이 완전히 열리는 경우isOpen
의 값은true
입니다.
class SportsListOnBackPressedCallback(
private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen)
이 코드는 소형 화면 기기에서 콘텐츠 창이 열려 있을 때만 콜백이 사용 설정되도록 해 줍니다.
- 구현되지 않은 메서드에 관한 오류를 해결하려면 빨간색 전구 를 클릭하고 Implement members를 선택합니다.
- Implement members 팝업에서 ok를 클릭하여
handleOnBackPressed
메서드를 재정의합니다.
클래스는 다음과 같이 표시됩니다.
class SportsListOnBackPressedCallback(
private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen) {
/**
* Callback for handling the [OnBackPressedDispatcher.onBackPressed] event.
*/
override fun handleOnBackPressed() {
TODO("Not yet implemented")
}
}
- 콘텐츠 창을 닫고 목록 창으로 돌아가도록
handleOnBackPressed()
함수 내에서 TODO 문을 삭제하고 다음 코드를 추가합니다.
slidingPaneLayout.closePane()
SlidingPaneLayout의 이벤트 모니터링
뒤로 누르기 이벤트를 처리하는 것 외에도 슬라이딩 창과 관련된 이벤트를 수신하고 모니터링해야 합니다. 콘텐츠 창이 슬라이드되면 그에 따라 콜백이 사용 설정되거나 중지되어야 합니다. 이를 위해 PanelSlideListener
를 사용합니다.
인터페이스 SlidingPaneLayout.PanelSlideListener
에는 세 가지 추상 메서드 즉, onPanelSlide()
, onPanelOpened()
, onPanelClosed()
가 포함되어 있습니다. 이러한 메서드는 세부정보 창을 슬라이드하거나 열거나 닫을 때 호출됩니다.
SlidingPaneLayout.PanelSlideListener
에서SportsListOnBackPressedCallback
클래스를 확장합니다.- 오류를 해결하려면 세 가지 메서드를 구현합니다. 빨간색 전구를 클릭하고 Android 스튜디오에서 Implement members를 선택합니다.
SportsListOnBackPressedCallback
클래스는 다음과 유사합니다.
class SportsListOnBackPressedCallback(
private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen),
SlidingPaneLayout.PanelSlideListener{
override fun handleOnBackPressed() {
slidingPaneLayout.closePane()
}
override fun onPanelSlide(panel: View, slideOffset: Float) {
TODO("Not yet implemented")
}
override fun onPanelOpened(panel: View) {
TODO("Not yet implemented")
}
override fun onPanelClosed(panel: View) {
TODO("Not yet implemented")
}
}
- TODO 문을 삭제합니다.
- 세부정보 창이 열려 있을 때(표시될 때)
OnBackPressedCallback
콜백을 사용 설정합니다. 이렇게 하려면setEnabled()
함수를 호출하고true
를 전달하면 됩니다.onPanelOpened()
내에 다음 코드를 작성합니다.
setEnabled(true)
- 위 코드는 속성 액세스 구문을 사용하여 단순화할 수 있습니다.
override fun onPanelOpened(panel: View) {
isEnabled = true
}
- 마찬가지로 세부정보 창이 닫혀 있을 때
isEnabled
를false
로 설정합니다.
override fun onPanelClosed(panel: View) {
isEnabled = false
}
- 콜백을 완료하는 마지막 단계는 세부정보 창 슬라이드 이벤트 관련 알림을 받는
SportsListOnBackPressedCallback
리스너 클래스를 리스너 목록에 추가하는 것입니다.SportsListOnBackPressedCallback
클래스에init
블록을 추가합니다.init
블록 내에서this
를 전달하는slidingPaneLayout.addPanelSlideListener()
를 호출합니다.
init {
slidingPaneLayout.addPanelSlideListener(this)
}
완성된 SportsListOnBackPressedCallback
클래스는 다음과 유사합니다.
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
}
}
콜백 등록
콜백의 작동 모습을 보기 위해 디스패처 OnBackPressedDispatcher
를 사용하여 콜백을 등록합니다.
FragmentActivity
의 기본 클래스에서는 OnBackPressedDispatcher
를 사용하여 뒤로 버튼의 동작을 제어할 수 있습니다. OnBackPressedDispatcher
는 뒤로 버튼 이벤트가 하나 이상의 OnBackPressedCallback
객체로 전달되는 방법을 제어합니다.
addCallback()
메서드를 사용하여 콜백을 추가합니다. 이 메서드는 LifecycleOwner
를 받습니다. 따라서 LifecycleOwner
가 Lifecycle.State.STARTED
일 때만 OnBackPressedCallback
이 추가됩니다. 또한 활동 또는 프래그먼트는 연결된 LifecycleOwner
가 제거될 때 등록된 콜백을 삭제합니다. 이는 메모리 누수를 방지하며, 전체 기간이 짧은 프래그먼트 또는 기타 수명 주기 소유자에 사용하기 적합합니다.
또한 addCallback()
메서드는 인스턴스에서 콜백 클래스를 두 번째 매개변수로 받습니다. 다음 단계에 따라 콜백을 등록합니다.
SportsListFragment
파일의onViewCreated()
함수 내에서 바인딩 변수 선언 바로 아래에SlidingPaneLayout
을 위한 인스턴스를 만들고 이 인스턴스에binding.slidingPaneLayout
의 값을 할당합니다.
val slidingPaneLayout = binding.slidingPaneLayout
SportsListFragment
파일의onViewCreated()
함수 내에서slidingPaneLayout
선언 바로 아래에 다음 코드를 추가합니다.
// Connect the SlidingPaneLayout to the system back button.
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
SportsListOnBackPressedCallback(slidingPaneLayout)
)
위 코드는 addCallback()
을 사용하여 viewLifecycleOwner
와 SportsListOnBackPressedCallback
인스턴스를 전달합니다. 이 콜백은 프래그먼트의 수명 주기 동안에만 활성화됩니다.
- 이제 휴대전화 프로필을 사용하는 에뮬레이터에서 앱을 실행하고 맞춤 뒤로 버튼 기능의 작동 모습을 확인할 차례입니다.
10. 잠금 모드
휴대전화 같은 소형 화면에서 목록 창과 세부정보 창이 겹치는 경우 사용자는 기본적으로 양방향으로 스와이프할 수 있고 동작 탐색을 사용하지 않더라도 두 창 간에 자유롭게 전환할 수 있습니다. SlidingPaneLayout
의 잠금 모드를 설정하여 세부정보 창을 잠그거나 잠금 해제할 수 있습니다.
- 휴대전화 프로필을 사용하는 에뮬레이터에서 세부정보 창이 화면에서 사라질 때까지 스와이프해 봅니다.
- 세부정보 창이 표시되도록 안쪽으로 스와이프할 수도 있습니다. 직접 해 보세요.
- 이 기능은 Sports 앱에서는 바람직하지 않습니다. 사용자가 동작을 사용하여 안쪽과 바깥쪽으로 스와이프하지 못하도록
SlidingPaneLayout
을 잠그는 것이 좋습니다. 이를 구현하려면onViewCreated()
메서드의slidingPaneLayout
정의 아래에서lockMode
를LOCK_MODE_LOCKED
로 설정합니다.
slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
다른 잠금 모드에 관해 자세히 알아보려면 문서를 참고하세요.
- 앱을 한 번 더 실행합니다. 이제 세부정보 창이 잠겨 있음을 알 수 있습니다.
축하합니다. 앱에 SlidingPaneLayout
을 추가했습니다.
11. 솔루션 코드
이 Codelab의 솔루션 코드는 아래에 나온 프로젝트와 모듈에 있습니다.
- 프로젝트에 제공된 GitHub 저장소 페이지로 이동합니다.
- 브랜치 이름이 Codelab에 지정된 브랜치 이름과 일치하는지 확인합니다. 예를 들어 다음 스크린샷에서 브랜치 이름은 main입니다.
- 프로젝트의 GitHub 페이지에서 Code 버튼을 클릭하여 팝업을 엽니다.
- 팝업에서 Download ZIP 버튼을 클릭하여 컴퓨터에 프로젝트를 저장합니다. 다운로드가 완료될 때까지 기다립니다.
- 컴퓨터에서 파일을 찾습니다(예: Downloads 폴더).
- ZIP 파일을 더블클릭하여 압축을 해제합니다. 프로젝트 파일이 포함된 새 폴더가 만들어집니다.
Android 스튜디오에서 프로젝트 열기
- Android 스튜디오를 시작합니다.
- Welcome to Android Studio 창에서 Open을 클릭합니다.
참고: Android 스튜디오가 이미 열려 있는 경우 File > Open 메뉴 옵션을 대신 선택합니다.
- 파일 브라우저에서 압축 해제된 프로젝트 폴더가 있는 위치로 이동합니다(예: Downloads 폴더).
- 프로젝트 폴더를 더블클릭합니다.
- Android 스튜디오가 프로젝트를 열 때까지 기다립니다.
- Run 버튼 을 클릭하여 앱을 빌드하고 실행합니다. 예상대로 작동하는지 확인합니다.