IT_Programming/Android_Java

[펌] Android CoordinatorLayout Behavior 사용하기 / CoordinatorLayout 에서 Behavior 사용해 보기

JJun ™ 2016. 4. 1. 13:40



 * 출처

 : http://iw90.tistory.com/270

 : https://black-jin0427.tistory.com/201

 : https://blog.kmshack.kr/CoordinatorLayout과-Behavior의-관계/




Android CoordinatorLayout Behavior 사용하기


android support design 라이브러리가 생기면서 behavior 라는 개념이 생겼습니다.

여태까지는 android 에서 정의한 behavior 를 사용 했습니다.
사용하면서 어떻게 만들었는지 
의문이 있었지만, 찾아보지 않아 이번에 기회가 있어 이렇게 찾아서 정리해보려 합니다.


behavior 는 view 에 의존하여 어떤뷰의 행동에 따라 위젯을 변경하는 것입니다.


이 포스트를 통해서는 간단한 behavior 를 작성하려합니다.

간단하지만 방법만 알면 얼마든지 응용이 가능한것 같습니다.


사전준비 작업


우선 간단하게 CoordinatorLayout 에 FloationgButton 선택시 snackBar 가 나타나는 화면을 구성해 보겠습니다.
위 위젯을 사용하기 위해서 design 라이브러리를 추가 합니다.


1
2
3
4
5
6
7
8
 
dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.2.1'
    compile 'com.android.support:design:24.0.0-alpha1'
}
 
cs


design 라이브러리를 추가 합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:layout_margin="16dp"
         />
 
</android.support.design.widget.CoordinatorLayout>
 
cs


화면은 CoordinatorLayout 에 FloationgButton 을 만들었습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
public class MainActivity extends AppCompatActivity {
 
    FloatingActionButton fab;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
 
        fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Snackbar.make(v, "Hello World", Snackbar.LENGTH_LONG).show();
            }
        });
 
    }
 
}
 
cs


FloationgButton 선택시 snackBar가 나타나도록 했습니다.
이렇게 구성하고 버튼을 선택해보면 snackBar가 
나타나면서 FloationgButton 을 밀어 올리고 사라지면서
제자리로 오는것을 볼수 있습니다. 
위와 같이 동작을 만들기 위해서 필요한것이 behavior 입니다.


위와 같이 동작하는 Custom Behavior 를 만들어 보려 합니다.



Behavior 코드 작성


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
 
    private static final String TAG = "FloatingActionButtonBehavior";
 
    public FloatingActionButtonBehavior() {
    }
 
    public FloatingActionButtonBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
 
 
}
 
cs


위와 같이 빈 Behavior 를 작성 하였습니다. 위와 같이 작성후에 behavior 를 적용해 보겠습니다.


1
2
3
4
5
6
7
8
9
10
 
<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="end|bottom"
    android:layout_margin="16dp"
    app:layout_behavior="com.android.woong.behaviorsample.FloatingActionButtonBehavior"
     />
 
cs


위와 같이 적용하고 실행해서 FloationgButton 선택해보면 snackBar 가 나타나지만 
FloationgButton 이 가만히 있는것을 알 수 있습니다. 행동이 없기 때문입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 
public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
 
    private static final String TAG = "FloatingActionButtonBehavior";
 
    public FloatingActionButtonBehavior() {
    }
 
    public FloatingActionButtonBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
 
 
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
        return dependency instanceof Snackbar.SnackbarLayout;
    }
 
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
 
        float translationY = Math.min(0,dependency.getTranslationY() - dependency.getHeight());
        child.setTranslationY(translationY);
 
        return true;
    }
 
}
 
cs


Behavior 를 작성해 보았습니다. 상속을 통해서 어떤뷰에서 Behaivor 를 사용할지 정의를 합니다.

layoutDependsOn 함수로 이벤트가 들어 옵니다. 이함수는 의존할 View를 Filter 하는 역할을 하는것 같습니다.


위와 같이 dependency 의 타입이 snackbarLayout 일때 리턴을 통해서 onDependentViewChanged 함수로

이벤트가 전달 됩니다. 


onDependentViewChanged 함수는 dependency 의 View의  변화가 있을때 이벤트가 들어 옵니다. 




위 코드를 작성하면 snackBar 의 변화가 있을때 이벤트가 호출됩니다. 나타나면서 움직일때,
사라지면서 움직일때의 이벤트가 호출됩니다.


snackBar의 이동 Y 값을 
FloationgButton 에 적용 시켜주기 때문에 아까와 같은 동작의 Behavior를 만들 수 있습니다.

이와 같이 행동을 만들고 응용을하면 구글플레이 메인 화면, 이쁜 화면들을 만들수 있을것 같습니다.









[Android, Behavior] CoordinatorLayout 에서 Behavior 사용해 보기


안녕하세요. 블랙진입니다.

CoordinatLayout을 사용해 애니메이션을 구현해야 되는 미션을 받아 공부한 내용을 간략히 정리해 보았습니다.


개요


CoordinatorLayout


CoordinatorLayout 은 강력한 FrameLayout 으로 2가지 주요 행동이 있습니다.


1. 애플리케이션에서 최상위 장식(decor) 뷰로써 사용

2. 자식 뷰들간의 특정한 인터렉션을 위한 컨테이너로써 사용


CoordinatorLayout의 자식뷰에 Behaviors를 지정하는 방식으로 하나의 부모뷰 안에서 다양한 인터렉션을 지원할 수 있고 자식뷰들 간의 인터렉션을 할 수 있습니다. 또한 CoordinatorLayout의 자식뷰는 anchor 와 insetEdge 속성을 가질 수 있습니다. anchor는 CoordinatorLayout의 자식뷰 중 하나이어야 하며 다른 뷰에 맞물러 상대적인 위치에 뷰를 띄우는 용도로 사용됩니다. insetEdge 속성은 자식뷰들이 어떻게 배치될지 지정할 수 있으며 만약 자식뷰가 겹칠 것을 대비해 dodgeInsetEdges 속성을 주어 적절하게 뷰가 겹치지 않도록 배치할 수 있습니다. 


Behavior


자식뷰들간의 하나 혹은 그 이상의 인터렉션을 구현하기 위해 사용합니다. 인터렉션에는 드래그, 스와이드, 플링 또는 제스처가 포함됩니다.


이번 장에서는 Behavior에 대해서만 알아보고 이를 커스텀 마이징을 해보겠습니다. 그러기에 있어 간단한 예제를 통해  Behavior 사용법을 알아보겠습니다.


<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="256dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="?attr/colorPrimaryDark"
app:expandedTitleMarginStart="24dp"
app:expandedTitleMarginEnd="64dp">

...

</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">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="24dp">

...

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

우리는 일반적으로 AppBarLayout 하단에 위치하게 될 위젯에 layout_behavior="@string/appber_scrolling_view_behavior" 속성을 추가하여 사용합니다.


@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout


AppBarLayout에는 보시는 바와 같이 DefaultBehavior이 선언되어 있습니다. CoordinatorLayout은 자식뷰의 변화 상태를 다른 자식뷰들에게 전달해주는 역활을 합니다. 


NestedScrollVIew에 layout_behavio 속성을 추가해 인터렉션을 CoordinatorLayout이 받아 AppBarLayout 에게 알려줍니다. 그렇기 때문에 AppbarLayout 과 NestedScrollView 는 상호작용할 수 있습니다.


그러면 Behavior 대한 간단한 예제를 살펴보겠습니다.


참고한 예제 사이트


xml

<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">

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_margin="16dp"
android:layout_gravity="end|bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

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

activity

fab.setOnClickListener {
Snackbar.make(it, "Hello black jin", Snackbar.LENGTH_LONG).show()
}

간단한 예제 입니다. 플로팅 액션 버튼을 누르면 스낵바가 올라옵니다. 스낵바가 올라오면서 플로팅액션버튼도 함께 올라옵니다.




플로팅액션버튼이 스낵바와 인터렉션 할 수 있는 것은 이미 Behavior 이 구현되어 있기 떄문입니다.


@DefaultBehavior(FloatingActionButton.Behavior.class)
public class FloatingActionButton extends VisibilityAwareImageButton

플로팅액션버튼의 클래스를 보면 DefultBehavior이 구현되어 있습니다. 그렇기 때문에 CoordinatorLayout 안에서의 변화를 받아 인터렉션 할 수 있는 것입니다. 만약 아무 기능이 없는 Behavior 을 적용하면 어떻게 될까요?


public class ExampleBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {

public Example01Behavior() {
}

public Example01Behavior(Context context, AttributeSet attrs) {
super(context, attrs);
}

/**
* 레이아웃에 트리거가 발생하면 불러집니다.
* true 를 리턴하면 onDependentViewChanged 를 부릅니다.
*/
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent,
@NonNull FloatingActionButton child,
@NonNull View dependency) {

//Log.d("MyTag","dependency : " + dependency.getClass());
return dependency instanceof Snackbar.SnackbarLayout;
}

/**
* dependency 의 View 의 변화가 있을때 이벤트가 들어옵니다.
*/
@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent,
@NonNull FloatingActionButton child,
@NonNull View dependency) {

//아무 기능이 없습니다.

return false;
}
}

ExampleBehavior 을 생성한 후 적용해 보겠습니다.

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_margin="16dp"
android:layout_gravity="end|bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="{경로}.ExampleBehavior"/>

layout_behavior에 ExampleBehavior을 적용한 후 실행해 보겠습니다.





보시는 바와 같이 아무 인터렉션도 이뤄 지지 않습니다. 아까와 같은 인터렉션을 할 수 있게 코드를 추가해 보겠습니다.



스낵바 움직임에 따라 함께 움직이는 프로팅액션버튼

@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent,
@NonNull FloatingActionButton child,
@NonNull View dependency) {

float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
child.setTranslationY(translationY);

return false;
}

translationY 함수를 사용해 받은 인터렉션값에 따라 Y 좌표를 이동시켜 줍니다. 이렇게 하면 기존 처럼 스낵바가 올라오면 플로팅액션버튼도 따라 올라가게 됩니다. 여기서 더 나아가 다른 액션을 추가해 보겠습니다.



스낵바 움직임에 따라 크기가 변하는 프로팅액션버튼

@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent,
@NonNull FloatingActionButton child,
@NonNull View dependency) {

float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
float percentComplete = -translationY / dependency.getHeight();
float scaleFactor = 1 - percentComplete;

child.setScaleX(scaleFactor);
child.setScaleY(scaleFactor);

return false;
}

받아온 Y 좌표를 변형해 뷰의 스케일을 변화시켜 줬습니다.




이번에는 스낵바가 올라오면서 플로팅액션버튼이 작아지고 스낵바가 내려가면 플로팅액션버튼이 커지게 됩니다. 이렇게 CoordinatorLayout 안에서 Behavior 을 사용해 뷰간의 인터렉션을 하는 기본 예제를 살펴 보았습니다. 필자는 이 예제를 활용해서 아래와 같은 예제를 만들어 보았습니다.





스크롤 변화를 감지하여 위와 같은 인터렉션이 나오게 구성해 보았습니다. 보시는 바와 같이 Behavior를 사용하면 얼마든지 사용자 반응에 따라 멋진 애니메이션을 구현하실 수 있을 겁니다.




<참고사이트>

CoordinatorLayout과 Behavior의 관계

AppBarLayout에 대한 고찰









CoordinatorLayout과 Behavior의 관계


머트리얼 디자인 가이드 라인중 스크롤시 다양한 반응을 위한 테크닉인 Behavior라는 개념이 도입 되었습니다. 기본적으로 액션바를 확장하여 스크롤시 액션바를 줄여들게 하도록 AppBarLayout의 ScrollingViewBehavior와 스크롤시 하단으로 숨기게 하기위해 BottomSheetBehavior를 서포트라이브러리에서 제공하고 있습니다.

android.support.design.widget.AppBarLayout$ScrollingViewBehavior
- android.support.design.widget.BottomSheetBehavior  


Behavior를 사용하기 위해서는 CoordinatorLayout을 통해서 사용되는데, CoordinatorLayout은 자식뷰의 스크롤의 변화 상태를 다른 자식뷰들에게 전달 해주는 역할을 합니다. 좀더 쉽게 말해 NestedScrollView나 RecyclerView등에 스크롤의 상태를 판단하여 정의된 반응을 하기위한 View에 Behavior를 등록하면 됩니다.

<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"
    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="android.support.design.widget.AppBarLayout$ScrollingViewBehavior">
        <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.CoordinatorLayout>


이해를 돕기위해 안드로이드 서포트 라이브러리에서 제공해주는 Behavior를 한번 보겠습니다. NestedScrollView에 layout_behavior에 AppBarLayout$ScrollingViewBehavior가 정의가 되어있습니다. NestedScrollView의 반응에 따라 AppBarLayout이 반응됩니다.

CoordinatorLayout는 NestedScrollView가 스크롤시 layout_behavior에 정의된 레이아웃으로 스크롤 정보를 전달 하는 역할을 합니다. 그럼 AppBarLayout의 ScrollingViewBehavior가 정보를 받아서 AppBarLayout 자신을 변형하도록 하는 구조입니다.

CoordinatorLayout이 스크롤되는 것은 Behavior에 구현된 NestedScrollingParent를 통해 전달 됩니다. 즉, CoordinatorLayout는 NestedScrollingParent가 구현되어 있으며 스크롤 되는 View들은 NestedScrollingChild가 구현되어 있어야 Behavior가 전달 됩니다. 그렇기 때문에 기존의 ScrollView나 ListView는 NestedScrollingChild가 구현되어 있지 않아 Behavior를 통해 스크롤 정보전달이 되지 않습니다.


이렇게 CoordinatorLayout의 역할과 Behavior의 관계를 알고 있다면 Behavior를 커스텀해서 구현하는데 전혀 문제 없을 것입니다.