Unit 3: Navigation (2)

quokka·2021년 11월 8일
0

Android Basics in Kotlin

목록 보기
11/25
post-thumbnail

Words app 개요

  • 시작화면에서는 알파벳 목록을 보여준다.
  • 알파벳을 클릭하면 해당 알파벳으로 시작하는 단어 목록을 보여준다.
  • 단어를 클릭하면 구글에 해당 단어를 검색한 창으로 연결된다.
  • 알파벳 목록은 linear layout과 grid layout 두가지로 보여준다. (오른쪽 상단 아이콘을 누르면 layout이 바뀐다)

여기에서 Words 프로젝트의 틀을 다운 받는다.
starter 브랜치에서 클론하거나 클론 후 starter 브랜치에 checkout하면 된다.

git clone -b starter https://github.com/google-developer-training/android-basics-kotlin-words-app/tree/starter

Intent

Implicit intent vs Explicit intent

  • Explicit intent는 구체적인 실행을 알 수 있고 실행하는 것이 앱 자체의 화면인 경우가 많다.
  • Implicit intent는 좀 더 추상적이다. 시스템에 작업 유형을 알려주면 시스템이 요청 처리 방법을 파악하는 방식.

Explicit Intent

  • Intent 실행 시 putExtra()로 값 전달하기
  • extras로 전달된 값 변수에 담기

LetterAdapter

먼저 LetterAdapter.kt에서 버튼을 누르면 단어 목록 activity로 이동하도록 코드를 작성해야 한다. ( A 버튼을 누르면 A로 시작하는 단어 목록이 있는 액티비티로 이동 )

override fun onBindViewHolder(holder: LetterViewHolder, position: Int) {
        val item = list.get(position)
        holder.button.text = item.toString()
        holder.button.setOnClickListener {
            val context = holder.view.context
            val intent = Intent(context, DetailActivity::class.java)
            intent.putExtra(DetailActivity.LETTER, holder.button.text.toString())
            context.startActivity(intent)
        }
    }
  1. holder.buttononClickListener를 설정한다.
  2. context 참조를 가져온다.
  3. Intent를 만들어 context와 실행할 activity를 담는다.
  4. putExtra 메소드를 이용해서 인텐트로 값을 전달할 수 있다.
    어떤 알파벳을 눌렀는지 전달하기 위해서 putExtra 메서드를 호출하고, name과 버튼에 적힌 알파벳을 값으로 전달한다.
  5. startActivity로 실행한다.

DetailActivity

DetailActivityonCreate에서 인텐트로 전달된 LETTER값을 letterId 변수에 담는다.

val letterId = intent?.extras?.getString("letter").toString()
  • extras 속성은 Bundle 유형이고 인텐트에 전달된 모든 extras에 액세스하는 방법을 제공한다.
  • ?를 쓰는 이유
    intent 및 extras 속성은 null을 허용하므로 값이 있을 수도 있고 없을 수도 있기 때문이다.
    intent가 null이면 앱은 extras 속성 액세스를 시도할 수 없고,
    extras가 null이면 코드에서 getString()을 호출할 수 없다.

DetailActivity에서는 WordAdater로 단어 목록을 생성한다.
WordAdapterletterId가 전달된다.

companion object

코틀린의 companion object를 사용하면 클래스의 특정 인스턴스 없이 상수를 구분하여 사용할 수 있다 (🤔)
프로그램 기간에 컴패니언 객체의 인스턴스는 하나만 존재하므로 싱글톤 패턴이라고도 한다.
정의하는 방법은 클래스랑 비슷하다.

companion object {
        const val LETTER = "letter"
}

onCreate 위에 companion object를 추가하고 letterId를 하드코딩된 "letter" 대신 상수 LETTER를 사용하도록 수정한다.

val letterId = intent?.extras?.getString(LETTER).toString()

Implicit intent

WordAdapter

단어를 클릭하면 웹 브라우저를 실행해 클릭한 단어를 검색한 결과가 뜨도록 한다.

    override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
        val item = filteredWords[position]
        val context = holder.view.context
        holder.button.text = item
        holder.button.setOnClickListener {
            val queryUrl: Uri = Uri.parse("${DetailActivity.SEARCH_PREFIX}${item}")
            val intent = Intent(Intent.ACTION_VIEW, queryUrl)
            context.startActivity(intent)
        }
    }
  1. filteredWordsList<String> 타입으로 words 리스트에서 조건에 맞는 단어들을 선택해 담은 리스트다. item에 단어를 담는다.
val words = context.resources.getStringArray(R.array.words).toList()
        filteredWords = words
            .filter { it.startsWith(letterId, ignoreCase = true) }
            .shuffled()
            .take(5)
            .sorted()
  1. WordAdapteronBindViewHolder()에서 버튼을 누르면 실행할 onClickListener를 추가한다. queryUrlSERACH_PREFIXitem을 합쳐 검색할 주소 URI를 만든다.
    참고로 URI는 Uniform Resource Identifier, URL은 Uniform Resource Locator. URI가 URL보다 큰 개념이다.
  2. 인텐트 생성

위에서 명시적 인텐트에서는 Intent(context, DetailActivity::class.java)로 Intent를 생성했는데, 이번에는 Intent.ACTION_VIEWURI를 전달한다.

val intent = Intent(Intent.ACTION_VIEW, queryUrl)

ACTION_VIEW는 시스템이 사용자의 웹브라우저에서 URI를 처리하는 인텐트다.

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

  1. startActivity로 인텐트를 실행한다.
context.startActivity(intent)
  1. 이제 단어 버튼을 클릭하면 웹브라우저가 실행되면서 해당 단어를 구글에 검색한 결과가 뜬다.

LayoutManager 전환

  • 오른쪽 상단에 아이콘을 추가해서 알파벳 목록을 보여주는 2가지 레이아웃을 제공한다. 아이콘을 클릭해 grid layout 또는 linear layout으로 전환할 수 있다.
  • 상단 앱 바를 생성한다.

아이콘 추가

vector asset을 이전에 강의에서 배운 방식으로 drawable 아래 ic_grid_layout.xmlic_linear_layout.xml을 추가한다.

앱 바 생성

res폴더에서 New > Android Resource File을 선택해 새 리소를 파일을 추가한다. 이때 Resource TypeMenu, 파일 이름은 layout_menu로 설정한다.
res폴더 아래 Menu 디렉토리가 생성되었고 그 아래 layout_menu.xml 파일이 생성되었다.

<!-- 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"
        android:iconTint="@color/white"
        app:showAsAction="always" />
</menu>
  • icon: 기본값은 ic_linear_layout으로 설정되어 있다.
  • showAsAction: 시스템에 버튼 표시 방법을 알려준다. always로 설정되어 있으므로 이 버튼은 앱 바에 항상 표시된다.

MainActivity 수정

1. 레이아웃 상태 추적

앱의 레이아웃 상태를 추적하는 속성을 만든다.

private var isLinearLayoutManager = true

2. chooseLayout()

사용자가 버튼을 클릭하면 레이아웃 관리자를 전환할 함수를 만든다.

기존에 onCreate에서 LayoutManager와 adapter를 설정하는 코드는 삭제하고 그 위치에 chooseLayout()을 호출한다.

private fun chooseLayout() {
    if (isLinearLayoutManager) {
        recyclerView.layoutManager = LinearLayoutManager(this)
    } else {
        recyclerView.layoutManager = GridLayoutManager(this, 4)
    }
    recyclerView.adapter = LetterAdapter()
}

GridLayoutManager에서 4는 칼럼수다.

3. setIcon()

아이콘 변경하는 함수를 만든다.

private fun setIcon(menuItem: MenuItem?) {
   if (menuItem == null)
       return
   menuItem.icon =
            if (isLinearLayoutManager)
                ContextCompat.getDrawable(this, R.drawable.ic_grid_layout)
            else ContextCompat.getDrawable(this, R.drawable.ic_linear_layout)
}

4. onCreateOptionsMenu

메뉴를 적용하려면 메서드 2개를 재정의해야 한다. Ctrl + O로 메서드를 찾을 수 있다.

onCreateOptionsMenu: 메뉴(앱 바)를 inflate 한다.

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
   menuInflater.inflate(R.menu.layout_menu, menu)

   val layoutButton = menu?.findItem(R.id.action_switch_layout)
   setIcon(layoutButton)

   return true
}

4. onOptionsItemSelected

onOptionsItemSelected: 버튼이 선택될 때 실제로 chooseLayout()을 호출한다.

override fun onOptionsItemSelected(item: MenuItem): Boolean {
   return when (item.itemId) {
       R.id.action_switch_layout -> {
           isLinearLayoutManager = !isLinearLayoutManager
           chooseLayout()
           setIcon(item)
           return true
       }
       else -> super.onOptionsItemSelected(item)
   }
}

이제 모든 기능이 완성되었다! 아이콘 클릭으로 레이아웃 변경도 되고, A버튼을 클릭하면 A로 시작하는 단어 목록이 나오고, 단어를 클릭하면 웹 브라우저에 검색한 결과가 나온다.

0개의 댓글