Android Design Support Library를 소개합니다

Cover image by Manual

Material Design

머티리얼 디자인은 Google I/O 2014에서 안드로이드 5.0(롤리팝)과 함께 처음 소개된 구글의 디자인 철학이자 가이드라인입니다. 모바일 뿐만 아니라 데스크탑을 아우르는 새로운 디자인 가이드라인을 제시하므로써 이전까지 구글이 내놓은 여러가지 제품에서 다소 제각각이었던 디자인들을 일관된 디자인으로 재편함으로써 통일된 UI/UX를 제공하겠다는 것이 목적입니다.

머티리얼 디자인에서 표면과 그림자는 물리적인 구조를 형성하여, 사용자들이 화면 상의 어떤 부분을 터치할 수 있고 움직일 수 있는지 쉽게 이해할 수 있도록 돕습니다. 현대적인 출판물 디자인 원칙이 반영되어 다른 부가 요소보다 컨텐츠 자체가 강조됩니다. 모든 움직임에는 의미가 있으며, 화면 요소들 간의 관계를 명확히 하고 세세한 디테일을 통해 사용자에게 이러한 관계를 알려줍니다.

"구글 디벨로퍼 코리아 블로그 - 머티리얼 디자인 (Material Design) 이란?"

머티리얼 디자인에 대한 간단한 설명은 "구글 디벨로퍼 코리아 블로그 - 머티리얼 디자인 (Material Design) 이란?"을, 머티리얼 디자인을 적용하기 위한 가이드라인은 Google Design을 참고하세요.

안드로이드와 머티리얼 디자인

I/O 2014에서 머티리얼 디자인과 함께 공개된 롤리팝에서는 기본적으로 이 새로운 머티리얼 디자인이 적용되어 있습니다.
Android 5.0 Lollipop Showcase from Android Central.

하지만 문제는 이 머티리얼 디자인을 직접 안드로이드 앱 개발에 구현하는 것이 매우 어렵다는 점이었습니다. v7-appcompat, cardview, recyclerview 등 일부 머티리얼 디자인을 적용하기 위한 서포트 라이브러리가 제공되었지만 그 기능이나 요소들이 매우 제한적이어서 별도의 커스텀 라이브러리를 사용해야 했습니다. 심지어 구글에서 만든 앱들조차도 제각각 다른 형태로 머티리얼 디자인을 적용하는 모습을 보여주었습니다.

안드로이드 디자인 서포트 라이브러리

개발자들의 이런 불만을 들어준 것일까요? 구글은 이번 Google I/O 2015에서 머티리얼 디자인을 더욱 쉽게 적용할 수 있는 'Android Design Support Library'를 공개했습니다. 덕분에 개발자들은 자신들의 앱에 머티리얼 디자인을 통일된 방식으로 좀 더 쉽게 적용할 수 있게 되었습니다. 이렇게 된 이상, 머티리얼 디자인을 적용하지 않을 이유는 없겠죠?

이제 빨리 이번 안드로이드 디자인 서포트 라이브러리에 포함된 요소들을 차례로 살펴보고, 이들을 어떻게 구현할지 간단히 소개해 보겠습니다.

시작하기

간단합니다. 다른 라이브러리들처럼 gradle에 dependency를 등록해 줍니다.
compile 'com.android.support:design:22.2.0'

물론 그 전에 SDK Manager를 통해서 Android Support Repository를 꼭 업데이트해야 합니다.

아직도 이클립스를 쓰시는 분들, 이제는 정말 안드로이드 스튜디오, 혹은 안드로이드 스튜디오의 기반이 되는 Intellij로 옮겨가실 때가 되었습니다. 이미 이클립스에 대한 SDK의 공식 지원이 끊긴지도 오래 되었습니다. 여기에서부터 천천히 시작해 보세요.

Snackbar

레퍼런스 / 디자인 가이드

SnackbarToast와 비슷하게 화면 하단에 간단한 메시지를 통해 어떤 동작에 대한 피드백을 줄 수 있는 요소입니다. 대부분의 동작와 사용법은 Toast와 비슷하지만, 화면 하단에 붙어 표시된다는 점과 메시지와 함께 액션(Action)을 포함할 수 있다는 점에서 차이가 있습니다.

표시되고 나면 사용자들은 (액션이 있는 경우) 액션을 취하거나, 좌우로 스와이프(swipe-to-dismiss)하여 스낵바를 숨길 수 있습니다. 물론 일정 시간이 지나면 스낵바는 자동으로 사라집니다.


Snackbar에는 액션을 추가할 수 있고, 나타나고 사라질때 애니메이션 효과가 적용됩니다.
from Google Material Design Guideline


물론 액션 없이 그냥 메시지만 표시할 수도 있습니다.
from Google Material Design Guideline

구현은 매우 간단합니다.

Snackbar.make(parentLayout, R.string.snackbar_text, Snackbar.LENGTH_LONG)  
  .setAction(R.string.snackbar_action, myOnClickListener)
  .show(); // show 까먹지 마세요!

Toast랑 무지 비슷하죠? 다만 차이점은 첫번째 인자로 view를 넘겨준다는 점입니다. 화면에 표시되고 있는 view중 아무거나 넣어 주면 Snackbar가 알아서 적당한 parent view를 찾아 Snackbar를 표시해줍니다.

혹시 기존의 ToastSnackbar로 옮기는 과정에서 view가 생성되기 전에 Snackbar를 보여주려고 한다면 당연히 문제가 생기게 됩니다. 이미 Activity의 View는 만들어졌지만, 내부 Fragment의 View가 아직 생성되지 않은 경우(ex. onCreate()) 다음과 같이 content view를 사용할 수 있습니다만, 추천하지는 않습니다. (급한 불을 꺼야 하는 경우에서만 사용하시고, 이 같은 상황에서는 Toast를 사용하는 것이 더 적합한 것일지도 모릅니다)

Snackbar.make(getActivity().findViewById(android.R.id.content), ...);  

만약 Snackbar에서 좌우로 밀어 숨기기(swipe-to-dismiss)나 위젯 자동 정렬(FloatingActionButton 자동 이동 등)의 기능을 사용하기 위해서는 아래에서 설명할 CoordinatorLayout을 최상위 레이아웃으로 사용하여야 합니다. 이 부분은 잠시 후 CoordinatorLayout을 설명하는 부분에서 좀 더 자세히 다루도록 하겠습니다.

TextInputLayout

레퍼런스 / 디자인 가이드

TextInputLayout은 기존의 EditText를 감싸 그 기능을 확장해 한층 더 업그레이드 해 주는 레이아웃입니다. 기존의 EditText는 글자를 입력하면 Hint Text가 사라지는 것과는 달리, TextInputLayout을 사용하면 EditText가 focus 될 때 Hint Text Text가 플로팅 레이블 형태로 좌측상단에 나타나 사용자가 입력하고 있는 내용이 뭔지 알 수 있게 됩니다.

미려한 애니메이션은 덤입니다. 강조색은 별도의 설정이 없으면 colorAccent로 설정됩니다.

역시나 구현은 매우 간단합니다. 다음과 같이 레이아웃에서 EditTextTextInputLayout으로 감싸줍니다.

<android.support.design.widget.TextInputLayout  
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <EditText
        android:id="@+id/edit_text_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textEmailAddress"
        android:hint="@string/hint_email" />
</android.support.design.widget.TextInputLayout>  

힌트를 표시하는 것 외에도 setError()를 호출하여 EditText 아래에 오류 메시지를 표시할 수 있습니다. setErrorEnabled()를 호출하면 EditText 아래에 에러 메시지를 표시하기 위한 공간이 할당됩니다. 에러 메시지가 나올 때 갑자기 레이아웃이 바뀌는 것을 방지하기 위해 뷰가 생성될 때 미리 함수를 호출해 놓는 것을 추천합니다.

setErrorEnabled(true);  
setError(getString(R.string.text_error_message));  

에러 메시지 표시하기

※주의: 라이브러리 버전 22.2.1 이하에서 TextInputLayout안의 EditTextOnFocusChangeListener를 새로 할당하면 애니메이션이 제대로 되지 않는 문제가 발생하고 있습니다. 다음 버전에서 고쳐진다고 합니다만, 그 때까지는 다음과 같이 기존에 있던 OnFocusChangeListener를 보존하면서 새로운 OnFocusChangeListener를 붙여주어야 합니다.

TextInputLayout inputLayout = (TextInputLayout)findViewById(R.id.input_layout);  
EditText editText = inputLayout.getEditText();  
final OnFocusChangeListener existing = editText.getOnFocusChangeListener();

editText.setOnFocusChangeListener(new OnFocusChangeListener() {  
    public void onFocusChange(View view, boolean focused) {
        existing.onFocusChange(view, focused);
        // Your custom logic
    }
});

참고: http://stackoverflow.com/questions/31197312/textinputlayout-not-animating-when-onfocuschangelistener-applied-to-wrapped-edit

Floating Action Button (FAB)

레퍼런스 / 디자인 가이드


A floating action button recentering a map view.
from Google Material Design Guideline

Floating Action Button은 인터페이스의 가능한 동작(ex. 새로운 아이템 목록에 추가)을 나타내는 동그란 버튼 컴포넌트입니다. 기본 색상은 별도의 설정이 없다면 역시 colorAccent로 적용되고, 2가지 크기(Normal, Mini)를 지원합니다.

2가지 크기의 FAB. Normal(왼쪽)과 Mini(오른쪽). (from Google Material Design Guideline)

  • Normal (56dp): 가장 일반적으로 사용되는 크기입니다. 주로 화면 우측 하단이나 AppBar에 붙어서 사용됩니다.
  • Mini (40dp): 소형. 화면에 보여지고 있는 다른 구성 요소들과의 시각적 조화(visual continuity)가 중요할 경우 이상적입니다.

뿐만 아니라 FloatingActionButtonImageView를 확장하기 때문에 XML의 android:src 속성이나 setImageDrawable()과 같은 메서드를 이용하여 FloatingActionButton에 표시될 아이콘을 설정할 수 있습니다.

  • fabSize: FAB의 크기 설정(normal 혹은 mini)
  • backgroundTint: 배경(background)에 적용할 tint의 색상을 설정합니다
  • rippleColor: 버튼을 눌렀을 때 나타나는 ripple effect의 색상을 설정합니다
  • src: 버튼 안에 사용할 아이콘을 설정합니다

FAB를 레이아웃에 추가하는 것 역시 매우 쉽습니다.

<android.support.design.widget.FloatingActionButton  
     android:id="@+id/fab"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:src="@drawable/ic_plus"
     app:fabSize="normal" />


레퍼런스 / 디자인 가이드

네비게이션 드로워(Navigation Drawer)는 사용자들이 앱 내에서 각기 다른 섹션들을 쉡게 오갈 수 있도록 해 주는 중요한 요소입니다. 이 네비게이션 드로워가 얼마나 일관성 있게 잘 디자인 되어 있는지의 여부가 앱의 편의성을 판가름하는 중요한 요소가 됩니다. 이는 특히 처음 사용하는 사용자에게 매우 크게 작용합니다.

새로 추가된 NavigationView는 기존의 DrawerLayout 내부에 위치하면서 이런 네비게이션 드로워를 구현하는데 필요한 프레임워크와 함께 메뉴 리소스를 통해 쉽게 네비게이션 메뉴를 설정할 수 있도록 해 줍니다.

우선 다음과 같이 레이아웃을 구성해 줍니다.

<android.support.v4.widget.DrawerLayout  
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <!-- your content layout -->
    <FrameLayout
        android:id="@+id/main_content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <android.support.design.widget.NavigationView
        android:id="@+id/navigation_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/navigation_header"
        app:menu="@menu/drawer" />

</android.support.v4.widget.DrawerLayout>  

Navigation View에서 살펴볼 핵심 요소는 2가지가 있습니다.

Header Layout

app:headerLayout 속성은 드로워(Drawer)의 헤더 색션의 레이아웃을 정의하는 데 쓰입니다. 이 부분은 네비게이션 아이템들의 바로 위에 위치하게 되는데요, 다음과 같이 주로 프로필 섹션으로 사용됩니다.

프로필 드로워 헤더 섹션 (from Google Material Design Guideline)

app:menu 속성은 드로워 내부에 사용될 네비게이션 메뉴를 지정하는 데 사용됩니다. 물론 inflateMenu() 를 통해 코드를 이용해 메뉴를 수동으로 구성할 수도 있습니다.

안드로이드 Gmail 앱의 내비게이션 드로워 (from Google Play Store)

네비게이션 메뉴를 구성할 때 서브 헤더를 사용해 항목들을 구분할 수도 있습니다. 위 Gmail 앱의 네비게이션을 보면 윗 부분과 달리 아래 All labels 부분에는 서브 헤더가 달려 있는 것을 볼 수 있습니다.

서브 헤더 없이 단순한 형태의 드로어 메뉴를 구성하는 방법은 다음과 같습니다.

<menu xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"    
    tools:context=".MainActivity">
    <group android:checkableBehavior="single">
        <item
            android:id="@+id/navigation_item_1"
            android:checked="true"
            android:icon="@drawable/ic_android"
            android:title="@string/navigation_item_1" />
        <item
            android:id="@+id/navigation_item_2"
            android:icon="@drawable/ic_android"
            android:title="@string/navigation_item_2" />
    </group>
</menu>  

이렇게 하면 네비게이션 메뉴들이 단순히 아래로 쭉 나열되는 형태로 보여지게 됩니다. 현재 선택되어 있는 항목은 자동으로 네비게이션 드로어에 강조 표시되므로 사용자가 현재 선택된 탐색 항목이 무엇인지 알 수 있게 됩니다.

반면 서브 헤더를 사용해 네비게이션 항목들을 구분하는 방법은 다음과 같습니다.

<menu xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"                       
    tools:context=".MainActivity">
    <group android:checkableBehavior="single">
        <item
            android:id="@+id/navigation_subheader"
            android:title="@string/nav_header">
            <menu>
                <!-- Menu items go here -->
                <item
                    android:id="@+id/navigation_sub_item_1"
                    android:icon="@drawable/ic_android"
                    android:title="@string/navigation_sub_item_1"/>
                <item
                    android:id="@+id/navigation_sub_item_2"
                    android:icon="@drawable/ic_android"
                    android:title="@string/navigation_sub_item_2"/>
            </menu>
         </item>
    </group>
</menu>  

네비게이션 드로워 안에 들어가는 메뉴들을 런타임에서 코드를 이용해 수정하는 것도 가능합니다. getMenu()를 이용해 현재의 Menu 인스턴스를 가져와 수정해 주면 됩니다.

뿐만 아니라 다음 속성들을 이용해 여러가지 추가적인 설정을 할 수 있습니다.

  • itemBackground — 메뉴 아이템의 background resource 설정
  • itemIconTint — 메뉴 아이템의 아이콘 틴트 색상 설정
  • itemTextColor — 메뉴 아이템의 텍스트 색상 설정

네비게이션 드로워의 선택한 항목에 대한 콜백을 받으려면 setNavigationItemSelectedListener()를 이용해 OnNavigationItemSelectedListener를 설정해 주면 됩니다. 이를 이용해 사용자가 네비게이션 메뉴를 선택했을 때 원하는 작업을 수행할 수 있습니다.

Note: API21 이상의 기기에서는 NavigationView가 자동으로 상태 표시줄의 scrim protection 문제를 자동으로 처리하여 네비게이션 드로워 위로 scrim이 오버레이 될 수 있도록 해 줍니다.

TabLayout

레퍼런스 / 디자인 가이드


탭 터치 애니메이션 (from Google Material Design Guideline)

TabLayout은 탭을 기존보다 보다 쉽고 간편하게 구현할 수 있게 도와주는 컴포넌트입니다. 기본적으로 고정 탭(Fixed tabs)스크롤 탭(Scrollable tabs) 2가지를 모두 지원합니다. 고정 탭에서는 모든 탭이 보기의 너비를 골고루 똑같이 나눠 쓰게 하거나 탭을 가운데 정렬 할 수 있는 반면, 스크롤 탭에서는 탭의 크기가 균일하지 않고 가로 방향으로 스크롤할 수 있습니다.

고정 탭, 균일한 너비 (from Google Material Design Guideline)

고정 탭, 가운데 정렬 (from Google Material Design Guideline)


Google Play Music 앱의 스크롤 탭 (from Google Material Design Guideline)

가장 먼저 레이아웃에 TabLayout을 추가해 줍니다.

<android.support.design.widget.TabLayout  
    android:id="@+id/sliding_tabs"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:tabMode="fixed"
    app:tabGravity="fill" />

여기에는 입맛에 따라 설정해 줘야 할 몇 가지 속성들이 있습니다.

  • tabMode: TabLayout의 모드를 설정합니다. fixed(모든 탭을 한번에 표시)와 scrollable(일부 탭만 표시하고 나머지는 스크롤)중에서 선택할 수 있습니다.
  • tabGravity: 탭의 정렬 방식을 선택합니다. fill(너비를 모두 같게 나누어 표시)와 center(가운데 정렬)을 사용할 수 있습니다.

또한, TabLayout에서는 다음과 같은 3가지의 리스너가 사용됩니다. OnTabSelectedListener를 제외한 나머지 2개는 탭과 함께 ViewPager를 사용하는 경우에만 필요합니다.

  • TabLayout.OnTabSelectedListener: 탭의 선택 상태가 변경될 때 호출되는 리스너입니다.
  • TabLayout.OnPageChangeListener: ViewPager에서 페이지의 상태가 변경될 때(스크롤) 이에 따라 탭의 상태도 변경되어야 하기 때문에 필요한 리스너입니다. 이 리스너가 ViewPager에 붙어서 페이지 변경 이벤트를 TabLayout에 전달해 탭의 선택 상태를 동기화 해 주는 역할을 합니다.
  • ViewPager.OnTabSelectedListener: 위와는 반대로 TabLayout에서 탭을 선택할 때 이에 따라 페이지의 선택 상태도 변경되어야 하기 때문에 필요한 리스너입니다. 이 리스너가 탭의 선택 이벤트를 ViewPager에 전달해 탭과 페이지의 선택 상태가 동기화 될 수 있도록 합니다.

TabLayout이 레이아웃에 추가되었다면, 탭을 추가하는 것은 이전에 액션바에 탭을 추가하는 방법과 동일합니다. 아래와 같이 코드에서 새 탭을 만들고, setText()setIcon()메소드를 이용해 탭의 이름과 아이콘을 설정해 줍니다.

물론, 페이지를 넘기는 데 ViewPager를 사용하는 경우, setupWithViewPager()를 사용해 이 둘을 간단하게 연결할 수 있습니다.

고맙게도, setupWithViewPager()는 다음과 같은 일들을 자동으로 수행해 줍니다.

  • ViewPager.OnPageChangeListener를 자동으로 등록해 ViewPager의 페이지 변경 이벤트가 TabLayout에 전달될 수 있도록 합니다.
  • 코드로 일일이 탭을 생성해 주지 않아도 ViewPagerPagerAdapter로부터 자동으로 탭을 만들어 줍니다. 만세! (참고: setTabsFromPagerAdapter())
  • TabLayout.OnTabSelectedListener를 자동으로 등록해 TabLayout의 탭 변경 이벤트가 ViewPager에 전달될 수 있도록 합니다.

그래서 다음과 같이 간단한 코드로 TabLayoutViewPager를 구현할 수 있게 됩니다.

ViewPager pager = (ViewPager)  
rootView.findViewById(R.id.viewPager);  
pager.setAdapter(new MyPagerAdapter(getActivity().getSupportFragmentManager()));

TabLayout tabLayout = (TabLayout) rootView.findViewById(R.id.sliding_tabs);  
tabLayout.addTab(tabLayout.newTab().setText("Tab One"));  
tabLayout.addTab(tabLayout.newTab().setText("Tab Two"));  
tabLayout.addTab(tabLayout.newTab().setText("Tab Three"));  
tabLayout.setupWithViewPager(pager);  


Coordinator Layout

레퍼런스 / 디자인 가이드

CoordinatorLayout은 터치나 드래그와 같은 이벤트가 일어날 때 여러 개의 하위(child) view들이 서로 상호 작용을 할 수 있도록 해 주는 레이아웃입니다.

Note: 레이아웃들이 정상적으로 작동할 수 있도록 support library dependency를 최신 버전(22.2.0 이상)으로 업데이트 해 주세요. (ex. RecyclerView 등)

CoordinatorLayout은 다음과 같은 두 가지 속성을 제공하여 화면에 있는 view들이 다른 view들을 기준(앵커, anchor)으로 하여 상호작용 할 수 있도록 해 줍니다.

  • layout_anchor: 다른 view를 anchor로 설정할 수 있습니다.
  • layout_anchorGravity: 위에서 설정한 anchor를 기준으로 한 gravity를 설정합니다.

FloatingActionButton & Snackbar

앞에서 Snackbar를 설명할 때 언급했던 SnackbarFloatingActionButton의 상호작용은 CoordinatorLayout을 활용한 좋은 예입니다. CoordinatorLayout을 사용하면 스낵바가 표시될 때 FAB를 가리는 대신 애니메이션 효과와 함께 자동으로 위, 아래쪽으로 이동합니다. 뿐만 아니라 Snackbarswipe-to-dismiss도 사용할 수 있게 됩니다.

CoordinatorLayout을 이용하면 Snackbar를 표시할 때 FAB을 자동으로 밀어올리고, Snackbar에 대해 swipe-to-dismiss를 수행할 수 있게 됩니다.

이를 구현하기 위해서는 FloatingActionBarCoordinatorLayout의 바로 하위(child)로 추가하고, layout_gravity 속성을 통해 FAB의 위치를 지정해 주어야 합니다.

<android.support.design.widget.CoordinatorLayout  
    android:id="@+id/main_content">

    <!-- Your other views -->

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_plus"
        android:layout_gravity="bottom|right"
        app:fabSize="normal" />

</android.support.design.widget.CoordinatorLayout>  

이제 Snackbar를 보여줄 때 CoordinatorLayout를 view 파라메터로 넘겨주면 됩니다.

Snackbar.make(mCoordinator, "Your message", Snackbar.LENGTH_SHORT)  
    .show();


App Bar

CoordinatorLayout을 활용하면 스크롤에 따른 view의 모양을 변화시킬 수 있습니다. 아주 대표적인 예가 바로 App Bar(예전에 Action bar라고 불리던)입니다. 이미 여러분은 레이아웃에서 Toolbar를 사용하고 계실 것입니다. 디자인 라이브러리는 이 기능을 한 단계 더 발전시킨 AppBarLayout를 제공합니다. 이것을 사용하면 Toolbar와 함께 사용되는 여러가지 레이아웃(예컨대 TabLayout)들이 (ScrollingViewBehavior로 표시된) 다른 뷰에서 스크롤 이벤트가 일어날 때 툴바와 어우러져 자연스럽게 반응하도록 할 수 있습니다.

<android.support.design.widget.CoordinatorLayout  
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

     <! -- Your Scrollable View -->
    <android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />


    <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
                  ...
                  app:layout_scrollFlags="scroll|enterAlways" />

        <android.support.design.widget.TabLayout
                  ...
                  />

    </android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>  

이제 사용자가 RecyclerView를 스크롤하면 AppBarLayout이 스크롤 이벤트에 응답할 수 있게 됩니다.


(from Google Material Design Guideline)

여기서 살펴볼 점은 두 가지가 있습니다.

먼저 layout_scrollFlags 속성에 스크롤에 반응할 방법인 scroll flag을 설정해 주어야 합니다. 여기에서 화면 밖으로 스크롤 될 때 화면 상단에 남아 있을 지, 아니면 사라질 지를 결정합니다. flag의 예는 다음과 같습니다.

  • scroll: 스크롤 이벤트에 반응할 모든 view에 반드시 이 flag를 설정해야 합니다.

  • enterAlways: 아래쪽 방향으로 스크롤할 때마다 이 보기가 표시됩니다. ('quick return')

  • enterAlwaysCollapsed: 해당 view에 minHeight 속성이 있는 경우, 해당 크기로 시작해 맨 위로 스크롤 될 때만 전체 높이로 확장됩니다.

  • exitUntilCollapsed: view가 minHeight가 될 때 까지만 축소됩니다.

Note: scroll 플래그를 사용하는 모든 보기는 이 플래그를 사용하지 않는 보기보다 먼저 선언해야 합니다. 그래야 모든 view가 맨 위쪽부터 화면 밖으로 스크롤되고 고정된 요소들이 화면에 남도록 할 수 있습니다.

다음으로는 RecyclerView에 사용된 layout_behavior속성입니다. 이 속성은 RecyclerViewCoordinatorLayout과 함께 동작하도록 하기 위해서 반드시 필요합니다. 즉, 레이아웃이 RecyclerView의 스크롤 이벤트에 반응할 수 있게 된다는 것입니다. 우리가 위 코드에서 사용한 레이아웃에서는 Toolbarlayout_scrollFlags 속성을 선언해 놓았기 때문에 RecyclerView에서 스크롤이 일어나면 이 이벤트가 ToolBar에 전달되어 화면 안 혹은 밖으로 스크롤 되게 됩니다. 반면, TabLayout에는 이 속성을 선언하지 않았기 때문에 스크롤이 일어나더라도 화면 상단에 남아 있게 되는 것이죠.

Collapsing Toolbar

앞에서 살펴본 것과 같이 ToolbarAppBarLayout로 감싸 주면 enterAlwaysCollapsedexitUntilCollapsed 같은 flag를 지정해 스크롤 이벤트에 반응할 수 있었습니다. 하지만 스크롤 이벤트에 따라 view들을 축소하거나 하는 등의 상세한 조작은 불가능했는데요, 새로 추가된 CollapsingToolbarLayout을 사용해 Toolbar를 감싸 주면 스크롤에 따라 Toolbar를 확장하거나 축소할 수 있게 됩니다.

<android.support.design.widget.AppBarLayout  
        android:layout_height="192dp"
        android:layout_width="match_parent">
    <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">
        <android.support.v7.widget.Toolbar
                android:layout_height="?attr/actionBarSize"
                android:layout_width="match_parent"
                app:layout_collapseMode="pin"/>
        </android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>  

CollapsingToolbarLayoutToolbar를 함께 사용하는 경우 좋은 점은 레이아웃이 완전히 표시될 때는 제목이 크게 나타났다가 레이아웃이 축소될 때 애니메이션과 함께 제목이 점점 작아져 기본 크기로 자연스럽게 전환된다는 것입니다. 이를 위해서는 Toolbar 자체가 아닌, CollapsingToolbarLayoutsetTitle()을 이용해 제목을 설정해 주어야 합니다.

뿐만 아니라, Collapsing Toolbar가 제대로 작동하기 위해서는 app:layout_collapseMode 속성을 지정해 주어야 하는데요, 여기에는 두 가지 옵션이 있습니다.

  • "pin": CollapsingToolbarLayout이 완전히 축소되면 툴바는 화면 맨 위에 고정(pinned)되어 보여집니다.

  • "parallax": 툴바가 축소되는 동안 Parallax 모드 (예컨대 CollapsingToolbarLayout 안에 ImageView를 사용하는 경우)로 동작하도록 합니다. 옵션으로 layout_collapseParallaxMultiplier 속성을 사용하여 transition의 translation multiplier를 설정할 수도 있습니다. (예: app:layout_collapseParallaxMultiplier="0.7") 이때 CollapsingToolbarLayoutapp:contentScrim="?attr/colorPrimary" 속성을 사용해 주면 view가 축소될 때 그림 위로 지정한 색상이 오버레이 되면서 자연스럽게 Toolbar로 축소되는 애니메이션을 구사할 수 있습니다. (참고: Android Reference

Wrap Up

이제 더 이상 망설일 필요가 없습니다. 지금 바로 여러분의 앱에 머티리얼 디자인을 적용해 보세요!
아직 잘 감이 안 오신다면 이 페이지를 참고해서 차근차근 따라해 보시고, 최근 버전 22.1 업데이트 되면서 디자인 서포트 라이브러리에 추가된 부분들은 구글 안드로이드 개발자 블로그를 참고해 보세요.

References

Google Developers Blog - Android Design Support Library
Google Developers Korea Blog - Android Design Support Library
Ribot Labs - Exploring the new Android Design Support Library
커니의 안드로이드 이야기 - Support Design Library (Google I/O Extended Seoul, 2015)