웹사이트 검색

Android Coordinator레이아웃


지금까지 많은 자습서에서 Android CoordinatorLayout을 사용했습니다. 그러나 우리는 그것에 대해 자세히 다루지 않았습니다. 이 튜토리얼에서는 Android 앱의 CoordinatorLayout에 대해 논의하고 사용자 정의합니다.

Android Coordinator레이아웃

CoordinatorLayout 동작

CoordinatorLayout 내의 FAB는 다른 보기가 FAB와 상호 작용할 때 그에 따라 애니메이션되도록 하는 기본 동작으로 지정되었습니다. Ctrl/CMD+를 수행하여 레이아웃/활동에서 FloatingActionButton을 클릭하면 주석이 있는 클래스에 동작이 정의된 것을 볼 수 있습니다. 다음과 같아야 합니다.

@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)

FloatingActionButton.Behavior는 FAB에서 사용되는 기본 Behavior 클래스입니다. CoordinatorLayout.Behavior 클래스를 확장하여 고유한 동작을 정의할 수 있습니다. 여기서 T는 Behavior를 정의하려는 클래스입니다. 위의 경우 CoordinatorLayout.Behavior입니다.

  • 비헤이비어는 CoordinatorLayout의 직계 자식에서만 작동합니다.
  • CoordinatorLayout이 활동의 루트 레이아웃이어야 합니다.

이제 화면 하단에 Button 위젯을 추가해 보겠습니다. activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.journaldev.coordinatorlayoutbehaviours.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

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

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

    <include layout="@layout/content_main" />

    <!--<android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@android:drawable/ic_dialog_email" />-->

    <android.support.v7.widget.AppCompatButton
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|start"
        android:text="CLICK ME"
        android:id="@+id/button"
        android:layout_margin="@dimen/fab_margin"
        android:layout_width="match_parent"/>

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

위 레이아웃에서 FAB를 주석 처리했습니다. 이제 아래와 같이 MainActivity.java에서 FloatingActionButton 수신기를 AppCompatButton으로 바꿉니다.

AppCompatButton fab = (AppCompatButton) findViewById(R.id.button);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Hey Button. Define a Custom Behaviour. Else I'll take your space", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

  • child : 동작이 수행되는 보기입니다.
  • 의존성: 자식에 대한 행동을 유발하는 보기입니다.

위의 경우 AppCompatButton은 자식이고 SnackBar는 종속성입니다. 참고: FloatingActionButton 기본 동작의 경우 종속성은 SnackBar만이 아닙니다. FloatingActionButton에서 동작을 트리거하는 다른 View 요소도 있습니다. AppCompatButton을 위로 이동하는 사용자 정의 동작 클래스를 생성하여 시작하겠습니다. 이름을 CustomMoveUpBehavior.java로 지정하겠습니다.

public class CustomMoveUpBehavior extends CoordinatorLayout.Behavior {

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

}

위 클래스에서 재정의해야 하는 두 가지 필수 메서드는 layoutDependsOn 및 onDependentViewChanged입니다. 우리 클래스에 재정의를 추가합시다.

package com.journaldev.coordinatorlayoutbehaviours;

import android.os.Build;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.view.View;

public class CustomMoveUpBehavior extends CoordinatorLayout.Behavior {

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency instanceof Snackbar.SnackbarLayout;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
        child.setTranslationY(translationY);
        return true;
    }

}

layoutDependsOn은 동작을 트리거하는 종속성이 SnackBar의 인스턴스인지 확인합니다. onDependentViewChanged는 기본 수학 계산을 기반으로 자식 보기(AppCompatbutton)를 위로 이동하는 데 사용됩니다.

Android CoordinatorLayout에 동작 연결

CustomMoveUpBehavior.java를 연결하기 위해 Custom AppCompatButton을 만들고 아래와 같이 주석을 추가합니다.

package com.journaldev.coordinatorlayoutbehaviours;

import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.widget.AppCompatButton;
import android.util.AttributeSet;

@CoordinatorLayout.DefaultBehavior(CustomMoveUpBehavior.class)
public class CustomButton extends AppCompatButton {
    public CustomButton(Context context) {
        super(context);
    }

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

    public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}

activity_main.xml 및 MainActivity.java에서 다음과 같이 변경합니다. AppCompatButton xml 태그를 다음으로 바꿉니다.

<com.journaldev.coordinatorlayoutbehaviours.CustomButton
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|start"
        android:text="CLICK ME"
        android:id="@+id/button"
        android:layout_margin="@dimen/fab_margin"
        android:layout_width="match_parent"/>

MainActivity.java에서 각 버튼 onClickListener를 교체합니다.

CustomButton fab = (CustomButton) findViewById(R.id.button);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Hey Button. Define a Custom Behaviour. Else I'll take your space", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
public class CustomRotateBehavior extends CoordinatorLayout.Behavior {

  public CustomRotateBehavior(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 = getFabTranslationYForSnackbar(parent, child);
        float percentComplete = -translationY / dependency.getHeight();
        child.setRotation(180 * percentComplete);
        child.setTranslationY(translationY);
        return false;
    }

    private float getFabTranslationYForSnackbar(CoordinatorLayout parent,
                                                FloatingActionButton fab) {
        float minOffset = 0;
        final List dependencies = parent.getDependencies(fab);
        for (int i = 0, z = dependencies.size(); i < z; i++) {
            final View view = dependencies.get(i);
            if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
                minOffset = Math.min(minOffset,
                        ViewCompat.getTranslationY(view) - view.getHeight());
            }
        }

        return minOffset;
    }

}

getFabTranslationYForSnackbar(parent,child) 메서드는 FAB가 변경을 시작하기 위해 SnackBar가 화면 위로 올라와야 하는 정도를 계산합니다. 관련 변경을 수행하기 전에 프로젝트 구조를 살펴보겠습니다.

Android CoordinatorLayout 예제 프로젝트 구조

Android CoordinatorLayout 예제 코드

이제 FloatingActionButton을 확장하는 대신 FloatingActionButton 보기에서 app:layout_behavior를 정의하고 하위 클래스를 가리킬 수 있습니다. 이것이 현재 activity_main.xml의 모습입니다.

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.journaldev.coordinatorlayoutbehaviours.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

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

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

    <include layout="@layout/content_main" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        app:layout_behavior="com.journaldev.coordinatorlayoutbehaviours.CustomRotateBehavior"
        android:src="@android:drawable/arrow_down_float" />

    <!--<com.journaldev.coordinatorlayoutbehaviours.CustomButton
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|start"
        android:text="CLICK ME"
        android:id="@+id/button"
        android:layout_margin="@dimen/fab_margin"
        android:layout_width="match_parent"/>-->

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

MainActivity.java는 이제 다음과 같습니다.

package com.journaldev.coordinatorlayoutbehaviours;

import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.AppCompatButton;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        /*CustomButton fab = (CustomButton) findViewById(R.id.button);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "You've added the CustomMoveUpBehavior. Now I'll let you move", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });*/

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Hey FAB. Please Rotate 180 degrees when I'm up.", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Android CoordinatorLayout 예제 프로젝트 다운로드

참조: 공식 문서