애니메이션으로 뷰 표시 또는 숨기기

앱 사용 시 오래된 정보가 삭제되는 동안 새 정보가 화면에 표시되어야 합니다. 표시 내용이 빠르게 전환되면 부자연스럽게 보이거나 사용자가 화면에서 새 콘텐츠를 쉽게 놓칠 수 있습니다. 애니메이션을 사용하면 변화 속도가 느리고 움직임으로 사용자의 시선을 끌 수 있으므로 변경 사항을 더 분명하게 알 수 있습니다.

뷰를 표시하거나 숨길 때 흔히 사용하는 애니메이션에는 3가지가 있습니다. 회전 표시 애니메이션과 크로스페이드 애니메이션, 카드플립 애니메이션이 이에 해당합니다.

크로스페이드 애니메이션 만들기

크로스페이드 애니메이션(디졸브 애니메이션이라고도 함)은 하나의 View 또는 ViewGroup을 점진적으로 페이드아웃하면서 동시에 다른 View 또는 ViewGroup을 페이드인합니다. 이 애니메이션은 앱에서 콘텐츠 또는 뷰를 전환하고 싶을 때 유용합니다. 여기에 표시된 크로스페이드 애니메이션에는 Android 3.1(API 레벨 12) 이상에 사용 가능한 ViewPropertyAnimator가 사용됩니다.

다음은 진행률 표시기에서 텍스트 콘텐츠로 바뀌는 크로스페이드의 예입니다.

크로스페이드 애니메이션
 

뷰 만들기

먼저, 크로스페이드할 뷰를 두 개 만들어야 합니다. 다음 예제에서는 진행률 표시기와 스크롤 가능한 텍스트 뷰를 만듭니다.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/content"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView style="?android:textAppearanceMedium"
                android:lineSpacingMultiplier="1.2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/lorem_ipsum"
                android:padding="16dp" />

        </ScrollView>

        <ProgressBar android:id="@+id/loading_spinner"
            style="?android:progressBarStyleLarge"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />

    </FrameLayout>
    

크로스페이드 애니메이션 설정

크로스페이드 애니메이션을 설정하는 방법은 다음과 같습니다.

  1. 크로스페이드할 뷰의 멤버 변수를 만들어야 합니다. 이러한 참조는 나중에 애니메이션 중에 뷰를 수정할 때 필요합니다.
  2. 페이드인되는 뷰에 대해 가시성을 GONE으로 설정합니다. 이렇게 하면 뷰가 레이아웃 공간을 차지하지 않고 레이아웃 계산에서 생략되어 처리 속도가 빨라집니다.
  3. 멤버 변수에서 config_shortAnimTime 시스템 속성을 캐시합니다. 이 속성은 애니메이션의 '짧은' 표준 재생 시간을 정의합니다. 이 재생 시간은 섬세한 애니메이션 또는 자주 발생하는 애니메이션에 이상적입니다. 원한다면 config_longAnimTimeconfig_mediumAnimTime도 사용할 수 있습니다.

다음은 이전 코드 스니펫에서 레이아웃을 활동 콘텐츠 뷰로 사용하는 예제입니다.

Kotlin

    class CrossfadeActivity : Activity() {

        private lateinit var contentView: View
        private lateinit var loadingView: View
        private var shortAnimationDuration: Int = 0

        ...

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_crossfade)

            contentView = findViewById(R.id.content)
            loadingView = findViewById(R.id.loading_spinner)

            // Initially hide the content view.
            contentView.visibility = View.GONE

            // Retrieve and cache the system's default "short" animation time.
            shortAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime)
        }
        ...
    }

    

자바

    public class CrossfadeActivity extends Activity {

        private View contentView;
        private View loadingView;
        private int shortAnimationDuration;

        ...

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_crossfade);

            contentView = findViewById(R.id.content);
            loadingView = findViewById(R.id.loading_spinner);

            // Initially hide the content view.
            contentView.setVisibility(View.GONE);

            // Retrieve and cache the system's default "short" animation time.
            shortAnimationDuration = getResources().getInteger(
                    android.R.integer.config_shortAnimTime);
        }
        ...
    }

    

뷰 크로스페이드

이제 뷰가 적절히 설정되었으므로 다음 단계를 통해 뷰를 크로스페이드합니다.

  1. 페이드인되는 뷰의 경우 알파 값을 0으로, 가시성을 VISIBLE로 설정합니다.(처음에는 GONE으로 설정되었음을 기억합니다.) 이렇게 하면 뷰가 표시되기는 하지만 완전히 투명한 상태입니다.
  2. 페이드인되는 뷰의 알파 값을 0에서 1로 애니메이션합니다. 페이드아웃되는 뷰의 알파 값을 1에서 0으로 애니메이션합니다.
  3. onAnimationEnd()Animator.AnimatorListener에서 사용하여 페이드아웃된 뷰의 가시성을 GONE으로 설정합니다. 알파 값이 0이더라도 뷰의 가시성을 GONE으로 설정하면 뷰가 레이아웃 공간을 차지하지 않고 레이아웃 계산에서 생략되어 처리 속도가 빨라집니다.

다음은 이 작업을 어떻게 수행하는지를 보여주는 예제입니다.

Kotlin

    class CrossfadeActivity : Activity() {

        private lateinit var contentView: View
        private lateinit var loadingView: View
        private var shortAnimationDuration: Int = 0

        ...

        private fun crossfade() {
            contentView.apply {
                // Set the content view to 0% opacity but visible, so that it is visible
                // (but fully transparent) during the animation.
                alpha = 0f
                visibility = View.VISIBLE

                // Animate the content view to 100% opacity, and clear any animation
                // listener set on the view.
                animate()
                        .alpha(1f)
                        .setDuration(shortAnimationDuration.toLong())
                        .setListener(null)
            }
            // Animate the loading view to 0% opacity. After the animation ends,
            // set its visibility to GONE as an optimization step (it won't
            // participate in layout passes, etc.)
            loadingView.animate()
                    .alpha(0f)
                    .setDuration(shortAnimationDuration.toLong())
                    .setListener(object : AnimatorListenerAdapter() {
                        override fun onAnimationEnd(animation: Animator) {
                            loadingView.visibility = View.GONE
                        }
                    })
        }
    }

    

자바

    public class CrossfadeActivity extends Activity {

        private View contentView;
        private View loadingView;
        private int shortAnimationDuration;

        ...

        private void crossfade() {

            // Set the content view to 0% opacity but visible, so that it is visible
            // (but fully transparent) during the animation.
            contentView.setAlpha(0f);
            contentView.setVisibility(View.VISIBLE);

            // Animate the content view to 100% opacity, and clear any animation
            // listener set on the view.
            contentView.animate()
                    .alpha(1f)
                    .setDuration(shortAnimationDuration)
                    .setListener(null);

            // Animate the loading view to 0% opacity. After the animation ends,
            // set its visibility to GONE as an optimization step (it won't
            // participate in layout passes, etc.)
            loadingView.animate()
                    .alpha(0f)
                    .setDuration(shortAnimationDuration)
                    .setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            loadingView.setVisibility(View.GONE);
                        }
                    });
        }
    }

    

카드플립 애니메이션 만들기

카드플립은 뒤집히는 카드를 에뮬레이션하는 애니메이션을 표시하여 두 콘텐츠 뷰 간을 애니메이션합니다. 여기에 나와 있는 카드플립 애니메이션에서는 FragmentTransaction을 사용합니다. 이 요소는 Android 3.0(API 레벨 11) 이상에서 사용 가능합니다.

다음은 카드플립 애니메이션입니다.

카드플립 애니메이션
 

Animator 개체 만들기

카드플립 애니메이션을 만들려면 총 4개의 애니메이터가 필요합니다. 카드 앞면이 애니메이션되어 왼쪽으로 뒤집혔다가 왼쪽에서 넘어오는 장면에 사용할 두 개의 애니메이터가 필요합니다. 또한 카드 뒷면이 애니메이션되어 오른쪽에서 넘어왔다가 오른쪽으로 뒤집히는 장면에 사용할 두 개의 애니메이터가 필요합니다.

card_flip_left_in.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- Before rotating, immediately set the alpha to 0. -->
        <objectAnimator
            android:valueFrom="1.0"
            android:valueTo="0.0"
            android:propertyName="alpha"
            android:duration="0" />

        <!-- Rotate. -->
        <objectAnimator
            android:valueFrom="-180"
            android:valueTo="0"
            android:propertyName="rotationY"
            android:interpolator="@android:interpolator/accelerate_decelerate"
            android:duration="@integer/card_flip_time_full" />

        <!-- Half-way through the rotation (see startOffset), set the alpha to 1. -->
        <objectAnimator
            android:valueFrom="0.0"
            android:valueTo="1.0"
            android:propertyName="alpha"
            android:startOffset="@integer/card_flip_time_half"
            android:duration="1" />
    </set>
    

card_flip_left_out.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- Rotate. -->
        <objectAnimator
            android:valueFrom="0"
            android:valueTo="180"
            android:propertyName="rotationY"
            android:interpolator="@android:interpolator/accelerate_decelerate"
            android:duration="@integer/card_flip_time_full" />

        <!-- Half-way through the rotation (see startOffset), set the alpha to 0. -->
        <objectAnimator
            android:valueFrom="1.0"
            android:valueTo="0.0"
            android:propertyName="alpha"
            android:startOffset="@integer/card_flip_time_half"
            android:duration="1" />
    </set>
    

card_flip_right_in.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- Before rotating, immediately set the alpha to 0. -->
        <objectAnimator
            android:valueFrom="1.0"
            android:valueTo="0.0"
            android:propertyName="alpha"
            android:duration="0" />

        <!-- Rotate. -->
        <objectAnimator
            android:valueFrom="180"
            android:valueTo="0"
            android:propertyName="rotationY"
            android:interpolator="@android:interpolator/accelerate_decelerate"
            android:duration="@integer/card_flip_time_full" />

        <!-- Half-way through the rotation (see startOffset), set the alpha to 1. -->
        <objectAnimator
            android:valueFrom="0.0"
            android:valueTo="1.0"
            android:propertyName="alpha"
            android:startOffset="@integer/card_flip_time_half"
            android:duration="1" />
    </set>
    

card_flip_right_out.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- Rotate. -->
        <objectAnimator
            android:valueFrom="0"
            android:valueTo="-180"
            android:propertyName="rotationY"
            android:interpolator="@android:interpolator/accelerate_decelerate"
            android:duration="@integer/card_flip_time_full" />

        <!-- Half-way through the rotation (see startOffset), set the alpha to 0. -->
        <objectAnimator
            android:valueFrom="1.0"
            android:valueTo="0.0"
            android:propertyName="alpha"
            android:startOffset="@integer/card_flip_time_half"
            android:duration="1" />
    </set>
    

뷰 만들기

'카드'의 각 면은 원하는 콘텐츠를 포함할 수 있는 별개의 레이아웃입니다. 예를 들면 두 개의 텍스트 뷰, 두 개의 이미지 등 서로 전환할 수 있는 다양한 뷰 조합 같은 콘텐츠를 포함할 수 있습니다. 두 개의 레이아웃은 나중에 애니메이션할 프래그먼트에 사용됩니다. 다음 레이아웃은 텍스트를 표시하는, 카드의 한 면을 만듭니다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="#a6c"
        android:padding="16dp"
        android:gravity="bottom">

        <TextView android:id="@android:id/text1"
            style="?android:textAppearanceLarge"
            android:textStyle="bold"
            android:textColor="#fff"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/card_back_title" />

        <TextView style="?android:textAppearanceSmall"
            android:textAllCaps="true"
            android:textColor="#80ffffff"
            android:textStyle="bold"
            android:lineSpacingMultiplier="1.2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/card_back_description" />

    </LinearLayout>
    

다음 레이아웃은 ImageView를 표시하는, 카드의 다른 면을 만듭니다.

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/image1"
        android:scaleType="centerCrop"
        android:contentDescription="@string/description_image_1" />
    

프래그먼트 만들기

카드의 앞면과 뒷면을 위한 프래그먼트 클래스를 만듭니다. 프래그먼트 클래스는 각 프래그먼트의 onCreateView() 메서드에서 이전에 만든 레이아웃을 반환합니다. 그러면 카드를 표시하고자 하는 상위 활동에서 이 프래그먼트의 인스턴스를 만들 수 있습니다. 다음 예제에서는 상위 활동 내에 중첩된 프래그먼트 클래스를 보여줍니다.

Kotlin

    class CardFlipActivity : FragmentActivity() {
        ...
        /**
         * A fragment representing the front of the card.
         */
        class CardFrontFragment : Fragment() {

            override fun onCreateView(
                    inflater: LayoutInflater,
                    container: ViewGroup?,
                    savedInstanceState: Bundle?
            ): View = inflater.inflate(R.layout.fragment_card_front, container, false)
        }

        /**
         * A fragment representing the back of the card.
         */
        class CardBackFragment : Fragment() {

            override fun onCreateView(
                    inflater: LayoutInflater,
                    container: ViewGroup?,
                    savedInstanceState: Bundle?
            ): View = inflater.inflate(R.layout.fragment_card_back, container, false)
        }
    }

    

자바

    public class CardFlipActivity extends FragmentActivity {
        ...
        /**
         * A fragment representing the front of the card.
         */
        public class CardFrontFragment extends Fragment {
            @Override
            public View onCreateView(LayoutInflater inflater, ViewGroup container,
                    Bundle savedInstanceState) {
                return inflater.inflate(R.layout.fragment_card_front, container, false);
            }
        }

        /**
         * A fragment representing the back of the card.
         */
        public class CardBackFragment extends Fragment {
            @Override
            public View onCreateView(LayoutInflater inflater, ViewGroup container,
                    Bundle savedInstanceState) {
                return inflater.inflate(R.layout.fragment_card_back, container, false);
            }
        }
    }

    

카드 플립 애니메이션

이제 상위 활동 내에 있는 프래그먼트를 표시해야 합니다. 이를 위해 활동에 대한 레이아웃을 먼저 만듭니다. 다음 예제에서는 런타임에 프래그먼트를 추가할 수 있는 FrameLayout을 만듭니다.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    

활동 코드에서 콘텐츠 뷰를 방금 만든 레이아웃으로 설정합니다. 또한 활동이 생성되면 기본 프래그먼트를 표시하는 것이 좋습니다. 이런 이유로 다음 예제 활동에서는 기본적으로 카드 앞면을 표시하는 방법을 보여줍니다.

Kotlin

    class CardFlipActivity : FragmentActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_activity_card_flip)
            if (savedInstanceState == null) {
                supportFragmentManager.beginTransaction()
                        .add(R.id.container, CardFrontFragment())
                        .commit()
            }
        }
        ...
    }

    

자바

    public class CardFlipActivity extends FragmentActivity {

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_activity_card_flip);

            if (savedInstanceState == null) {
                getSupportFragmentManager()
                        .beginTransaction()
                        .add(R.id.container, new CardFrontFragment())
                        .commit();
            }
        }
        ...
    }

    

이제 카드 앞면이 표시되었으므로 적절한 시간에 플립 애니메이션으로 카드 뒷면을 표시할 수 있습니다. 카드의 다른 면을 보여주는 메서드를 만듭니다. 이 메서드는 다음 작업을 합니다.

  • 프래그먼트 전환을 위해 이전에 만든 맞춤 애니메이션을 설정합니다.
  • 현재 표시된 프래그먼트를 새 프래그먼트로 바꾸고 이 이벤트를 이전에 만든 맞춤 애니메이션으로 애니메이션합니다.
  • 이전에 표시된 프래그먼트를 프래그먼트 백 스택에 추가합니다. 그러면 사용자가 뒤로 단추를 누르면 카드가 다시 뒤집힙니다.

Kotlin

    class CardFlipActivity : FragmentActivity() {

        ...

        private fun flipCard() {
            if (showingBack) {
                supportFragmentManager.popBackStack()
                return
            }

            // Flip to the back.

            showingBack = true

            // Create and commit a new fragment transaction that adds the fragment for
            // the back of the card, uses custom animations, and is part of the fragment
            // manager's back stack.

            supportFragmentManager.beginTransaction()

                    // Replace the default fragment animations with animator resources
                    // representing rotations when switching to the back of the card, as
                    // well as animator resources representing rotations when flipping
                    // back to the front (e.g. when the system Back button is pressed).
                    .setCustomAnimations(
                            R.animator.card_flip_right_in,
                            R.animator.card_flip_right_out,
                            R.animator.card_flip_left_in,
                            R.animator.card_flip_left_out
                    )

                    // Replace any fragments currently in the container view with a
                    // fragment representing the next page (indicated by the
                    // just-incremented currentPage variable).
                    .replace(R.id.container, CardBackFragment())

                    // Add this transaction to the back stack, allowing users to press
                    // Back to get to the front of the card.
                    .addToBackStack(null)

                    // Commit the transaction.
                    .commit()
        }
    }

    

자바

    public class CardFlipActivity extends FragmentActivity {

        ...

        private void flipCard() {
            if (showingBack) {
                getSupportFragmentManager().popBackStack();
                return;
            }

            // Flip to the back.

            showingBack = true;

            // Create and commit a new fragment transaction that adds the fragment for
            // the back of the card, uses custom animations, and is part of the fragment
            // manager's back stack.

            getSupportFragmentManager()
                    .beginTransaction()

                    // Replace the default fragment animations with animator resources
                    // representing rotations when switching to the back of the card, as
                    // well as animator resources representing rotations when flipping
                    // back to the front (e.g. when the system Back button is pressed).
                    .setCustomAnimations(
                            R.animator.card_flip_right_in,
                            R.animator.card_flip_right_out,
                            R.animator.card_flip_left_in,
                            R.animator.card_flip_left_out)

                    // Replace any fragments currently in the container view with a
                    // fragment representing the next page (indicated by the
                    // just-incremented currentPage variable).
                    .replace(R.id.container, new CardBackFragment())

                    // Add this transaction to the back stack, allowing users to press
                    // Back to get to the front of the card.
                    .addToBackStack(null)

                    // Commit the transaction.
                    .commit();
        }
    }

    

회전 표시 애니메이션 만들기

표시 애니메이션은 UI 요소 그룹을 표시하거나 숨길 때 사용자에게 시각적 연속성을 제공합니다. ViewAnimationUtils.createCircularReveal() 메서드를 사용하면 뷰를 표시하거나 숨기기 위해 클리핑 서클을 애니메이션할 수 있습니다. 이 애니메이션은 ViewAnimationUtils 클래스에서 제공되며, 이 클래스는 Android 5.0(API 레벨 21) 이상용으로 제공됩니다.

다음은 이전에 보이지 않았던 뷰를 표시하는 방법을 보여주는 예제입니다.

Kotlin

    // previously invisible view
    val myView: View = findViewById(R.id.my_view)

    // Check if the runtime version is at least Lollipop
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        // get the center for the clipping circle
        val cx = myView.width / 2
        val cy = myView.height / 2

        // get the final radius for the clipping circle
        val finalRadius = Math.hypot(cx.toDouble(), cy.toDouble()).toFloat()

        // create the animator for this view (the start radius is zero)
        val anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0f, finalRadius)
        // make the view visible and start the animation
        myView.visibility = View.VISIBLE
        anim.start()
    } else {
        // set the view to invisible without a circular reveal animation below Lollipop
        myView.visibility = View.INVISIBLE
    }

    

자바

    // previously invisible view
    View myView = findViewById(R.id.my_view);

    // Check if the runtime version is at least Lollipop
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        // get the center for the clipping circle
        int cx = myView.getWidth() / 2;
        int cy = myView.getHeight() / 2;

        // get the final radius for the clipping circle
        float finalRadius = (float) Math.hypot(cx, cy);

        // create the animator for this view (the start radius is zero)
        Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0f, finalRadius);

        // make the view visible and start the animation
        myView.setVisibility(View.VISIBLE);
        anim.start();
    } else {
        // set the view to invisible without a circular reveal animation below Lollipop
        myView.setVisibility(View.INVISIBLE);
    }

    

ViewAnimationUtils.createCircularReveal() 애니메이션에는 5개의 매개변수가 사용됩니다. 첫 번째 매개변수는 화면에서 숨기거나 표시하려는 뷰입니다. 그다음 두 개의 매개변수는 클리핑 서클 중심에 대한 x, y 좌표입니다. 일반적으로 이것이 뷰의 중심이 되지만, 사용자가 터치한 지점도 사용할 수 있습니다. 이 경우 사용자가 선택한 위치에서 애니메이션이 시작됩니다. 네 번째 매개변수는 클리핑 서클의 시작 반지름입니다.

위 예제에서 초기 반지름이 0으로 설정되었기 때문에 뷰가 서클에 숨겨집니다. 마지막 매개변수는 서클의 최종 반지름입니다. 뷰를 표시할 때 최종 반지름이 뷰 자체보다 커야 합니다. 그래야 애니메이션이 끝나기 전에 뷰가 완전히 표시됩니다.

이전에 표시된 뷰를 숨기려면 다음을 수행합니다.

Kotlin

    // previously visible view
    val myView: View = findViewById(R.id.my_view)

    // Check if the runtime version is at least Lollipop
    if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
        // get the center for the clipping circle
        val cx = myView.width / 2
        val cy = myView.height / 2

        // get the initial radius for the clipping circle
        val initialRadius = Math.hypot(cx.toDouble(), cy.toDouble()).toFloat()

        // create the animation (the final radius is zero)
        val anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0f)

        // make the view invisible when the animation is done
        anim.addListener(object : AnimatorListenerAdapter() {

            override fun onAnimationEnd(animation: Animator) {
                super.onAnimationEnd(animation)
                myView.visibility = View.INVISIBLE
            }
        })

        // start the animation
        anim.start()
    } else {
        // set the view to visible without a circular reveal animation below Lollipop
        myView.visibility = View.VISIBLE
    }

    

자바

    // previously visible view
    final View myView = findViewById(R.id.my_view);

    // Check if the runtime version is at least Lollipop
    if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
        // get the center for the clipping circle
        int cx = myView.getWidth() / 2;
        int cy = myView.getHeight() / 2;

        // get the initial radius for the clipping circle
        float initialRadius = (float) Math.hypot(cx, cy);

        // create the animation (the final radius is zero)
        Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0f);

        // make the view invisible when the animation is done
        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                myView.setVisibility(View.INVISIBLE);
            }
        });

        // start the animation
        anim.start();
    } else {
        // set the view to visible without a circular reveal animation below Lollipop
        myView.setVisibility(View.VISIBLE);
    }

    

이 경우 클리핑 서클의 초기 반지름 크기가 뷰와 같도록 설정되었기 때문에 애니메이션이 시작되기 전에 뷰가 표시됩니다. 최종 반지름이 0으로 설정되어 애니메이션이 끝나면 뷰가 숨겨집니다. 애니메이션이 완료되면 뷰의 가시성을 INVISIBLE로 설정할 수 있도록 애니메이션에 리스너를 추가하는 것이 중요합니다.