
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 실행 시
putExtra()로 값 전달하기- extras로 전달된 값 변수에 담기
먼저 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)
}
}
holder.button의 onClickListener를 설정한다.context 참조를 가져온다. Intent를 만들어 context와 실행할 activity를 담는다.putExtra 메소드를 이용해서 인텐트로 값을 전달할 수 있다.putExtra 메서드를 호출하고, name과 버튼에 적힌 알파벳을 값으로 전달한다. startActivity로 실행한다. DetailActivity의 onCreate에서 인텐트로 전달된 LETTER값을 letterId 변수에 담는다.
val letterId = intent?.extras?.getString("letter").toString()
Bundle 유형이고 인텐트에 전달된 모든 extras에 액세스하는 방법을 제공한다.?를 쓰는 이유DetailActivity에서는 WordAdater로 단어 목록을 생성한다.
WordAdapter에 letterId가 전달된다.
companion object
코틀린의 companion object를 사용하면 클래스의 특정 인스턴스 없이 상수를 구분하여 사용할 수 있다 (🤔)
프로그램 기간에 컴패니언 객체의 인스턴스는 하나만 존재하므로 싱글톤 패턴이라고도 한다.
정의하는 방법은 클래스랑 비슷하다.
companion object {
const val LETTER = "letter"
}
onCreate 위에 companion object를 추가하고 letterId를 하드코딩된 "letter" 대신 상수 LETTER를 사용하도록 수정한다.
val letterId = intent?.extras?.getString(LETTER).toString()
- 기기의 웹 브라우저 실행하기 (common 인텐트)
단어를 클릭하면 웹 브라우저를 실행해 클릭한 단어를 검색한 결과가 뜨도록 한다.
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)
}
}
filteredWords는 List<String> 타입으로 words 리스트에서 조건에 맞는 단어들을 선택해 담은 리스트다. item에 단어를 담는다. val words = context.resources.getStringArray(R.array.words).toList()
filteredWords = words
.filter { it.startsWith(letterId, ignoreCase = true) }
.shuffled()
.take(5)
.sorted()
WordAdapter의 onBindViewHolder()에서 버튼을 누르면 실행할 onClickListener를 추가한다. queryUrl에 SERACH_PREFIX와 item을 합쳐 검색할 주소 URI를 만든다.
위에서 명시적 인텐트에서는 Intent(context, DetailActivity::class.java)로 Intent를 생성했는데, 이번에는 Intent.ACTION_VIEW와 URI를 전달한다.
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- 전화를 겁니다.
context.startActivity(intent)
- 오른쪽 상단에 아이콘을 추가해서 알파벳 목록을 보여주는 2가지 레이아웃을 제공한다. 아이콘을 클릭해 grid layout 또는 linear layout으로 전환할 수 있다.
- 상단 앱 바를 생성한다.
vector asset을 이전에 강의에서 배운 방식으로 drawable 아래 ic_grid_layout.xml과 ic_linear_layout.xml을 추가한다.
res폴더에서 New > Android Resource File을 선택해 새 리소를 파일을 추가한다. 이때 Resource Type은 Menu, 파일 이름은 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로 설정되어 있으므로 이 버튼은 앱 바에 항상 표시된다.앱의 레이아웃 상태를 추적하는 속성을 만든다.
private var isLinearLayoutManager = true
사용자가 버튼을 클릭하면 레이아웃 관리자를 전환할 함수를 만든다.
기존에 onCreate에서 LayoutManager와 adapter를 설정하는 코드는 삭제하고 그 위치에 chooseLayout()을 호출한다.
private fun chooseLayout() {
if (isLinearLayoutManager) {
recyclerView.layoutManager = LinearLayoutManager(this)
} else {
recyclerView.layoutManager = GridLayoutManager(this, 4)
}
recyclerView.adapter = LetterAdapter()
}
GridLayoutManager에서 4는 칼럼수다.
아이콘 변경하는 함수를 만든다.
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)
}
메뉴를 적용하려면 메서드 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
}
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로 시작하는 단어 목록이 나오고, 단어를 클릭하면 웹 브라우저에 검색한 결과가 나온다.
