App bar
안드로이드에서 App bar라고 하는것은 Material Design에서 정의하는 상단의 바를 말한다.

Material Design에서는 하단 App bar도 나오지만, 그리 익숙한 형태는 아니다.

여기서는 이전부터 익숙한 상단 바에 대해서만 다룰 것이다. App bar의 각 명칭은 다음과 같다.

1. Container
2. Navigation icon (optional)
3. Title (optional)
4. Action items (optional)
5. Overflow menu (optional)
App bar라고 하는 것은 앞에서 말한대로 Material Design의 디자인 명칭이고, 실제 구현되는건 ActionBar 또는 Toolbar 이다. 이게 좀 혼란스러운 부분이 많은데, 히스토리를 좀 알아야 한다. ActionBar API reference를 살펴보자.
ActionBar and Toolbar
ActionBar는 Android 3.0 (API level 11)이 나오면서 Activity에 붙은 형태로 등장했다. 이후로 쭈욱 ActionBar가 사용되다가 Android L (API level 21)부터 Toolbar widget을 사용하여 ActionBar를 사용할 수 있게 됐다. ActionBar는 Activity에 종속적인 문제와 함께, 안드로이드 버전에 따라 다르게 동작할 여지가 있었다. Toolbar는 Activity 종속성도 없지만, support library를 통해 지원이 시작되어 호환성 문제도 없었다. 그럼 그냥 Toolbar를 쓰면될거 같은데, 왜 ActionBar형태로 사용하도록 지원하는걸까? ActionBar 형태의 테마는 여전히 사용되고 Backward compatibility도 지원해야 하므로 Toolbar를 생성해서 사용하되, 이걸 ActionBar에 호환되도록 만든 것으로 보인다.
결론은 ActionBar는 과거의 유물이고 Toolbar로 대체 되는게 맞겠지만, App bar로서 ActionBar도 여전히 유효한 형태이고 예전버전 호환성을 유지해야 하기 때문에 1) ActionBar형태를 사용하되, 구현은 Toolbar로 하는 방법과 2) ActionBar를 사용하지 않도록 설정하고 Toolbar만을 사용하는 두가지 전부 유효한 상황이다.
참고로 ActionBar의 사용유무는 테마에서 결정된다. 예를들어 다음과 같이 NoActionBar 테마를 사용하면 ActionBar를 사용하지 않는다.
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
</style>
</resources>
Toolbar를 이용한 ActionBar 형태의 App bar 구현
기본 테마를 사용하면, Toolbar를 생성하고 이를 ActionBar형태로 사용하도록 되어 있다. 최신 안드로이드 스튜디오에서 새 프로젝트를 생성하면 사용되는 방법이다. 가장 보편적이므로 새 프로젝트를 만들어 살펴보자.
제일 먼저, Toolbar를 사용하기 위한 라이브러리가 필요하다.
dependencies {
...
implementation 'androidx.appcompat:appcompat:1.3.1'
...
}
기본적으로 Activity가 AppCompatActivity를 상속받으므로, 별도로 추가하지 않아도 이미 포함되어 있을 것이다. 이제 Activity의 테마부분을 살펴보자.
AndroidManifest.xml 에 Activity를 살펴보면, 다음과 같이 NoActionBar 테마가 사용된다.
<activity
...
android:theme="@style/Theme.JustDonit.NoActionBar">
...
</activity>
NoActionBar 테마를 찾아들어가면 다음과 같이 되어 있음을 볼 수 있다.
<style name="Theme.JustDonit.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
windowsActionBar를 false로 설정함으로서, ActionBar를 disable 시키고 있는게 보인다.
아니, ActionBar를 없애버리면서 어떻게 ActionBar 형태를 사용한다는 얘긴가? 그것은 Toolbar가 알아서 해준다. activity_main.xml 레이아웃 파일을 살펴보자. Toolbar widget이 포함된걸 확인 할 수 있다.
...
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/Theme.JustDonit.PopupOverlay" />
...
layout_width는 Actiivty의 폭, layout_height는 ActionBar 크기를 이용하는게 보인다. 아, ActionBar의 위치에 Toolbar가 위치하는 거구나. 하지만, 이대로는 그냥 Toolbar일 뿐이다. ActionBar처럼 동작하려면 마지막 단계가 남았다. MainActivity 클래스를 살펴보자.
override fun onCreate(savedInstanceState: Bundle?) {
...
setSupportActionBar(binding.toolbar)
...
setSupportActionBar() 함수가 보일 것이다. 인자로 툴바를 넘겨주면, 이것을 ActionBar로 만들어준다. 끝! 이제 기존 ActionBar와 동일한 형태의 App bar를 볼 수 있다.
주의사항 : setSupportActionBar()로 지정한경우, ActionBar를 가져오고 싶을 때, getActionBar()는 항상 null을 리턴할 것이다. 대신에 getSupportActionBar()를 사용해야 한다.
App Bar의 사용과 Toolbar의 사용에 대해서도 정리를 해야하는데, 공식문서가 잘되어 있으니 링크만 남기겠다. 위에 서술한 내용도 보니까 공식문서에 다 있음. 쳇. 그나저나, 워드프레스 5.8 업데이트하고 에디터 왤케 느려터졌지?
- 기본적인 App bar 의 추가와 사용방법 : https://developer.android.com/training/appbar
- Fragment에서 Appbar와 작업하기 : https://developer.android.com/guide/fragments/appbar
특정 Fragment 에서 App bar 없애기
새로 포스팅을 할까 하다가 원래 기존포스팅에 포함될 예정이었고 간단한 내용이어서 업데이트를 하기로 했다.
기본 프로젝트 생성해서 사용하면 ActionBar 형태의 App bar를 사용하게 된다. 이는 사용중인 Navigation 라이브러리와도 잘 작동한다. 그런데, 새로운 데이터를 추가하는 화면을 만들다보니, App bar를 없애고 간단한 Toolbar를 사용하고 싶었다. 예를들면 트위터 앱에 새글 쓰기라던지, Keep 메모앱에 새글쓰기처럼. 언급한 앱들은 아예 ActionBar 형태를 버리고, 모든 화면에서 Toolbar를 이용하는 것 같은데, 어쩌면 가장 탁월한 선택일 수도 있겠다는 생각이 든다. 하지만, 나는 기본적으로 제공하는 ActionBar 형태를 버리고 싶진 않았기에 입력 fragment에서만 ActionBar를 제거하고 별도의 Toolbar를 사용하기로 결정.
기본 ActionBar를 특정 fragment에서 없애는건 간단하다. 그냥 숨기면된다.
override fun onStart() {
super.onStart()
(activity as AppCompatActivity?)!!.supportActionBar!!.hide()
}
override fun onStop() {
super.onStop()
(activity as AppCompatActivity?)!!.supportActionBar!!.show()
}
fragment가 start될 때 숨기고 stop될 때 보여주는 코드이다. 문제는 Fragment가 전환될 때 ActionBar가 일단 보인 다음 제거가 된다는 것이다. hide코드의 위치를 바꿔봐도 문제는 해결되지 않았다. ActionBar는 Activity에 속해있기 때문에, Fragment간 이동시점에 Activity에서 뭔가 처리를 해줘야 할 것 같았다. 그렇게 찾다가 알게된게 navController의 addOnDestinationChangedListener() 메소드. Fragment의 destination이 변경되어 전환시 콜백으로 불리도록 한다. 이 외에도 Navigation 사용시 UI를 다루는 관련 내용이 다음의 링크에 잘 나와있다.
Update UI components with Navigation UI : https://developer.android.com/guide/navigation/navigation-ui
Listen for navigation events : https://developer.android.com/guide/navigation/navigation-ui#listen_for_navigation_events
링크의 내용으로 ActionBar를 show/hide 하는 방법은 다음과 같이 구현된다.
navController.addOnDestinationChangedListener{ _, destination, _ ->
when (destination.id) {
R.id.EditAddFragment -> {
supportActionBar?.hide()
Timber.i("Navigation dest. changed: EditAddFragment. $supportActionBar")
}
else -> {
supportActionBar?.show()
Timber.i("Navigation dest. changed: else fragment. $supportActionBar")
}
}
}
addOnDestinationChangedListener 에 넘겨지는 Listner는 NavController.OnDestinationChangedListener 인데, 인자가 원래 3개다. 여기서 사용하지 않는 NavController와 Bundle타입 arguments 는 사용하지 않으므로 under score로 처리해줬다.
리스너 내용을 보면, destination.id를 구별하여 특정 destination으로 이동시 ActionBar를 show/hide 하고 있는게 보인다. 여기서 처리를 하면 화면 전환시, ActionBar가 보였다 사라지는 현상없이 깔끔하게 처리된다.