App Bar 와 Fragment
App bar는 보통 액티비티 환경에서 이루어지지만 상황에 따라
1. 액티비티에서 사용되는앱 바를 프래그먼트 상황에 맞게 커스텀하여 사용할 수 있다.
2. 액티비티와 관련없이 Fragment에서 단독으로 사용할 수 있다.
활동이 소유하고 있는 App Bar (앱 바)를
프래그먼트에서 그대로 또는 커스텀하여 사용
앱 바는 호스트 활동에서 소유한 경우가 거의 대부분이다. 활동에서 이미 앱 바를 소유하고 있으면 프래그먼트가 프래그먼트 생성 중에 호출된 프레임워크 메서드를 재정의 하여 앱 바와 상호작용할 수 있다.
따라서 해당 방식은 활동에서 앱 바를 소유하고 있는 경우에만 적용된다.
1.활동에 등록
앱 바 프래그먼트가 옵션 메뉴를 채우는 데 참여하고 있다고 시스템에 알려야 한다.
이렇게 하려면 다음 예와 같이 프래그먼트의 onCreate(Bundle)메서드에서 setHasOptionsMenu(true)를 호출한다.
class ExampleFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
setHasOptionMenu(true)는 프래그먼트가 메뉴 관련 콜백을 수신하려 한다고 시스템에 알린다.
메뉴 관련 이벤트(생성,클릭 등) 발생하면 이벤트 처리 메서드가 먼저 활동에서 호출된 후 프래그먼트에서 호출된다.
하지만 애플리케이션 로직은 이 순서(활동 호출 -> 프래그먼트 호출)를 따르지 않는다. 하나의 활동이 여러 프래그먼트를 호스팅하는 상황이면, 각 프래그먼트에서 커스텀 된 메뉴 옵션을 제공한다. 이 경우 콜백 순서는 프래그먼트가 추가된 순서에 따라 달라진다.
2.메뉴 확장
프래그먼트의 메뉴를 앱 바의 옵션 메뉴에 병합하려면 프래그먼트에서 onCreateOptionMenu()를 재정의 한다.
이 메서드는 현재 앱 바 메뉴(=menu)와 MenuInflater(=inflater)를 매개변수로 수신한다. 메뉴 인플레이터를 사용하여 프래그먼트의 메뉴 인스턴스를 만들어 현재 메뉴에 병합(=앱 바의 옵션 메뉴)한다.
class ExampleFragment : Fragment() {
...
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.sample_menu, menu)
}
}
3.클릭 이벤트 처리
옵션 메뉴에 참여하는 모든 활동과 프래그먼트는 터치에 응답한다. 프래그먼트의 onOptionsItemSelected()는 선택된 메뉴 항목을 매개변수로 수신하고, 부울 값을 반환하여 터치가 사용되었는지 나타낸다.
활동이나 프래그먼트가 onOptionsItemSelected()에서 TRUE를 반환하면 APP BAR에 연관된 다른 프래그먼트는 콜백을 수신하지 않는다.
class ExampleFragment : Fragment() {
...
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_settings -> {
// navigate to settings screen
true
}
R.id.action_done -> {
// save profile changes
true
}
else -> super.onOptionsItemSelected(item)
}
}
}
참고 : Fragment는 위 3.메뉴확장 에서 onCreateOptionMenu() 호출을 통해 추가된 메뉴 항목만 처리해야 한다. 활동 소유 앱 바를 사용하는 경우, 활동은 위로 버튼과 Fragment에서 추가하지 않은 메뉴 항목의 클릭 이벤트를 처리해야 한다.
4.동적으로 메뉴 수정
버튼을 숨기거나 표시하거나 아이콘을 변경하는 로직은 onPrepareOptionsMenu()에 배치해야 한다.
이 메서드는 메뉴가 표시되는 각 인스턴스 직전에 호출된다.
즉, 일반적으로 앱 바에 옵션메뉴를 Display할 때 onPrepareOptionsMenu() 호출 -> onCreateOptionMenu() 호출
class ExampleFragment : Fragment() {
...
override fun onPrepareOptionsMenu(menu: Menu){
super.onPrepareOptionsMenu(menu)
val item = menu.findItem(R.id.action_done)
item.isVisible = isEditing
}
}
따라서 내가 R.id.action_done이라는 메뉴 아이템을 수정하고 싶다면
이렇게 onPrepareOptionsMenu() 메서드에 수정하고 싶은 코드를 짠다.
위의 소스코드에서는 R.id.action_done 이라는 메뉴를 숨기거나 보여지게 하려는 코드.
class ExampleFragment : Fragment() {
...
fun updateOptionsMenu() {
isEditing = !isEditing
requireActivity().invalidateOptionsMenu()
}
}
이런식으로 updateOptionMenu() 메서드를 만들고, 해당 메서드가 어떤 버튼이 눌려지면 호출되게끔 만든다.
해당 메서드에서는 isEditing이 toggle 되면서 R.id.action_done 아이템이 사라지거나 보여지게끔 만들 수 있다.
활동의 invalidateOptionsMenu() 메서드를 호출하면 시스템에서 다시 메뉴를 호출하려는 작업을 한다.
이때 그럼 다시 invalidateOptionsMenu() -> onPrepareOptionsMenu() -> onCreateOptionsMenu() 와 같은 식으로 호출이 되기 때문에 메뉴를 동적으로 변경할 수 있다.
프래그먼트에서 독립적으로
소유하고 있는 App Bar (앱 바)
앱의 대부분의 화면에 앱 바가 필요하지 않거나 한 화면에 상당히 다른 앱 바가 필요하다면
프래그먼트 레이아웃에 Toolbar를 추가하면 된다.
프래그먼트의 뷰 계층 구조 내 어디라도 Toolbar를 추가할 수 있지만 일반적으로 화면 상단에 유지해야 한다.
프래그먼트에서 Toolbar를 사용하려면 다른 뷰에서와 마찬가지로 id를 제공하고 프래그먼트에서 ID참조를 가져온다.
<androidx.appcompat.widget.Toolbar
android:id="@+id/myToolbar"
... />
layout.xml에서 해당 Toolbar의 Id를 코드에서 참조하여 사용해야 한다.
※ 프래그먼트가 단독으로 소유하는 앱 바를 사용하는 경우, Toolbar API를 직접 사용하는 것이 좋다.
setSupportActionBar() 또는 Fragment 메뉴 API(onCreateOptionsMenu(), onOptionsItemSelected(), etc..)
를 사용하지 말 것. -> 이는 활동 소유 앱 바 에만 적합하다.
1.메뉴 확장
Toolbar 편의 메서드 inflateMenu(int)는 메뉴 리소스의 ID를 매개변수로 사용한다.
XML메뉴 리소스를 툴바로 확장하려면 다음 예와 같이 resId를 이 메서드에 전달한다.
class ExampleFragment : Fragment() {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
viewBinding.myToolbar.inflateMenu(R.menu.sample_menu)
}
}
또 다른 메뉴 리소스를 확장하려면 새 메뉴의 resId를 사용하여 메서드를 다시 호출.
새 메뉴 항목이 메뉴에 추가되고, 기존 메뉴 항목은 수정되거나 삭제되지 않는다.
만약 기존 메뉴 세트를 교체하고 싶다면, 새 메뉴의 id를 사용하여 inflateMenu(int)를 호출하기 전에 toolbar.menu.clear() 메서드를 호출하여 메뉴를 삭제한다.
class ExampleFragment : Fragment() {
...
fun clearToolbarMenu() {
viewBinding.myToolbar.menu.clear()
}
}
2.클릭 이벤트 처리
setOnMenuItemClickListener() 메서드를 사용하여 툴바에 직접 OnMenuItemClickListener를 전달할 수 있다.
OnMenuItemClickListener는 사용자가 툴바 또는 연결된 오버플로 끝 부분에 표시되는 작업 버튼에서 메뉴 항목을 선택할 때마다 호출된다.
선택된 MenuItem은 리스너의 onMenuItemClick() 메서드에 전달된다.
class ExampleFragment : Fragment() {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
viewBinding.myToolbar.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_settings -> {
// Navigate to settings screen
true
}
R.id.action_done -> {
// Save profile changes
true
}
else -> false
}
}
}
}
3.동적으로 메뉴 수정
프래그먼트에서 앱 바를 소유하면 다른 뷰에서와 마찬가지로 런타임 시 Toolbar 를 수정할 수 있다.
활동이 소유한 앱 바에서 다뤘던 것처럼
class ExampleFragment : Fragment() {
...
fun updateToolbar() {
isEditing = !isEditing
val saveItem = viewBinding.myToolbar.menu.findItem(R.id.action_done)
saveItem.isVisible = isEditing
}
}
toolbar.menu.findItem(int) 를 이용하여 메뉴를 찾고 해당 메뉴를 참조하여 메뉴를 수정할 수 있다.
4.탐색 아이콘 추가
탐색 버튼이 있다면 툴바의 시작 부분에 표시된다.
툴바에서 탐색 아이콘을 설정하면 아이콘이 표시된다. 다음 예와 같이 사용자가 탐색 버튼을 클릭할 때 마다 호출되는 탐색별 onClickListener() 을 설정할 수도 있다
class ExampleFragment : Fragment() {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
myToolbar.setNavigationIcon(R.drawable.ic_back)
myToolbar.setNavigationOnClickListener { view ->
// Navigate somewhere
}
}
}
toolbar.setNavigationIcon () 을 통해 탐색 아이콘을 설정하고,
toolbar.setNavigationOnClickListener { view -> } 를 통해 onClickListener 설정.