활동 및 인텐트

지금까지 작업한 앱에는 활동이 하나만 있었습니다. 실제로 많은 Android 앱에는 활동 간 탐색이 포함된 여러 활동이 필요합니다.

이 Codelab에서는 여러 활동을 사용하고 인텐트를 사용하여 활동 간에 탐색하며 다른 앱에 데이터를 전달하도록 사전 앱을 빌드합니다.

기본 요건

다음을 실행할 수 있어야 합니다.

  • Android 스튜디오에서 프로젝트를 탐색합니다.
  • Android 스튜디오에서 XML 리소스를 사용하고 추가합니다.
  • 기존 클래스의 메서드를 재정의하고 구현합니다.
  • Kotlin 클래스의 인스턴스를 만들고 클래스 속성에 액세스하며 메서드를 호출합니다.
  • 특정 클래스에 관한 자세한 내용은 developer.android.com의 문서를 참고하세요.

학습할 내용

다음을 학습합니다.

  • 명시적 인텐트를 사용하여 특정 활동으로 이동하는 방법
  • 암시적 인텐트를 사용하여 다른 앱의 콘텐츠로 이동하는 방법
  • 메뉴 옵션을 추가하여 앱 바에 버튼을 추가하는 방법

빌드할 항목

  • 인텐트를 사용하고 옵션 메뉴를 추가하여 화면 간 탐색을 구현하도록 사전 앱을 수정합니다.

필요한 항목

  • Android 스튜디오가 설치된 컴퓨터

다음 몇 단계에서는 Words 앱으로 작업합니다. Words 앱은 간단한 사전 앱으로, 문자 목록과 각 문자 관련 단어, 브라우저에서 각 단어의 정의를 찾아보는 기능이 포함되어 있습니다.

실행할 작업이 많지만 걱정하지 않아도 됩니다. 인텐트를 학습하려고 전체 앱을 빌드할 필요는 없기 때문입니다. 대신 불완전한 버전의 프로젝트 또는 시작 프로젝트가 제공되었습니다.

모든 화면이 구현되어 있지만 아직 한 화면에서 다른 화면으로 이동할 수 없습니다. 해야 할 작업은 처음부터 모든 항목을 빌드하지 않고도 전체 프로젝트가 작동하도록 인텐트를 사용하는 것입니다.

이 Codelab의 시작 코드 다운로드

이 Codelab은 시작 코드를 제공합니다. 이 Codelab에서 학습한 기능을 사용하여 시작 코드를 확장할 수 있습니다. 시작 코드에는 이전 Codelab을 통해 익숙한 코드가 포함될 수 있습니다. 익숙하지 않은 코드도 포함될 수 있으며 이에 관해서는 향후 Codelab에서 알아봅니다.

GitHub에서 시작 코드를 다운로드하면 폴더 이름은 android-basics-kotlin-words-app-starter입니다. Android 스튜디오에서 프로젝트를 열 때 이 폴더를 선택합니다.

git 명령어에 익숙하다면 시작 코드는 'starter'라는 분기에 있습니다. 저장소를 클론한 후 origin/starter 분기에서 코드를 확인합니다. git 명령어를 사용해본 적이 없으면 아래 단계를 따라 GitHub에서 코드를 다운로드합니다.

이 Codelab의 코드를 가져와서 Android 스튜디오에서 열려면 다음을 실행합니다.

코드 가져오기

  1. 제공된 URL을 클릭합니다. 브라우저에서 프로젝트의 GitHub 페이지가 열립니다.
  2. 프로젝트의 GitHub 페이지에서 Code 버튼을 클릭하여 대화상자를 엽니다.

5b0a76c50478a73f.png

  1. 대화상자에서 Download ZIP 버튼을 클릭하여 컴퓨터에 프로젝트를 저장합니다. 다운로드가 완료될 때까지 기다립니다.
  2. 컴퓨터에서 파일을 찾습니다(예: Downloads 폴더).
  3. ZIP 파일을 더블클릭하여 압축을 해제합니다. 프로젝트 파일이 포함된 새 폴더가 만들어집니다.

Android 스튜디오에서 프로젝트 열기

  1. Android 스튜디오를 시작합니다.
  2. Welcome to Android Studio 창에서 Open an existing Android Studio project를 클릭합니다.

36cc44fcf0f89a1d.png

참고: Android 스튜디오가 이미 열려 있는 경우 File > New > Import Project 메뉴 옵션을 대신 선택합니다.

21f3eec988dcfbe9.png

  1. Import Project 대화상자에서 압축 해제된 프로젝트 폴더가 있는 위치로 이동합니다(예: Downloads 폴더).
  2. 프로젝트 폴더를 더블클릭합니다.
  3. Android 스튜디오가 프로젝트를 열 때까지 기다립니다.
  4. Run 버튼 11c34fc5e516fb1c.png을 클릭하여 앱을 빌드하고 실행합니다. 예상대로 작동하는지 확인합니다.
  5. Project 도구 창에서 프로젝트 파일을 살펴보고 앱이 설정된 방식을 확인합니다.

진행하기 전에 잠시 시간을 내어 프로젝트를 숙지합니다. 이전 단원을 통해 모든 개념을 숙지해야 합니다. 현재 앱은 두 활동으로 구성됩니다. 각 활동에는 recycler 뷰와 어댑터가 포함되어 있습니다.

f1e0ec543698f945.png

구체적으로 다음 파일로 작업합니다.

  1. LetterAdapterMainActivityRecyclerView에서 사용합니다. 각 문자는 현재 비어 있는 onClickListener가 포함된 버튼입니다. 여기에서 버튼 누름을 처리하여 DetailActivity로 이동합니다.
  2. WordAdapterRecyclerViewDetailActivity에서 단어 목록을 표시하는 데 사용합니다. 아직 이 화면으로 이동할 수 없지만 각 단어에도 onClickListener가 있는 상응하는 버튼이 있습니다. 여기에서 단어의 정의를 표시하기 위해 브라우저로 이동하는 코드를 추가합니다.
  3. MainActivity에도 몇 가지 변경이 필요합니다. 여기에서 옵션 메뉴를 구현하여 사용자가 목록 및 그리드 레이아웃 간에 전환할 수 있는 버튼을 표시합니다.

ce3474dba2a9c1c8.png

지금까지 프로젝트를 숙지했다고 생각되면 다음 섹션으로 진행하여 인텐트에 관해 알아봅니다.

이제 초기 프로젝트를 설정했으므로 인텐트 그리고 앱에서 인텐트를 사용할 수 있는 방법을 알아보겠습니다.

인텐트는 실행할 작업을 나타내는 객체입니다. 인텐트는 활동을 실행하는 데 가장 많이 사용되지만 다른 용도도 있습니다. 인텐트 유형에는 두 가지가 있습니다. 암시적 인텐트와 명시적 인텐트입니다. 명시적 인텐트는 매우 구체적이며 실행할 활동을 정확하게 알 수 있고 자체 앱의 화면인 경우가 많습니다.

암시적 인텐트는 좀 더 추상적이며 시스템에 링크 열기나 이메일 작성, 전화 걸기와 같은 작업 유형을 알려주고 시스템은 요청 처리 방법을 파악해야 합니다. 인식하지 못한 채로 두 종류의 인텐트가 실제로 작동하는 것을 확인했을 수 있습니다. 일반적으로 자체 앱에서 활동을 표시할 때 명시적 인텐트를 사용합니다.

그러나 현재 앱과 관련이 없는 작업의 경우 예를 들어 흥미로운 Android 문서 페이지를 발견하여 친구와 공유하려고 할 때 암시적 인텐트를 사용합니다. 페이지를 공유하는 데 사용할 앱을 묻는 메뉴가 표시될 수 있습니다.

e9c77033d9224170.png

개발자는 자체 앱에서 작업 또는 화면 표시에 명시적 인텐트를 사용하고 전체 프로세스를 책임집니다. 일반적으로 암시적 인텐트는 다른 앱이 관련된 작업을 실행하는 데 사용하고 시스템이 최종 결과를 결정합니다. Words 앱에서 두 유형의 인텐트를 모두 사용합니다.

702236c6e2276f91.png

첫 번째 인텐트를 구현해보겠습니다. 첫 번째 화면에서 사용자가 문자를 탭하면 단어 목록이 있는 두 번째 화면으로 이동해야 합니다. DetailActivity는 이미 구현되었으므로 인텐트를 사용하여 실행하기만 하면 됩니다. 앱이 실행해야 할 활동을 정확히 알고 있으므로 명시적 인텐트를 사용합니다.

몇 단계만 거치면 인텐트를 만들고 사용할 수 있습니다.

  1. LetterAdapter.kt를 열고 onBindViewHolder()까지 아래로 스크롤합니다. 버튼 텍스트를 설정하는 줄 아래에서 holder.buttononClickListener를 설정합니다.
holder.button.setOnClickListener {

}
  1. 그런 다음 context 참조를 가져옵니다.
val context = holder.view.context
  1. Intent를 만들어 컨텍스트와 대상 활동의 클래스 이름을 전달합니다.
val intent = Intent(context, DetailActivity::class.java)

표시하려는 활동의 이름은 DetailActivity::class.java로 지정됩니다. 실제 DetailActivity 객체는 백그라운드에서 만들어집니다.

  1. putExtra 메서드를 호출하여 '문자'를 첫 번째 인수로 전달하고 버튼 텍스트를 두 번째 인수로 전달합니다.
intent.putExtra("letter", holder.button.text.toString())

extra는 무엇인가요? 인텐트는 명령어 집합일 뿐입니다. 대상 활동의 인스턴스는 아직 없습니다. 대신 extra는 나중에 검색할 수 있도록 이름이 지정된 숫자나 문자열과 같은 데이터입니다. 함수를 호출할 때 인수를 전달하는 것과 비슷합니다. DetailActivity는 어떤 문자에도 표시될 수 있으므로 표시할 문자를 알려줘야 합니다.

또한 toString() 호출이 필요한 이유는 무엇이라고 생각하나요? 버튼의 텍스트가 이미 문자열이지 않나요?

꼭 그렇지는 않습니다. 실제로는 CharSequence 유형으로, 인터페이스라고 합니다. 지금은 Kotlin 인터페이스에 관해 알 필요가 없습니다. 문자열과 같은 유형이 특정 함수와 속성을 구현하도록 하는 방법이라는 점만 알면 됩니다. CharSequence를 문자열과 같은 클래스의 좀 더 일반적인 표현으로 생각하면 됩니다. 버튼의 text 속성은 문자열이거나 CharSequence이기도 한 객체일 수 있습니다. 그러나 putExtra() 메서드는 CharSequence만이 아니라 String을 허용하므로 toString()을 호출해야 합니다.

  1. 컨텍스트 객체에서 startActivity() 메서드를 호출하여 intent를 전달합니다.
context.startActivity(intent)

이제 앱을 실행하고 문자를 탭해봅니다. 세부정보 화면이 표시됩니다. 그러나 사용자가 어떤 문자를 탭하든 세부정보 화면에는 항상 문자 A에 관한 단어가 표시됩니다. 어떤 문자가 intent extra로 전달되든 그에 관한 단어가 표시되도록 세부정보 활동에서 여전히 해야 할 작업이 있습니다.

첫 번째 명시적 인텐트를 만들었습니다! 이제 세부정보 화면으로 이동합니다.

DetailActivityonCreate 메서드에서 setContentView 호출 후에 하드 코딩 문자를 intent에서 전달된 letterId를 가져오는 코드로 바꿉니다.

val letterId = intent?.extras?.getString("letter").toString()

여기서 많은 일이 발생하므로 각 부분을 살펴보겠습니다.

먼저 intent 속성의 출처는 어디인가요? DetailActivity의 속성이라기보다는 모든 활동의 속성입니다. 활동을 실행하는 데 사용된 인텐트 참조를 유지합니다.

extras 속성은 Bundle 유형이고 짐작했겠지만 인텐트에 전달된 모든 extras에 액세스하는 방법을 제공합니다.

두 속성 모두 물음표로 표시됩니다. 이유가 뭘까요? intentextras 속성은 null을 허용하므로 값이 있을 수도 있고 없을 수도 있기 때문입니다. 개발자는 변수를 null로 하고 싶을 때가 있습니다. intent 속성은 실제로 Intent가 아닐 수 있고(활동이 인텐트에서 실행되지 않은 경우) extras 속성은 실제로 Bundle이 아니라 null이라는 값일 수 있습니다. Kotlin에서 null은 값이 없음을 의미합니다. 객체가 있거나 null일 수 있습니다. 앱이 속성에 액세스하거나 null 객체에서 함수를 호출하려고 하면 다운됩니다. 이 값에 안전하게 액세스하려면 이름 뒤에 ?를 입력합니다. intentnull이면 앱은 extras 속성 액세스를 시도조차 하지 않으며 extras가 null이면 코드에서 getString()을 호출하려고 시도조차 하지 않습니다.

null 안전을 보장하기 위해 물음표가 필요한 속성을 어떻게 알 수 있나요? 유형 이름 뒤에 물음표가 오는지 느낌표가 오는지 알 수 있습니다.

2009463ce2fd82f2.png

마지막으로 주의할 점은 실제 문자가 getString으로 검색되어 String?를 반환하므로 toString()을 호출하여 null이 아닌 String인지 확인한다는 것입니다.

이제 앱을 실행하여 세부정보 화면으로 이동하면 각 문자의 단어 목록이 표시됩니다.

c465ef280fe3792a.png

삭제

두 코드가 모두 인텐트를 실행하고 선택된 문자를 검색하여 extra 이름 'letter'를 하드코딩합니다. 이 작은 예에서는 이런 방법이 작동하지만 추적할 인텐트 extras가 훨씬 많은 큰 앱에는 최상의 접근 방식이 아닙니다.

단순히 'letter'라는 상수를 만들어도 되지만 앱에 더 많은 인텐트 extras를 추가할 때 다루기 어려워질 수 있습니다. 그리고 이 상수를 어떤 클래스에 배치할 건가요? 문자열은 DetailActivityMainActivity에서 모두 사용됩니다. 코드를 체계적으로 유지하면서 여러 클래스에서 사용할 수 있는 상수를 정의할 방법이 필요합니다.

다행히 유용한 Kotlin 기능을 사용하면 컴패니언 객체라는 클래스의 특정 인스턴스 없이 상수를 구분하여 사용할 수 있습니다. 컴패니언 객체는 다른 객체(예: 클래스의 인스턴스)와 비슷합니다. 그러나 프로그램 기간에 컴패니언 객체의 인스턴스는 하나만 존재하므로 싱글톤 패턴이라고도 합니다. 이 Codelab에서 다루지 않는 싱글톤의 사용 사례가 수없이 많지만 지금은 상수를 구성하여 DetailActivity 외부에서 액세스할 수 있도록 하는 방법으로 컴패니언 객체를 사용합니다. 먼저 컴패니언 객체를 사용하여 'letter' extra의 코드를 리팩터링합니다.

  1. DetailActivityonCreate 바로 위에 다음을 추가합니다.
companion object {

}

object 키워드를 사용한다는 점만 빼고 클래스를 정의하는 방법과 비슷합니다. companion 키워드도 있습니다. 즉, 키워드가 DetailActivity 클래스와 연결되어 있으므로 별도의 유형 이름을 지정하지 않아도 됩니다.

  1. 중괄호 내에서 문자 상수 속성을 추가합니다.
const val LETTER = "letter"
  1. 새 상수를 사용하려면 다음과 같이 onCreate()에서 하드 코딩 문자 호출을 업데이트합니다.
val letterId = intent?.extras?.getString(LETTER).toString()

평소처럼 점 표기법을 사용하여 참조하지만 상수는 DetailActivity에 속합니다.

  1. LetterAdapter로 전환하고 새 상수를 사용하도록 putExtra 호출을 수정합니다.
intent.putExtra(DetailActivity.LETTER, holder.button.text.toString())

이제 모두 준비되었습니다. 리팩터링을 통해 코드를 읽고 유지하기가 더 쉬워졌습니다. 이 상수나 추가하는 다른 상수를 변경해야 하면 한곳에서만 변경하면 됩니다.

컴패니언 객체에 관한 자세한 내용은 객체 표현식 및 선언에 관한 Kotlin 문서를 참고하세요.

대부분의 경우 자체 앱에서 특정 활동을 표시합니다. 그러나 실행하려는 활동이나 앱을 알 수 없는 때도 있습니다. 세부정보 화면에서 각 단어는 단어의 사용자 정의를 보여주는 버튼입니다.

이 예에서는 Google 검색에서 제공하는 사전 기능을 사용합니다. 그러나 새 활동을 앱에 추가하는 대신 기기의 브라우저를 실행하여 검색 페이지를 표시합니다.

그렇다면 Android의 기본 브라우저인 Chrome에서 페이지를 로드할 인텐트가 필요할 수 있나요?

꼭 그렇지는 않습니다.

타사 브라우저를 선호하는 사용자도 있을 수 있습니다. 또는 휴대전화가 제조업체에서 사전 설치한 브라우저와 함께 제공됩니다. Google 검색 앱을 설치했거나 타사 사전 앱을 설치했을 수도 있습니다.

사용자가 어떤 앱을 설치했는지 확실히 알 수는 없습니다. 사용자가 단어를 어떤 방식으로 찾으려고 하는지도 추측할 수 없습니다. 이때 암시적 인텐트를 사용하면 가장 좋습니다. 앱이 작업 유형에 관한 정보를 시스템에 제공하면 시스템은 이 작업으로 실행할 일을 파악하여 필요에 따라 사용자에게 추가 정보를 요청합니다.

다음을 실행하여 암시적 인텐트를 만듭니다.

  1. 이 앱의 경우 Google에서 단어를 검색합니다. 첫 번째 검색결과는 단어의 사전 정의입니다. 모든 검색에 동일한 기본 URL이 사용되므로 이 URL을 자체 상수로 정의하는 것이 좋습니다. DetailActivity에서 컴패니언 객체를 수정하여 새 상수 SEARCH_PREFIX를 추가합니다. 이 URL이 Google 검색의 기본 URL입니다.
companion object {
   const val LETTER = "letter"
   const val SEARCH_PREFIX = "https://www.google.com/search?q="
}
  1. 그런 다음 WordAdapter를 열고 onBindViewHolder() 메서드의 버튼에서 setOnClickListener()를 호출합니다. 먼저 검색어의 URI를 만듭니다. parse()를 호출하여 String에서 URI를 만들 때 문자열 형식을 사용하여 단어가 SEARCH_PREFIX에 추가되도록 해야 합니다.
holder.button.setOnClickListener {
    val queryUrl: Uri = Uri.parse("${DetailActivity.SEARCH_PREFIX}${item}")
}

URI가 무엇인지 궁금하다면 URI는 오타가 아니고 Uniform Resource Identifier를 나타냅니다. 이미 알고 있겠지만 URL 또는 Uniform Resource Locator는 웹페이지를 가리키는 문자열입니다. URI는 형식에 관한 좀 더 일반적인 용어입니다. 모든 URL은 URI이지만 모든 URI가 URL인 것은 아닙니다. 예를 들어 전화번호의 주소 등 다른 URI는 tel:로 시작하지만 이는 URL이 아닌 URN, 즉 Uniform Resource Name으로 간주됩니다. 두 가지를 모두 나타내는 데 사용되는 데이터 유형을 URI라고 합니다.

828cef3fdcfdaed.png

여기 자체 앱에서는 어떤 활동 참조도 없습니다. 최종적으로 어떻게 사용되는지 나타내지 않고 URI를 제공할 뿐입니다.

  1. queryUrl를 정의한 후 새 intent 객체를 초기화합니다.
val intent = Intent(Intent.ACTION_VIEW, queryUrl)

컨텍스트와 활동을 전달하는 대신 URI와 함께 Intent.ACTION_VIEW를 전달합니다.

ACTION_VIEW는 URI(이 경우 웹 주소)를 사용하는 일반적인 인텐트입니다. 그러면 시스템은 사용자의 웹브라우저에서 URI를 열어 이 인텐트를 처리할 수 있습니다. 다른 인텐트 유형은 다음과 같습니다.

  • CATEGORY_APP_MAPS - 지도 앱을 실행합니다.
  • CATEGORY_APP_EMAIL - 이메일 앱을 실행합니다.
  • CATEGORY_APP_GALLERY - 갤러리(사진) 앱을 실행합니다.
  • ACTION_SET_ALARM - 백그라운드에서 알람을 설정합니다.
  • ACTION_DIAL - 전화를 겁니다.

자세한 내용은 흔히 사용되는 인텐트 문서를 참고하세요.

  1. 마지막으로 앱에서 특정 활동을 실행하지 않더라도 startActivity()를 호출하고 intent를 전달하여 시스템에 다른 앱을 실행하라고 지시합니다.
context.startActivity(intent)

이제 앱을 실행하고 단어 목록으로 이동하여 단어 중 하나를 탭하면 기기가 URL로 이동합니다(또는 설치된 앱에 따라 옵션 목록을 표시함).

정확한 동작은 사용자마다 달라서 코드를 복잡하게 하지 않고 모두에게 원활한 환경을 제공합니다.

명시적 인텐트와 암시적 인텐트를 추가하여 앱을 완전히 탐색할 수 있게 만들었으므로 이제 메뉴 옵션을 추가하여 사용자가 문자의 목록 및 그리드 레이아웃 간에 전환할 수 있도록 합니다.

이제는 알고 있겠지만 여러 앱의 화면 상단에 이 바가 있습니다. 앱 바라고 하며 앱 이름을 표시하는 것 외에도 앱 바는 맞춤설정할 수 있고 여러 유용한 기능(예: 유용한 작업 바로가기나 더보기 메뉴)을 호스팅할 수 있습니다.

dda2fc115721ca96.png

이 앱의 경우 완전한 메뉴를 추가하지는 않지만 맞춤 버튼을 앱 바에 추가하여 사용자가 레이아웃을 변경할 수 있도록 하는 방법을 알아봅니다.

  1. 먼저 그리드 및 목록 보기를 나타내는 아이콘 두 개를 가져와야 합니다. '뷰 모듈'(이름: ic_grid_layout)과 '뷰 목록'(이름: ic_linear_layout)이라는 클립 아트 벡터 애셋을 추가합니다. 머티리얼 아이콘 추가를 다시 확인해야 하면 이 페이지의 안내를 참고하세요.

44c530717478f2e6.png

  1. 앱 바에 표시되는 옵션과 사용할 아이콘을 시스템에 알리는 방법도 필요합니다. res 폴더를 마우스 오른쪽 버튼으로 클릭하고 New > Android Resource File을 선택하여 새 리소스 파일을 추가하면 됩니다. Resource TypeMenu로 설정하고 File Namelayout_menu로 설정합니다.

c4f83806a1aa121b.png

  1. OK를 클릭합니다.
  2. res/Menu/layout_menu를 엽니다. layout_menu.xml의 내용을 다음으로 바꿉니다.
<menu xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
   <item android:id="@+id/action_switch_layout"
       android:title="@string/action_switch_layout"
       android:icon="@drawable/ic_linear_layout"
       app:showAsAction="always" />
</menu>

메뉴 파일의 구조는 매우 간단합니다. 레이아웃이 개별 뷰를 보유하는 레이아웃 관리자로 시작하듯이 메뉴 xml 파일은 개별 옵션을 포함하는 메뉴 태그로 시작합니다.

메뉴에는 버튼이 하나만 있고 속성은 몇 가지가 있습니다.

  • id: 뷰와 마찬가지로 메뉴 옵션에는 코드에서 참조할 수 있도록 ID가 있습니다.
  • title: 이 텍스트는 실제로 이 경우에 표시되지 않지만 스크린 리더에서 메뉴를 식별하는 데 유용할 수 있습니다.
  • icon: 기본값은 ic_linear_layout입니다. 그러나 버튼이 선택될 때 그리드 아이콘을 표시하기 위해 사용 설정되거나 사용 중지됩니다.
  • showAsAction: 시스템에 버튼 표시 방법을 알려줍니다. 항상으로 설정되어 있으므로 이 버튼은 앱 바에 항상 표시되고 더보기 메뉴에 속하지 않습니다.

물론 속성을 설정했다고 해서 메뉴가 실제로 어떤 작업을 실행하는 것은 아닙니다.

메뉴가 작동하도록 하려면 여전히 코드를 MainActivity.kt에 추가해야 합니다.

메뉴 버튼이 작동하는 모습을 보려면 MainActivity.kt에서 몇 가지 작업을 실행해야 합니다.

  1. 먼저 앱의 레이아웃 상태를 추적하는 속성을 만드는 것이 좋습니다. 이렇게 하면 레이아웃 버튼 전환이 더 쉬워집니다. 기본값을 true로 설정합니다. 선형 레이아웃 관리자를 기본적으로 사용하기 때문입니다.
private var isLinearLayoutManager = true
  1. 사용자가 버튼을 전환할 때 항목 목록을 항목 그리드로 전환하려고 합니다. recycler 뷰에 관해 알아본 내용을 떠올려보면 여러 다양한 레이아웃 관리자가 있고 그중 하나인 GridLayoutManager가 단일 행에서 여러 항목을 허용합니다.
private fun chooseLayout() {
    if (isLinearLayoutManager) {
        recyclerView.layoutManager = LinearLayoutManager(this)
    } else {
        recyclerView.layoutManager = GridLayoutManager(this, 4)
    }
    recyclerView.adapter = LetterAdapter()
}

여기서는 if 문을 사용하여 레이아웃 관리자를 할당합니다. layoutManager를 설정하는 것 외에도 이 코드는 어댑터를 할당합니다. LetterAdapter는 목록 및 그리드 레이아웃에 모두 사용됩니다.

  1. xml로 처음 메뉴를 설정할 때 정적 아이콘을 제공했습니다. 그러나 레이아웃을 전환한 후에는 아이콘을 업데이트하여 목록 레이아웃으로 다시 전환하는 새 기능을 반영해야 합니다. 여기서는 다음 번에 탭할 때 버튼이 다시 전환될 레이아웃에 따라 선형 및 그리드 레이아웃 아이콘을 설정하기만 하면 됩니다.
private fun setIcon(menuItem: MenuItem?) {
   if (menuItem == null)
       return

   // Set the drawable for the menu icon based on which LayoutManager is currently in use

   // An if-clause can be used on the right side of an assignment if all paths return a value.
   // The following code is equivalent to
   // if (isLinearLayoutManager)
   //     menu.icon = ContextCompat.getDrawable(this, R.drawable.ic_grid_layout)
   // else menu.icon = ContextCompat.getDrawable(this, R.drawable.ic_linear_layout)
   menuItem.icon =
       if (isLinearLayoutManager)
           ContextCompat.getDrawable(this, R.drawable.ic_grid_layout)
       else ContextCompat.getDrawable(this, R.drawable.ic_linear_layout)
}

아이콘은 isLinearLayoutManager 속성에 따라 조건부로 설정됩니다.

앱이 실제로 메뉴를 사용하려면 두 메서드를 더 재정의해야 합니다.

  • onCreateOptionsMenu: 옵션 메뉴를 확장하여 추가 설정을 실행합니다.
  • onOptionsItemSelected: 버튼이 선택될 때 실제로 chooseLayout()을 호출합니다.
  1. 다음과 같이 onCreateOptionsMenu를 재정의합니다.
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
   menuInflater.inflate(R.menu.layout_menu, menu)

   val layoutButton = menu?.findItem(R.id.action_switch_layout)
   // Calls code to set the icon based on the LinearLayoutManager of the RecyclerView
   setIcon(layoutButton)

   return true
}

간단합니다. 레이아웃을 확장한 후 레이아웃에 따라 setIcon()을 호출하여 아이콘이 올바른지 확인합니다. 이 메서드는 Boolean을 반환합니다. 여기서는 옵션 메뉴를 만들려고 하므로 true를 반환합니다.

  1. 코드를 몇 줄만 더 추가하여 onOptionsItemSelected와 같이 구현합니다.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
   return when (item.itemId) {
       R.id.action_switch_layout -> {
           // Sets isLinearLayoutManager (a Boolean) to the opposite value
           isLinearLayoutManager = !isLinearLayoutManager
           // Sets layout and icon
           chooseLayout()
           setIcon(item)

           return true
       }
       //  Otherwise, do nothing and use the core event handling

       // when clauses require that all possible paths be accounted for explicitly,
       //  for instance both the true and false cases if the value is a Boolean,
       //  or an else to catch all unhandled cases.
       else -> super.onOptionsItemSelected(item)
   }
}

메뉴 항목을 탭할 때마다 호출되므로 어떤 메뉴 항목을 탭하는지 확인해야 합니다. 위의 when 문을 사용합니다. idaction_switch_layout 메뉴 항목과 일치하면 isLinearLayoutManager의 값을 무효화합니다. 그런 다음 chooseLayout()setIcon()을 호출하여 적절하게 UI를 업데이트합니다.

앱을 실행하기 전에 한 가지 작업이 더 있습니다. 레이아웃 관리자와 어댑터가 이제 chooseLayout()에서 설정되므로 onCreate()에서 코드를 교체하여 새 메서드를 호출해야 합니다. 변경 후 onCreate()는 다음과 같이 표시됩니다.

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)

   val binding = ActivityMainBinding.inflate(layoutInflater)
   setContentView(binding.root)

   recyclerView = binding.recyclerView
   // Sets the LinearLayoutManager of the recyclerview
   chooseLayout()
}

이제 앱을 실행하면 메뉴 버튼을 사용하여 목록 보기와 그리드로 보기 간에 전환할 수 있습니다.

이 Codelab의 솔루션 코드는 아래 프로젝트에 있습니다.

이 Codelab의 코드를 가져와서 Android 스튜디오에서 열려면 다음을 실행합니다.

코드 가져오기

  1. 제공된 URL을 클릭합니다. 브라우저에서 프로젝트의 GitHub 페이지가 열립니다.
  2. 프로젝트의 GitHub 페이지에서 Code 버튼을 클릭하여 대화상자를 엽니다.

5b0a76c50478a73f.png

  1. 대화상자에서 Download ZIP 버튼을 클릭하여 컴퓨터에 프로젝트를 저장합니다. 다운로드가 완료될 때까지 기다립니다.
  2. 컴퓨터에서 파일을 찾습니다(예: Downloads 폴더).
  3. ZIP 파일을 더블클릭하여 압축을 해제합니다. 프로젝트 파일이 포함된 새 폴더가 만들어집니다.

Android 스튜디오에서 프로젝트 열기

  1. Android 스튜디오를 시작합니다.
  2. Welcome to Android Studio 창에서 Open an existing Android Studio project를 클릭합니다.

36cc44fcf0f89a1d.png

참고: Android 스튜디오가 이미 열려 있는 경우 File > New > Import Project 메뉴 옵션을 대신 선택합니다.

21f3eec988dcfbe9.png

  1. Import Project 대화상자에서 압축 해제된 프로젝트 폴더가 있는 위치로 이동합니다(예: Downloads 폴더).
  2. 프로젝트 폴더를 더블클릭합니다.
  3. Android 스튜디오가 프로젝트를 열 때까지 기다립니다.
  4. Run 버튼 11c34fc5e516fb1c.png을 클릭하여 앱을 빌드하고 실행합니다. 예상대로 작동하는지 확인합니다.
  5. Project 도구 창에서 프로젝트 파일을 살펴보고 앱이 설정된 방식을 확인합니다.
  • 명시적 인텐트는 앱의 특정 활동으로 이동하는 데 사용됩니다.
  • 암시적 인텐트는 특정 작업(예: 링크 열기, 이미지 공유)에 상응하며 시스템이 인텐트 처리 방법을 결정하도록 합니다.
  • 메뉴 옵션을 사용하면 버튼과 메뉴를 앱 바에 추가할 수 있습니다.
  • 컴패니언 객체는 재사용 가능한 상수를 유형의 인스턴스가 아닌 유형과 연결하는 방법을 제공합니다.

인텐트를 실행하려면 다음을 실행합니다.

  • 컨텍스트 참조를 가져옵니다.
  • 명시적인지 암시적인지에 따라 활동이나 인텐트 유형을 제공하는 Intent 객체를 만듭니다.
  • putExtra()를 호출하여 필요한 데이터를 전달합니다.
  • intent 객체를 전달하는 startActivity()를 호출합니다.