레이블이 Support 라이브러리인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Support 라이브러리인 게시물을 표시합니다. 모든 게시물 표시

2018년 6월 5일 화요일

[안드로이드] Android JetPack

Use Android Jetpack to Accelerate Your App Development

이 포스트에서, 안드로이드 앱 개발을 더 쉽도록 도와주는 차세대의 컴포넌트, 도구, 구조적 가이드라인을 포함하는 JetPack을 소개하려고 한다.
JetPack은 새로운 안드로이드의 기능들이 하위호환성을 유지할 수 있도록 도와주는 Support Library로부터 고안되었다. 그리고, 앱의 라이프사이클과 관련된 데이터 처리, 수시로 변하는 데이터 처리의 복잡성을 쉽게 처리할 수 있도록 디자인된 아키텍쳐 컴포넌트를 소개했다. 아키텍쳐 컴포넌트를 작년에 발표한 이래로 많은 앱들이 이 컴포넌트를 사용하기 시작했다. LinkedIn, Zillow, iHeartRadio 같은 회사들은 더 적은 버그, 더 쉬워진 테스트, 앱의 순수 기능에만 집중할 수 있는 시간 등의 이점을 보았다.

What is Android JetPack

Android JetPack은 좋은 앱을 만들기 위한 컴포넌트, 도구, 가이드라인의 모음 이다. JetPack은 기존의 서포트 라이브러리, 아키텍쳐 컴포넌트들을 모두 가져와서 4개의 카테고리로 재배치했다.


JetPack 라이브러리는 안드로이브 플랫폼의 일부로 포함되지 않고(unbundled) 별개로 제공되는 라이브러리 이다. 즉, 각 컴포넌트를 필요한 시점에 맞추어 적욧할 수 있다는 의미다. JetPack에 새로운 기능이 추가되면 손쉽게 앱에 반영하여 배포할 수 있다. JetPack으로 변경되면서,기존/신규 컴포넌트들이 androidx.*로 이동되었다. 해당 내용은 다음의 포스트를 참고한다.
또한, 서포트 라이브러리의 취지대로, JetPack의 컴포넌트도 특정 버전에 종속되지 않도록 만들어졌기 때문에 다양한 안드로이드 버전의 플랫폼에서 실행할 수 있다.
게다가, JetPack은 관심의 분리 / 테스트 가능을 중점적으로 하는 현대 설계기법과 코틀린 통합과 같은 생산성 관련 내용들도 고려하여 제작되었다. 이는 더 적은 코드로 견고하고 고품질의 앱을 쉽게 만들도록 도와준다. JetPack의 컴포넌트들은 유기적으로 동작할 수 있도록 구현되었지만, 개발자는 굳이 모두를 사용할 필요 없이 필요한 부분만 사용할 수도 있다. 그러므로 이미 구현된 모든 부분을 고칠 필요 없이, 필요한 부분에만 JetPack 컴포넌트를 순차적으로 교체할 수도 있다.
우리는 다음과 같은 피드백을 받았기 때문에, JetPack의 이러한 특성이 얼마나 중요한지 알고 있다.
우리 앱은 MVVM으로 코드 구조 변경을 고려하고 있었습니다. 아키텍쳐 컴포넌트는 이 고민을 쉽게 해결하고 구현할 수 있도록 도와주었습니다. 그리고, 테스트하기도 훨씬 수월해졌습니다. ViewModel만 유닛테스트를 수행하면 되기 때문에 코드가 전보다 훨씬 견고해졌습니다.

What's New

다음의 5가지 새로운 컴포넌트가 JetPack에 곧 추가될 것이다.
  • WorkManager alpha release
  • Navigation alpha release
  • Paging stable release
  • Slices alpha release
  • Android KTX (Kotlin Extensions) alpha release

WorkManager

WorkManager 컴포넌트는 백그라운드 제약상황에서 실행이 보장되어야 하는 백그라운드 동작을 구현하기 위한 멋진 해결방법을 제공하는 라이브러리 이다. 이 라이브러리는 다음과 같은 특징을 가지고 있다.
  • 단순함
  • 최근 스타일의 API
  • Google Play Service의 유무에 상관없이 동작할 수 있음
  • work의 그래프를 생성하여 순서를 지정할 수 있음
  • work의 상태를 조회할 수 있음
    자세한 내용은 공식문서에서 확인할 수 있다.

Navigation

엑티비티는 시스템에서 제공하는 앱 UI의 진입점이지만, 화면간에 데이터를 공유할떄나, 화면전환에서 유연성이 떨어지는 점 때문에 이상적인 아키텍쳐라고 볼 수 없었다. 그래서 하나의 엑티비티만을 사용하는 것을 기본 아키텍쳐로 하여 앱을 만들도록 하는 Navagation 컴포넌트가 도입되었다. 프레그먼트의 지원을 통해 Lifecycle, ViewModel 같은 아키텍쳐 컴포넌트의 모든 이점을 얻을 수 있으며, Navigation 컴포넌트가 FragmentTransition의 복잡함을 처리할 수 있다. 게다가, Navigation컴포넌트는 올바른 Up / Back 동작을 자동적으로 빌드되도록, Deep 링크를 지원하도록, NavigationDrawer / BottomNavigation 같은 UI위젯에서도 적절하게 네비게이셔닝이 되도록 도와준다. 안드로이드 스튜디오 3.2에서는 네비게이션 에디터를 추가하여 네비게이션 속성들을 보고 관리할 수 있도록 추가될 예정이다.


자세한 내용은 공식문서에서 확인할 수 있다.

Paging

앱에 표시되는 데이터는 양이 많고, 로드하는 동작은 비용이 크기 때문에, 다운로드나 생성, 한번에 너무 많이 보여주는 동작을 피해야한다. Paging 컴포넌트는 RecyclerView에서 많은 양의 데이터를 로드하고 나타내는 작업을 빠르게 수행할 수 있도록 도와준다. 페이징 된 데이터를 로컬 / 네트워크 / 둘다 에서 불러올 수 있으며, 어떻게 컨텐츠를 불러올지 정의할 수 있다. Room, LiveData, RxJava와 함께 사용될 수 있다.
자세한 내용은 공식문서에서 확인할 수 있다.

Slice

JetPack에 Slice 컴포넌트가 추가되었다. "Slice"는 특정 내용을 다른 앱에서 보여주는 개념이다. 다음의 예시는 구글 어시스턴트의 검색결과로 어시스턴트 UI안에 내용물을 렌더링한다.


자세한 내용은 공식문서에서 확인할 수 있다.

Android KTX

JetPack의 많은 목표중에 하나는 코틀린 언어의 이점을 이용해 앱 구현의 생산성을 높이는 것이다. Android KTX는 코틀린코드를 다음과 같이 변경할 수 있도록 도와준다.
view.viewTreeObserver.addOnPreDrawListener(
  object : ViewTreeObserver.OnPreDrawListener {
    override fun onPreDraw(): Boolean {
      viewTreeObserver.removeOnPreDrawListener(this)
      actionToBeTriggered()
      return true
    }
});
더 간결하게 줄일 수 있다.
view.doOnPreDraw { actionToBeTriggered() }

이는 단지 JetPack을 이용하여 코틀린을 지원하는 시작일 뿐이다. 우리의 목표는 JetPack을 이용해 코틀린 개발자가 좋은 앱을 만드는 것이다.
자세한 내용은 공식문서에서 확인할 수 있다.

원문

https://android-developers.googleblog.com/2018/05/use-android-jetpack-to-accelerate-your.html

2018년 5월 18일 금요일

[안드로이드] ConstraintLayout 1.1 새로운 기능 소개

ConstraintLayout 1.1 새로운 기능 소개

Percents

1.0에서는 뷰의 크기를 퍼센트로 지정하려면 가이드라인 2개를 이용하거나, ConstraintLayout 대신 PecentLayout을 이용해야만 가능했다. 1.1에서 추가된 속성을 이용하면 뷰의 크기를 쉽게 퍼센트로 지정할 수 있다.
퍼센트를 사용하려면 다음의 사항을 따라야 한다.
  • layout_width / layout_height를 0dp(MATCH_CONSTRAINT) 로 해야한다.
  • layout_constraintWidth_percent / layout_constraintHeight_percent를 이용한다.
    • 해당 속성의 값은 0 ~ 1 사이의 값이여야 한다.
<Button
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintWidth_percent="0.7"
    app:layout_constraintHeight_percent="0.2" />

Chains

Chain 제약은 여러개의 뷰를 묶어 어떻게 공간에 배치할지를 정하는 조건이다.
Chain으로 묶으려면, 쌍방에 대한 제약조건을 걸면 된다. 수평 / 수직 방향 모두 가능하며, 배치 방법에는 다음의 3가지가 있다.


  • speard : 기본적인 배치이다. layout_constraintVertical_weight / layout_constraintHorizontal_weight 을 이용하면 LinearLayout의 layout_weight효과를 낼 수 있다.
  • spread_inside : 공간의 가장자리에 첫번째 부와 마지막 뷰가 배치되고, 남은 사이공간에 나머지 뷰들이 배치된다.
  • packed : 묶인 모든 뷰들이 공간의 가운데에 배치된다. bias 속성과 함꼐 사용하면 위치를 조절할 수 있다.

Barriers

Barrier 라는 헬퍼 객체를 이용하여, 뷰들의 크기가 런타임으로 변할때에 적합한 제약조건을 생성할 수 있다.(객체라기보다는 가상의 그룹에 가깝다.) 해당 Barrier에 연관된 뷰들은 크기가 동적으로 변할 수 있고, 크기가 동적으로 변할 때, Barrier에 제약조건을 건 뷰들은 크기 / 위치가 Barrier에 맞게 변경된다. Barrier는 start, top, end, bottom중 하나의 위치로 지정될 수 있다. 해당 객체는 ConstraintLayout에 계층구조로 잡히지 않으며 렌더링되지 않는다.



<android.support.constraint.ConstraintLayout 
  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">

  <TextView
    android:id="@+id/textView1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="16dp"
    android:layout_marginTop="16dp"
    android:text="@string/warehouse"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

  <TextView
    android:id="@+id/textView2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="16dp"
    android:layout_marginTop="8dp"
    android:text="@string/hospital"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/textView1" />

  <android.support.constraint.Barrier
    android:id="@+id/barrier7"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:barrierDirection="end"
    app:constraint_referenced_ids="textView2,textView1" />

  <TextView
    android:id="@+id/textView3"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginStart="8dp"
    android:text="@string/lorem_ipsum"
    app:layout_constraintStart_toEndOf="@+id/barrier7"
    app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Group

Group이라는 헬퍼 객체를 이용해, 몇몇 뷰들을 그룹화 할 수 있다. 그룹화 함으로써 얻는 장점은, visibility 등의 동시적인 작업이 필요할때, 해당 그룹을 이용하여 작업을 수행하면 그룹안의 뷰들에게 전체적으로 적용된다. 해당 객체는 ConstraintLayout에 계층구조로 잡히지 않으며 렌더링되지 않는다.
<android.support.constraint.Group
    android:id="@+id/profile"
    app:constraint_referenced_ids="profile_name,profile_image" />
profile.setVisibility(View.VISIBLE);

Circular Constraints

기존의 대부분 레이아웃이 수평 / 수직의 배치만 지원한 것에 비해, 원형의 배치를 지원하게 되었다. 원형 배치를 구현하려면 다음을 수행한다.
  • layout_constraintCircle속성에 구심점이 될 뷰를 지정한다.
  • layout_constraintCircleRadius속성에 구심점으로부터의 거리를 지정한다.(반지름)
  • layout_constraintCircleAngle속성에 0 ~ 360 사이의 각도값을 지정한다. 12시 방향이 0도이고, 시계방향으로 각도가 늘어난다.
<android.support.design.widget.FloatingActionButton
    android:id="@+id/middle_expanded_fab"
    app:layout_constraintCircle="@+id/fab"
    app:layout_constraintCircleRadius="50dp"
    app:layout_constraintCircleAngle="315" />




ConstraintSet을 이용한 애니메이션

ConstraintSet을 이용하면 여러개의 뷰들을 한번에 애니메이셔닝 할 수 있다. ConstraintSet은 ConstraintLayout에서의 제약조건들만 가지고 있고, 해당 제약조건들을 ConstraintLayout에 지정함으로써 제약조건들을 갱신할 수 있다.
애니메이션을 수행하려면, TransitionManager.beginDelayedTransition() API를 이용한다. 이 메서드는 ConstraintSet의 모든 레이아웃의 변경사항을 애니메이션으로 수행한다.
해당 기능에 대해 설명한 비디오
https://www.youtube.com/watch?v=OHcfs6rStRo&feature=youtu.be

New Optimizations

ConstraintLayout안의 불필요한 제약조건들을 줄여서, 레이아웃 속도를 빠르게 해주는 최적화 레벨을 지정할 수 있다. layout_optimizationLevel 속성에 레벨을 지정하여 최적화 레벨을 지정한다. 최적화레벨은 다음의 5가지가 존재한다.
  • barriers : Barrier 제약조건에 관련하여 최적화
  • direct : 고정된 요소(예 : parent, Guideline)에 연결된 제약조건에 관련하여 최적화
  • standard : barriers, direct를 포함한 기본 최적화 레벨
  • dimensions : 뷰 크기에 관련된 부분을 최적화 - 해당 옵션은 현재 테스트중이며 이슈 발생의 소지가 있다.
  • chains : 고정된 크기의 뷰를 체이닝할때의 최적화 - 해당 옵션은 현재 테스트중이며 이슈 발생의ㅏ 소지가 있다.
<android.support.constraint.ConstraintLayout 
    app:layout_optimizationLevel="standard|dimensions|chains"

참고링크

  • http://dktfrmaster.blogspot.kr/2016/09/constraintlayout.html
  • https://developer.android.com/reference/androidx/constraintlayout/widget/ConstraintLayout

2018년 3월 15일 목요일

[안드로이드] CoordinatorLayout 기본 동작 원리

CoordinatorLayout

개요

CoordinatorLayout은 매우 강력한 기능을 가진 FrameLayout 이다. 이 레이아웃은 다음의 2가지 기본사용 사례를 위해 만들어졌다.
  1. 애플리케이션에서 최상위의 decor View로써 사용
  2. 자식 뷰들간의 특정한 인터렉션을 지원하는 컨테이너로써 사용
CoordinatorLayout의 자식뷰에 Behavior를 지정하는 방식으로, 하나의 부모 안에서 여러 다른 인터렉션을 지원할 수 있고, 자식뷰들간에도 서로 인터렉션 할 수 있다. Behavior 는 슬라이딩 드로어나 스와이프 해제액션 등 뷰의 다양한 움직임이나 애니메이션에 따른 상호작용을 구현하기 위해 사용된다.
CoordinatorLayout의 자식들은 anchor를 가질 것이다. 이 anchor는 일반적으로 다른 뷰를 가리키며, 뷰의 id 값으로 표시된다. anchor는 반드시 CoordinatorLayout 하위의 자손 뷰중 하나의 아이디여야 하지만, 고정된 자손뷰가 아닐 수도 있다. 이는 다른 임의의 화면의 상대위치에 뷰를 띄우는 용도로 사용될 수도 있다.
insetEdge속성을 이용해 CoordinatorLayout 안에 자식뷰들이 어떻게 배치될지 지정할 수 있다. 만약 자식뷰가 겹칠 것을 대비해, dodgeInsetEdges속성을 주어 적절하게 뷰가 겹치지 않도록 배치할 수 있다.

Behavior

CoordinatorLayout 구현의 가장 핵심 개념이다. 자식뷰에 Behavior가 지정되어 있으면, CoordinatorLayout은 그것을 토대로 부모뷰 - 자식뷰 / 자식뷰 - 자식뷰 의 소통을 구현한다. CoordinatorLayout에서 자식뷰와 소통해야 하는 부분(인터렉션, 뷰 배치 등)에서 Behavior를 사용하는 코드를 확인할 수 있다. 아래의 예제와 같이 곳곳에서 Behavior를 사용하고 있다.
public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent {
    ...
    // Behavior가 인터렉션에 활용되는 예시
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        ...
        final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);
        ...
        return intercepted;
    }
    
    private boolean performIntercept(MotionEvent ev, final int type) {
        boolean intercepted = false;
        boolean newBlock = false;

        MotionEvent cancelEvent = null;

        final int action = MotionEventCompat.getActionMasked(ev);

        final List<View> topmostChildList = mTempList1;
        getTopSortedChildren(topmostChildList);

        // Let topmost child views inspect first
        final int childCount = topmostChildList.size();
        for (int i = 0; i < childCount; i++) {
            final View child = topmostChildList.get(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Behavior b = lp.getBehavior();

            if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
                // Cancel all behaviors beneath the one that intercepted.
                // If the event is "down" then we don't have anything to cancel yet.
                if (b != null) {
                    if (cancelEvent == null) {
                        final long now = SystemClock.uptimeMillis();
                        cancelEvent = MotionEvent.obtain(now, now,
                                MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                    }
                    switch (type) {
                        case TYPE_ON_INTERCEPT:
                            b.onInterceptTouchEvent(this, child, cancelEvent);
                            break;
                        case TYPE_ON_TOUCH:
                            b.onTouchEvent(this, child, cancelEvent);
                            break;
                    }
                }
                continue;
            }

            ...
        }
        topmostChildList.clear();
        return intercepted;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean handled = false;
        boolean cancelSuper = false;
        MotionEvent cancelEvent = null;

        final int action = MotionEventCompat.getActionMasked(ev);

        if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {
            // Safe since performIntercept guarantees that
            // mBehaviorTouchView != null if it returns true
            final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();
            final Behavior b = lp.getBehavior();
            if (b != null) {
                handled = b.onTouchEvent(this, mBehaviorTouchView, ev);
            }
        }

        ...
        return handled;
    }
    
    ...
    
    // Behavior가 뷰의 배치에 활용되는 예시
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        
        final int childCount = mDependencySortedChildren.size();
        for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);
            if (child.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            
            ...
            
            final Behavior b = lp.getBehavior();
            if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
                    childHeightMeasureSpec, 0)) {
                onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
                        childHeightMeasureSpec, 0);
            }

            widthUsed = Math.max(widthUsed, widthPadding + child.getMeasuredWidth() +
                    lp.leftMargin + lp.rightMargin);

            heightUsed = Math.max(heightUsed, heightPadding + child.getMeasuredHeight() +
                    lp.topMargin + lp.bottomMargin);
            childState = ViewCompat.combineMeasuredStates(childState,
                    ViewCompat.getMeasuredState(child));
        }

        ...

        setMeasuredDimension(width, height);
    }
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int layoutDirection = ViewCompat.getLayoutDirection(this);
        final int childCount = mDependencySortedChildren.size();
        for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);
            if (child.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Behavior behavior = lp.getBehavior();

            if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
                onLayoutChild(child, layoutDirection);
            }
        }
    }
}

기본적으로 제공되는 Behavior

  • CoordinatorLayout.Behavior
    • BottomSheetBehavior : Bottom Sheet처럼 동작하도록 지원하는 Behavior
    • FloatActionButton.Behavior
    • SwipeDismissBehavior : swipe-to-dismiss제스쳐를 지원하는 Behavior
      • BaseTransientBottomBar.Behavior
    • ViewOffsetBehavior : 뷰들의 offset을 지정
      • HeaderScrollingViewBehavior : 수직으로 된 레이아웃에서 다른 뷰 아래에 있으면서 스크롤되는 뷰를 위한 Behavior
        • AppBarLayout.ScrollingViewBehavior
      • HeaderBehavior : 수직으로 스크롤되는 뷰 위에 놓이는 뷰를 위한 Behavior
        • AppBarLayout.Behavior

기본 Behavior 동작영상

Anchor

Anchor는 자식뷰들간의 연관성을 표현하는 개념이다. CoordinatorLayout 안에서 어떤 자식뷰의 배치나 인터렉션이 있을 때, 연관된(Anchor로 등록된) 자식뷰들에게 상태변화를 알려주도록 구현하기 위해 사용된다. 연관성을 표현하기 위해 내부적으로 그래프를 이용한다. 아래의 예제는 CoordinatorLayout 안에서의 Anchor개념을 사용하는 코드이다.
public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent {
    ...
    private final DirectedAcyclicGraph<View> mChildDag = new DirectedAcyclicGraph<>();
    ...
    
    // onMeasure()에서 anchor 그래프 준비
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        prepareChildren();
        ...
    }

    private void prepareChildren() {
        mDependencySortedChildren.clear();
        mChildDag.clear();

        for (int i = 0, count = getChildCount(); i < count; i++) {
            final View view = getChildAt(i);

            final LayoutParams lp = getResolvedLayoutParams(view);
            lp.findAnchorView(this, view);

            mChildDag.addNode(view);

            // Now iterate again over the other children, adding any dependencies to the graph
            for (int j = 0; j < count; j++) {
                if (j == i) {
                    continue;
                }
                final View other = getChildAt(j);
                final LayoutParams otherLp = getResolvedLayoutParams(other);
                if (otherLp.dependsOn(this, other, view)) {
                    if (!mChildDag.contains(other)) {
                        // Make sure that the other node is added
                        mChildDag.addNode(other);
                    }
                    // Now add the dependency to the graph
                    mChildDag.addEdge(view, other);
                }
            }
        }

        // Finally add the sorted graph list to our list
        mDependencySortedChildren.addAll(mChildDag.getSortedList());
        // We also need to reverse the result since we want the start of the list to contain
        // Views which have no dependencies, then dependent views after that
        Collections.reverse(mDependencySortedChildren);
    }

    ...

    // 연관된 View들에게 상태변화를 알려줌
    public void dispatchDependentViewsChanged(View view) {
        final List<View> dependents = mChildDag.getIncomingEdges(view);
        if (dependents != null && !dependents.isEmpty()) {
            for (int i = 0; i < dependents.size(); i++) {
                final View child = dependents.get(i);
                CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)
                        child.getLayoutParams();
                CoordinatorLayout.Behavior b = lp.getBehavior();
                if (b != null) {
                    b.onDependentViewChanged(this, child, view);
                }
            }
        }
    }
    
    ...
}

LayoutParams

CoordinatorLayout은 다른 몇몇 레이아웃처럼, 자신에게 특화된 LayoutParams 정적 클래스를 가지고 있다. LayoutParams 클래스는 Behavior와 anchor 정보를 가지고 있어서, 부모가 요청할 때마다 그 정보를 넘겨주도록 구현되어 있다. 다음의 코드는 CoordinatorLayout.LayoutParams의 코드 일부분이다.
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
    Behavior mBehavior;
    public int anchorGravity = Gravity.NO_GRAVITY;
    int mAnchorId = View.NO_ID;
    View mAnchorView;
    View mAnchorDirectChild;
    ...
    
    @Nullable
    public Behavior getBehavior() {
        return mBehavior;
    }
        
     public void setBehavior(@Nullable Behavior behavior) {
        if (mBehavior != behavior) {
            if (mBehavior != null) {
                // First detach any old behavior
                mBehavior.onDetachedFromLayoutParams();
            }

            mBehavior = behavior;
            mBehaviorTag = null;
            mBehaviorResolved = true;

            if (behavior != null) {
                // Now dispatch that the Behavior has been attached
                behavior.onAttachedToLayoutParams(this);
            }
        }
    }
    
    @IdRes
    public int getAnchorId() {
        return mAnchorId;
    }

    public void setAnchorId(@IdRes int id) {
        invalidateAnchor();
        mAnchorId = id;
    }

    View findAnchorView(CoordinatorLayout parent, View forChild) {
        if (mAnchorId == View.NO_ID) {
            mAnchorView = mAnchorDirectChild = null;
            return null;
        }

        if (mAnchorView == null || !verifyAnchorView(forChild, parent)) {
            resolveAnchorView(forChild, parent);
        }
        return mAnchorView;
    }
    ...
}

CoordinatorLayout 기본 사용법

  1. 모듈레벨의 build.gradle 에 다음을 추가한다.
...
dependencies {
    ...
    compile 'com.android.support:appcompat-v7:25.1.1'
    compile 'com.android.support:design:25.1.1'
    ...
}
...
  1. CoordinatorLayout을 지정할 레이아웃 xml의 루트 레이아웃으로 지정한다.
  2. CoordinatorLayout의 자식중 behavior를 지정할 자식에게 app:layout_behavior속성을 지정해 준다. 해당 속성은 String 타입으로, 지정할 Behavior의 패키지를 포함한 클래스명을 넣어야 한다.
<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay" />

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

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        tools:showIn="@layout/activity_scrolling">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/text_margin"
            android:text="@string/large_text" />

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

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/fab_margin"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end"
        app:srcCompat="@android:drawable/ic_dialog_email" />

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

<!-- strings.xml -->
<resources>
    <string name="appbar_scrolling_view_behavior" translatable="false">android.support.design.widget.AppBarLayout$ScrollingViewBehavior</string>
</resources>