본문 바로가기

Android

FloatingActionButton Anchor to Appbar

FAB 버튼을 Appbar에 부착하기


CollapsingToolbarLayout은 Toolbar를 확장된 영역으로 보여주고 스크롤 시 축소시켜 기존의 Toolbar기능을 보여주는 레이아웃이다.
이때 Floating Action Button을 Toolbar에 부착하면 Toolbar의 크기가 줄어들 때 FAB가 사라지도록 할 수 있다.

 


 

두 화면에서 다른 방식으로 FAB를 사용하기


현재 2개의 다른 Fragment를 가지고 있다. TaskListFragment와 AddEditTaskFragment이다.

TaskListFragment는 RecyclerView를 갖고 있으며 아래로 길게 스크롤하며 사용될 가능성이 높다.
그러므로 스크롤 시에 FAB이 Toolbar에 숨겨지는 Anchor 모드보다는 보편적으로 사용되는 화면 최상위 레이어에 떠있는 형태가 사용하기 좋을 것이다. (이름 그대로 View 최상위에 Floating해 있는 상태로 사용.)

 

AddEditTaskFragment는 필요한 정보를 입력받아서 Task를 저장하는 화면이다.
고정으로 화면의 하단에 위치하는 일반적인 버튼보다는 Toolbar 옆에 위치하면 눈에 잘띄고 직관적이기 때문에 Save버튼으로 FAB을 사용하려고 한다.
Floating의 성격을 그대로 가지고 있기 때문에 Task의 정보량이 늘어나서 스크롤을 하더라도 사용자가 바로 접근할 수 있는 장점도 있다.

 


 

3D로 보는 뷰의 계층


 

최선을 다해 그렸다.

1 Activity - Multiple Fragment 구조를 사용하므로 Activity에서 Appbar구역을 제외한 지역이 NavHostFragment 구역이 된다.
그리고 다수의 프래그먼트는 NavHostFragment 영역 위에서만 그려진다.

 

 

 

평면으로 보면 이런식으로 사용자와 가깝게 배치되어 있는 형태이다.
Floating Action Button은 기본 elevation 값이 높기 때문에 화면 위에 둥둥 떠있는 것으로 묘사된다.

 

 


 

Fragment가 FAB을 소유할 때


두 개의 서로 다른 Fragment는 각자 Fab의 사용방법이 다르다.
하나는 Fab을 Appbar와 함께 Anchor 되길 원하고, 다른 하나는 그냥 Floating 되어 있길 원한다.

 

그렇다면 각 Fragment의 xml에서 직접 사용할 Fab을 정의해보자.

Activity에는 Fab이 없고 Fragment에서 Fab을 소유한다.
그리고 각자 Fragment에서 원하는대로 Fab을 사용한다.

 

 

첫번째 문제는 TaskListFragment에서 발생한다.
TaskListFragment에서 사용하는 RecyclerView는 리스트의 크기가 커지면 스크롤이 아래로 크게 늘어난다.

 

Fab버튼은 CoordinatorLayout을 사용해서 layout_gravity값을 사용해서 위치를 정렬하는데.
Bottom , Center , End, Start, Top과 같은 가중치를 사용한다.

 

 

위의 그림을 보면 Fab을 화면의 우측 하단에 Floating 시키려면 Bottom | End 값을 사용하게 되는데
리스트가 커졌을 경우 NavHostFragment의 영역 밖의 하단에 위치한다.

 

즉, 스크롤을 내리지 않으면 Floating 되어 있는 Fab이 화면에서 즉시 보이지 않는다. NestedScrollView에 FloatingActionButton을 정의하면 Floating이라는 제 기능을 하지 못한다는 것을 알 수 있다.

 

 

 

프로젝트 생성 시에 디폴드 정의처럼 Activity가 Floating을 소유하고 NavHostFragment 영역 우측하단에 위치하도록 정의되어야 원하는데로 동작할 것이다.

 

 

 

두번째 문제는 AddEditTaskList에서 발생한다.
이번에는 Anchor의 문제이다. Anchor 시키려는 대상이 Appbar이기 때문에 문제가 발생한다.


Anchor는 부모 뷰에 부착할 수 없기 때문이다. 자식 뷰에 부착해서 사용하는 것이다.

Appbar는 Activity의 소속이고, AddEditTask는 Activty의 내부 NavHostFragment 위에 그려지는 뷰이기 때문에
AddEditTask에 정의된 Fab은 MainActivity의 Appbar에 부착될 수 없다.

 

Floating Action Button의 정의를 제대로 살펴보지 않고 여러 방법으로 시도하다가 많은 시간을 소비했다.
다음에는 구현이 막힐 경우 공식 문서를 다시 한 번 잘 읽어보도록 하자.

 

 

 

 

멀티 Activity를 사용하는 구조라면 고민 없이 Activity에 새로운 Appbar를 그리면 된다.
각 Activity는 독립적으로 그려지고 쌓아올려지기 때문이다.

 

이 지점까지 막혔을 때는 Activity에서 Appbar를 지우고 NoActionbar 스타일의 Empty View를 만든 후
NavHostFragment의 영역을 Activity 전체 영역으로 한다.


그리고 각 Fragment에서 Appbar를 각각 가지는 구조로 변경해야하는지에 대해서 고민했다.

하지만 위 방법은 Activity를 새로 그리는 방법보다 불편하며, 1 Activity - Multi-Fragment 구조의 장점을 전혀 얻지 못하는듯 했다.

 

 


 

FAB은 Activity에 존재해야한다.


Floating Action Button이 자식에게만 Anchoring하다는 것을 알았을 때 든 생각은 FAB는 Activity에 존재해야 한다는 것이다.

모든 Fragment는 Activity 위에 그려질 것이고, 각 Fragment는 FAB를 조작하여 사용할 방법이 있을것이라 생각했다.

 

1. 화면 전환을 캐치할 수 있어야한다.

Navigation Component는 NavController를 통해서 화면 전환을 한다.
앞에서 Navigation Backstack을 확인하기 위해 사용한 NavController.OnDestinationChangedListenr()를 활용해보자.

이 리스너는 Destination이 변경될 때 알아챌 수 있다.


내부에서 destination.getLabel()을 사용하면 현재 보여지는 화면이 어떤 Fragment인지 구별할 수 있다.

TaskLsitFragment인지 AddEditTaskFragment인지에 따라서 FAB를 알맞게 조작해주면 될 것이다.

 

2. AddEditTaskFragment이라면 Fab -> Anchor to Appbar 한다.

Fab의 정렬 Args는 두가지 타입이있다. AnchorGravitiylayout_gravity이다.
앵커포인트가 없이 뷰 위에 Floating해서 사용할 때는 layout_gravity를 사용해야한다.
앵커 시킨 뒤 정렬을 위해서는 layout_gravity값을 없앤 뒤에 anchorGravitiy 값을 추가해야한다.

두 개의 정렬 인자를 동시에 사용하지 않는 것이 포인트이다.

 

 

3. TaskLsitFragment라면 Anchor를 해제해야한다.

역으로 TaskListFragment가 실행될 때는 Fab이 우측하단에 위치하도록 앵커를 해제한다.

 

이때 주의점은 navController의 생명주기이다.


navController는 fragment보다 생명주기가 긴 객체이다.

 

그러므로 MainActivity에서 NavController.OnDestinationChangedListenr()를 등록하면
액티비티 한곳에서 Fragment가 전환될 때마다 콜백을 받을 수 있다.

 

하지만 액티비티에서 AddEditTaskFragment전환 시 Fab을 앵커시키는 코드를 작성시 에러가 발생한다.
반드시 현재 보여지고 있는 Fragment마다 Listener를 등록하고 Fab을 조작해야한다.

 

그리고 NavController는 생명주기가 길기 때문에 Fragment가 소멸되어도 리스너는 계속 남아있다.
로그를 찍어보면 AddEditTaskLsitFragment가 5번 실행되고 소멸되면 (새로운 Task를 5개 추가한 상황과 동일)
리스너가 5개 쌓여서 5번 콜백을 수행하는 것을 확인할 수 있다.

 

 

Fragment가 소멸할때 호출하는 onDestroy()에서 반드시 NavController.removeOnDestinationChangedListener 처리를 해주어야 리소스 누수를 막을 수 있다.

 

 


 

Fab의 setIamgeResource() 버그 발생.


Fab의 앵커만 탈부착 하는 것이 아니라 Fragment 상황에 맞는 Icon으로 변경해주어야 한다.

하지만 Fragment전환과 함께 수동으로 Drawable을 변경한 후에 icon이 사라지는 현상이 발생했다.

 

fab.show() 콜한 경우 setImageResource() 내부에 imageMatrixScale 값이 0으로 처리되는 버그가 있다고 한다.

 

간단 해결책은 hide & show를 한 번 더 콜해주는 방법이다.
그러면 아이콘이 사라지지 않고 잘 표현된다.

 

 


 

Summary


이번에 느낀 점은 NavHostFragment를 사용하는 현재 구조와 CollapsingToolbarLayout은 궁합이 좋지 못한 것같다.

이 구조는 Activity에서 Appbar 영역을 소유하고, Fragment는 분리된 구조이기 때문에
Fragment에서 Appbar를 조작할 일이 많은 경우 상당히 번거로운 작업이 늘어난다.

 

그래서 구글이 NavigationComponent를 소개할 때 여러가지 Actionbar 스타일 중에 CollapsingToolbar에 대한 예제가 적은 것이 아니었나 생각해본다.

 

CollapsingToolbarLayout은 Toolbar영역을 더 활용할 수 있기 때문에 선택했는데
각 Fragment에서 CollapsingToolbar를 다양하게 조작하는 것은 기피해야할 것 같다. (여러 Fragment에서 공유하는 Toolbar이기 때문이다.)

'Android' 카테고리의 다른 글

자동 Keypad 보여주기  (0) 2019.10.25
NavIndicator와 NavigationUp  (0) 2019.10.25
Navigation Component 사용하기  (0) 2019.10.03
Fragment에 리스트 출력하기 Part.2  (0) 2019.09.29
Fragment에 리스트 출력하기 Part.1  (0) 2019.09.29