^(코딩캣)^ = @"코딩"하는 고양이;

[단편 Android 개발 정리] ActionBar 대신 Toolbar로 대체하기(2021년 버전)

API/Android
2021. 7. 7. 13:39

단편 Android 개발 정리

다음 포스트: 사이드 메뉴(DrawerLayout) 구현하기 (2021년 버전)

 

ActionBar 대신 Toolbar로 대체하기

2021년 기준 최신 버전의 Android Studio에서 앱의 ActionBar 대신 Toolbar로 대체하는 방법을 단계별로 정리해보겠다.

 

1 단계. Empty Project 생성하기

Android Studio를 열고 아무 액티비티도 갖지 않는 빈 프로젝트를 하나 생성한다. 프로젝트의 이름와 도메인은 적절하게 입력한다.

아무것도 Activity도 포함하지 않는 새 프로젝트를 선택한다.

그 다음 /res/values/themes/themes.xml를 열면 style 태그가 ActionBar로부터 파생되었음을 지정하는 내용이 있을 것이다. 일단 확인만 하고 놔 둔다.

기본 스타일이 ActionBar로부터 파생되고 있다.

 

2 단계. 빈 액티비티 추가하기

빈 액티비티를 하나 추가한다. 이름은 적절히 부여한다.

activity_main.xml

아무것도 수정하지 않은 맨 처음 상태의 빈 액티비티라면 이렇게 생겼을 것이다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".MainActivity">

</androidx.constraintlayout.widget.ConstraintLayout>

 

MainActivity.kt

또한 비하인드 코드는 이렇게 생겼다.

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)
	}
}

 

3 단계. ActionBar를 지우기

2 단계에서 아무것도 건드리지 않고 그냥 실행을 해 보겠다. 그러면 다음과 같이 상단에 ActionBar를 포함하는 빈 화면이 나타난다.

기본 설정에 따라 ActionBar가 추가되어 있는 빈 액티비티

 

여기에서부터 하나씩 수정해나가겠다. 앞서 확인했던 /res/values/themes/themes.xml 파일을 다시 열고 앱이 정의하고 있는 기본 테마를 ActionBar에서 NoActionBar 계열로 바꾼다. 그러면 AndroidManifest.xmlapplication에서 themes.xml에서 정의된 테마를 참조하고 그 결과가 application의 일부인 MainActivity에도 반영된다.

NoActionBar 테마를 적용한다.

정리하자면,

themes.xmlTheme.MaterialComponents.DayNight.NoActionBar로부터 파생된 Theme.Testapp라는 이름의 새 테마를 정의한다.

AndroidManifest.xmlTheme.Testapp를 참조하여 앱의 전반적인 테마를 적용한다.

MainActivity도 여기에 영향을 받아 ActionBar가 사라진다.

NoActionBar가 적용된 앱의 모습

 

4 단계. ActionBar가 사라진 자리에 Toolbar 넣기

이전 버전의 Android Studio에서는 Toolbar의 이름이 다음과 같이 android.support.v7.widget.Toolbar였다.

<android.support.v7.widget.Toolbar
	android:id="..." />

그러나 2021년 현재 최신 Android Studio에는 이 코드가 오류가 난다. Toolbar의 이름은 다음과 같이 바뀌었다. 물론 gradle에서 의존성을 수정하고 번거로운 세팅을 하면 옛날 이름으로 쓸 수 있겠으나, 이제부터 개발하는 앱은 기본 설정을 사용하는 것으로...

<androidx.appcompat.widget.Toolbar
	android:id="..." />

다시 나타난 것은 ActionBar가 아니라 Toolbar임

이제 MainActivty의 비하인드 코드 측에도 레이아웃에 새로 추가된 Toolbar를 ActionBar처럼 사용하라고 지정해 주어야 한다. MainActivty.kt를 열고 onCreate 메서드의 맨 끝 부분에 다음의 내용을 적는다.

val toolbar = findViewById<Toolbar>(R.id.toolbar)
//setSupportActionBar(toolbar)

onCreate 메소드의 전체적인 내용은 다음과 같아야 할 것이다. super.onCreate를 호출하고 setContentView를 실행한 다음에 새로 작성하는 문장들이 등장해야 할 것이다.

override fun onCreate(savedInstanceState: Bundle?) {
	super.onCreate(savedInstanceState)
	setContentView(R.layout.activity_main)

	val toolbar = findViewById<Toolbar>(R.id.toolbar)
	setSupportActionBar(toolbar)
}

그러면 ActionBar처럼 Toolbar에도 앱의 이름이 나타나는 것을 볼 수 있다. 텍스트의 색이 조금 달라 보이는 것은 기분 탓일 것이다.

완성된 Toolbar의 모습

 

5 단계. 메뉴 추가하기

ActionBar와 달리 Toolbar는 View의 일종이기 때문에 버튼이나 메뉴 등 다양한 요소들의 기능과 위치를 제어하기가 편리하다.

 

메뉴 작성하기

프로젝트에 /res/menu 디렉토리를 하나 생성한다. 여기에 메뉴를 정의하는 XML 파일을 생성한다. 이 파일에 Toolbar에 넣을 메뉴들을 적는다.

<?xml version="1.0" encoding="utf-8"?>
<menu
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:id="@+id/action_item0"
        android:title="바나나"
        app:showAsAction="ifRoom" />
    <item android:id="@+id/action_item1"
        android:title="포도"
        app:showAsAction="never" />
    <item android:id="@+id/action_item2"
        android:title="딸기"
        app:showAsAction="never" />
    <item android:id="@+id/action_item3"
        android:title="포도"
        app:showAsAction="never" />
</menu>

여기서 app:showAsAction 속성은 메뉴 항목을 Toolbar 우측 [더 보기...] 버튼을 눌러야 나타나게 할 것인지 여부를 지정한다.

  • never는 해당 항목을 무조건 [더 보기...] 버튼을 눌러야만 나타나게 한다.
  • ifRoom은 화면의 폭이 충분할 때는 Toolbar에 직접 나타내고 좁을 때는 [더 보기...] 버튼을 통해 보여지게 한다.
  • always라고 해서 화면의 폭과 무관하게 무조건 Toolbar에 직접 나타내는 옵션이 있는데 이는 IDE에서 권장하지 않고 ifRoom을 사용하도록 유도하고 있다.

설명보다는 직접 보고 정리하면 되겠다.

메뉴를 정의하는 xml 파일

 

레이아웃에 메뉴 적용하기

레이아웃 파일의 androidx.appcompat.widget.Toolbar로 돌아가서 다음의 속성을 추가하면 Toolbar에 메뉴가 적용된다.

app:menu="@menu/파일.xml"

"바나나" 항목은 app:showAsAction="ifRoom"으로 지정했으므로 Toolbar에 바로 나타난다.

ifRoom 값을 지정한 메뉴 항목

app:showAsAction="never"로 지정한 항목은 [더 보기...] 버튼을 눌러야 나타난다.

never 값을 지정한 메뉴 항목

 

6 단계. 메뉴 항목마다 이벤트 작성하기

Toolbar와 menu의 겉 모양은 대충 만들었으니 비하인드 코드를 작성할 차례이다. kotlin 소스 코드를 열고 액티비티에 다음과 같이 메소드를 오버라이드한다.

class MainActivity: AppCompatActivity() {
	// ...
	override fun onCreateOptionsMenu(menu: Menu?): Boolean {
		Log.d("menu", "onCreateOptionsMenu")
		menuInflater.inflate(R.menu.menu_toolbar, menu)
		return super.onCreateOptionsMenu(menu)
	}

	override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
		Log.d("menu", "onPrepareOptionsMenu")
		return super.onPrepareOptionsMenu(menu)
	}

	override fun onOptionsItemSelected(item: MenuItem): Boolean {
		var result = false

		when (item.itemId) {
			R.id.action_item0 -> { Log.d("menu", "바나나 선택함"); result = true }
			R.id.action_item1 -> { Log.d("menu", "포도 선택함"); result = true }
			R.id.action_item2 -> { Log.d("menu", "딸기 선택함"); result = true }
		}

		return result;
	}
}

각 메소드가 언제 실행되는지 Logcat으로 찍어보면 알 수 있다

onCreateOptionsMenu는 액티비티에 딸려 있는 메뉴를 최초로 구성할 때 1회만 호출된다. 이것을 재정의하여 menuInflater.inflate를 호출해준다. menuInflater.inflate 메소드가 무엇인가 하니, 리소스로부터 메뉴 XML 파일을 불러와서 메뉴로 띄워주는 역할을 한다. 레이아웃에서 지정했던 app:menu="@menu/파일.xml"과 동일한 것이다. 다만 레이아웃에서 app:menu="@menu/파일.xml"을 굳이 적지 않고 menuInflater.inflate 메소드만 호출해도 앱 실행 시 메뉴가 나타났지만, 반대로 menuInflater.inflate 메소드를 호출하지 않고 레이아웃에게 app:menu="@menu/파일.xml"만을 지정하면 메뉴가 나타나지 않았다. 그냥 그렇다는 거다.

onPrepareOptionsMenu는 메뉴를 최초로 구성할 때는 물론이고 [더 보기...] 버튼을 클릭하여 메뉴가 펼쳐질 때마다 호출된다. 사용자에게 메뉴 항목을 보여주기 전 뭔가를 메뉴 항목을 업데이트할 때 유용할 것으로 보인다.

onOptionsItemSelected는 메뉴 항목을 클릭/터치할 때 호출된다. 어떤 메뉴가 클릭/터치되었는지는 매개변수로 넘어온 item: MenuItemmenu.itemId를 통해 식별 가능하다.

 

7 단계. 햄버거 버튼 만들기

이렇게까지 ActionBar를 굳이 ToolBar로 바꾸어서 버튼들을 추가하는 이유 중 하나는 사이드 바 메뉴, 일명 "햄버거 버튼(hamburger button)"을 만들기 위함일 것이다. 햄버거 버튼을 추가해 보겠다.

 

햄버거 이미지 준비하기

먼저 햄버거 아이콘을 준비한다. 직접 만들 필요도 없고, 구글링 통해 굳이 직접 구할 필요도 없다. Android Studio의 클립아트에 미리 준비되어 있다. drawable 디렉토리에 새로운 Vector(Image) Asset을 추가해본다.

New 메뉴에서 Vector(Image) Asset 항목 클릭

여기서는 벡터 이미지를 넣어보겠다. 클립아트 옆의 그림을 클릭하면 미리 준비된 아이콘들의 목록이 나타난다. 이 중에서 햄버거 아이콘(이름: menu)을 찾아서 클릭하고, 색상과 크기(일단은 넉넉하게 크기를 지정해 준다)를 적절하게 넣어주면 끝.

표시된 부분을 클릭
menu라고 이름이 붙은 아이콘 선택

그리고 onCreate 메소드로 가서 홈(Home) 버튼이 Toolbar에 생겨나도록 다음과 같이 문장을 작성한다. 앞서 만들어 본 메뉴 항목과 달리 햄버거 아이콘은 대체로 화면의 왼쪽에 위치할텐데, Toolbar의 왼쪽에 생기는 아이콘을 홈 버튼이라 한다.

class MainActivity : AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)

		val toolbar = findViewById<Toolbar>(R.id.toolbar)
		setSupportActionBar(toolbar)

		// 새로 추가된 두 줄
		supportActionBar?.setDisplayHomeAsUpEnabled(true)
		supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_menu)
	}
}

setDisplayHomeAsUpEnabled은 매개변수에 true를 전달함으로써 Toolbar에 홈 버튼이 나타나게 한다. setHomeAsUpIndicator은 홈 버튼으로 표시할 아이콘을 지정하는데, 위에서 클립아트를 통해 추가한 햄버거 아이콘의 아이디를 적으면 된다.

그러면 이와 같이 넉넉한 사이즈로 햄버거 아이콘이 나타난다

이런 모양을 원했던 것이 아니다. 그렇다면 화면 크기에 맞게 이미지 크기를 축소해주어야 한다. 여기서는 벡터 형식으로 아이콘을 추가했으므로 크기 조정이 매우 간편하다. 햄버거 아이콘 파일을 열고 폭(android:width)과 높이(android:height)를 ?attr/actionBarSize로 수정한다. 그러면 다음과 같이 제법 그럴듯한 아이콘이 만들어진다.

<vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="?attr/actionBarSize"
    android:tint="#FFFFFF"
    android:viewportHeight="24"
    android:viewportWidth="24"
    android:width="?attr/actionBarSize">
    <path android:fillColor="@android:color/white" android:pathData="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z" />
</vector>

한결 그럴듯한 햄버거 아이콘

이것도 크다 하면 마진(margin)을 좀 더 주는 식으로 줄일 수 있다. path element를 group으로 감싼 다음 scaleXscaleY1.0보다 작은 값으로 지정한다. 그러면 path가 표현하는 햄버거 아이콘이 좀 더 축소된다. 예를 들어 scaleX="0.8" scaleY="0.8"로 하면 가로와 세로를 본래의 80%로 축소시킨다.

좀 더 보기 좋아졌으나 뭔가 아쉬운 아이콘

크기는 좀 줄었는데 위치가 애매해졌다. 아이콘을 버튼 영역의 정중앙에 오도록 하려면 pivotXpivotY를 각각 android:viewportWidthM의 절반, android:viewportHeight의 절반으로 지정하면 된다.

<vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="?attr/actionBarSize"
    android:tint="#FFFFFF"
    android:viewportHeight="24"
    android:viewportWidth="24"
    android:width="?attr/actionBarSize">
    <group
        android:scaleX="0.8"
        android:scaleY="0.8"
        android:pivotX="12"
        android:pivotY="12">
        <path android:fillColor="@android:color/white" android:pathData="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z" />
    </group>
</vector>

딱 좋은 상태의 햄버거 아이콘

이 버튼을 터치했을 때의 이벤트 처리는 다음과 같이 작성한다.

class MainActivity : AppCompatActivity() {
	override fun onOptionsItemSelected(item: MenuItem): Boolean {
		var result = false

		when (item.itemId) {
			android.R.id.home -> { Log.d("menu", "햄버거 버튼"); result = true }
            // ...
		}

		return result;
	}
}

햄버거 버튼, 홈 버튼의 ID는 android.R.id.home으로 고정되어 있다.

카테고리 “API/Android”
more...