반응형

Android에서 View는 Tree 구조로 이루어져 있습니다.

 

layout 을 구성할 때 xml 형태로 ViewGroup과 View를 적절히 배치하여 구성하게 되는데, 이렇게 정의된 xml 파일을 Activity의 setContentView나 LayoutInflater를 통하여 렌더링 하게 됩니다. 그리고 안드로이드는 렌더링 할 때 Tree의 각 노드들에 대해 Pre-Order 방식으로 방문합니다. 즉, Parent View부터 Child View 순으로 방문하게 되는 것이지요.

 

그렇기 때문에 Android 에서 layout 을 그려낼 때에는 Parent View가 Child View보다 먼저 그려지게 됩니다.

Android에서 View를 Draw 하는 과정에 대한 자세한 설명은 developer 사이트를 참고하시면 되겠습니다.

 

여기서 한 가지 알아야 할 특징은, 안드로이드 프레임워크는 기본적으로 Parent View의 범위를 벗어나는 영역에서의 Child View에 대해서 invalid(유효하지 않은)로 판단하여 해당 부분을 그려내지 않는다는 것입니다.

 

이해를 돕기 위해 예제를 살펴보겠습니다.

 

Parent View 하나와 Child View를 각각 하나씩 만들어 Child View의 사이즈를 Parent View의 사이즈보다 크게 설정하여 어떻게 렌더링 되는지 확인해 보겠습니다.

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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"
    tools:context=".MainActivity">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@color/colorPrimary"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <TextView
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:layout_gravity="center"
            android:background="@color/colorAccent"
            android:text="child"
            android:textColor="@android:color/white"
            android:gravity="center"/>
    </FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

 

보시다시피 부모 뷰인 FrameLayout의 높이는 100dp로 설정하고, 자식 뷰의 높이를 150dp로 설정하여 부모의 높이보다 자식의 뷰가 더 크게 그려지는지 확인해보도록 하겠습니다. 위에서 설명한 대로라면 자식 뷰는 부모 뷰의 영역 밖에서는 그려질 수 없기 때문에 부모 뷰보다 더 크게 그려지면 안 됩니다.

 

결과 화면

보시다시피 child의 높이가 parent 보다 더 크게 설정됐음에도 불구하고 parent 영역 바깥에서 더 그려지지가 않습니다. 비단 이 특징은 사이즈뿐만 아니라 margin으로 인해 영역을 벗어나도 마찬가지로 적용되는데요.

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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"
    tools:context=".MainActivity">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@color/colorPrimary"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <TextView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_gravity="center"
            android:background="@color/colorAccent"
            android:layout_marginTop="20dp"
            android:text="child"
            android:textColor="@android:color/white"
            android:gravity="center"/>
    </FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

이번에는 Child View의 사이즈는 Parent View와 똑같이 100dp로 설정하고서 Child View에 margin Top을 20dp 만큼 주어서 자식 뷰가 부모 뷰의 영역 밑으로 범위를 벗어나도록 정의한 후 결과를 살펴보겠습니다.

 

결과 화면

뿐만 아니라 View의 등장 및 퇴장 시 애니메이션을 사용하는 경우가 많은데 이럴 때에도 적용되는 특징이기 때문에 잘 알아둘 필요가 있습니다.

 

그렇다면 이 특징을 적용하고 싶지 않을 때는 어떻게 하면 될까요?

바로 clipChildren이라는 옵션을 사용하면 됩니다.

 

clipChildren 옵션은 ViewGroup에 있는 속성(attributes)으로, 자식 뷰가 그릴 수 있는 범위를 해당 ViewGroup 영역으로 설정해주는 것입니다. 기본 값은 true이며, Child View의 드로잉 범위 제한을 해제하기 위해서는 clipChildren 속성 값을 false로 설정해줘야 합니다.

 

그럼 위 예에서 한 번 적용해서 살펴보겠습니다.

 

두 번째 예제에서 Child View가 제대로 그려지려면 어디에 clipChildren=false을 적용해야 할까요? 아마 많은 분들이 바로 직속 상위 뷰인 FrameLayout에 설정해야 할 것으로 예상하실 텐데요.

 

사실 원하는 결과를 얻기 위해서는 그보다 상위 뷰인 ConstraintLayout 에다가 적용해야 합니다. 왜냐하면 FrameLayout의 영역을 확장해주는 것이 곧 TextView의 제한 범위를 확장하는 결과를 불러오기 때문입니다.

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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:clipChildren="false"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@color/colorPrimary"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <TextView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_gravity="center"
            android:background="@color/colorAccent"
            android:layout_marginTop="20dp"
            android:text="child"
            android:textColor="@android:color/white"
            android:gravity="center"/>
    </FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

결과를 바로 확인해보겠습니다!

 

결과 화면

 

제대로 적용된 결과를 확인했습니다!

 

그럼 이 속성을 xml 수준이 아닌 코드 수준에서 적용하려면 어떻게 해야 할까요?

ViewGroup#setClipChildren(boolean enabled) 메소드를 사용하면 됩니다.

 

마지막으로 특정 뷰의 모든 조상 뷰를 방문하면서 setClipChildren(false)를 적용하는 재귀 함수를 소개해드리고, 포스팅 마무리하겠습니다.

 

java

public void setAllParentsClip(View v) {
    while (v.getParent() != null && v.getParent() instanceof ViewGroup) {
        ViewGroup viewGroup = (ViewGroup) v.getParent();
        viewGroup.setClipChildren(false);
        viewGroup.setClipToPadding(false);
        v = viewGroup;
    }
}

 

kotlin

fun View.setAllParentsClip() {
    var parent = parent
    while (parent is ViewGroup) {
        parent.clipChildren = false
        parent.clipToPadding = false
        parent = parent.parent
    }
}

 

위에서 살펴본 예제 코드는 Github 저장소에서 확인할 수 있습니다.


 

반응형
반응형