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

Compose 방법 사용해 보기
Jetpack Compose는 Android에 권장되는 UI 도구 키트입니다. Compose에서 애니메이션을 사용하는 방법을 알아보세요.

앱이 사용되는 동안 새로운 정보가 화면에 표시되고 이전 정보는 삭제됩니다. 화면에 표시되는 내용을 즉시 변경하는 것은 불편할 수 있으며 사용자는 갑자기 표시되는 새로운 콘텐츠를 놓칠 수 있습니다. 애니메이션은 변화 속도를 늦추고 모션으로 사용자의 시선을 끌어 업데이트가 더 명확하게 표시되도록 합니다.

뷰를 표시하거나 숨기는 데 사용할 수 있는 일반적인 애니메이션으로는 표시 애니메이션, 크로스페이드 애니메이션, 카드플립 애니메이션 등이 있습니다.

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

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

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

그림 1. 크로스페이드 애니메이션

뷰 만들기

크로스페이드할 두 개의 뷰를 만듭니다. 다음 예에서는 진행률 표시기와 스크롤 가능한 텍스트 뷰를 만듭니다.

<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)
    }
    ...
}

Java

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으로, 가시성을 초기 GONE 설정에서 VISIBLE로 설정합니다. 이렇게 하면 뷰가 표시되지만 투명합니다.
  2. 페이드인되는 뷰의 알파 값을 0에서 1로 애니메이션합니다. 페이드아웃되는 뷰의 알파 값을 1에서 0으로 애니메이션합니다.
  3. Animator.AnimatorListeneronAnimationEnd()를 사용하여 페이드아웃되는 뷰의 공개 상태를 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 so it doesn't
        // participate in layout passes.
        loadingView.animate()
                .alpha(0f)
                .setDuration(shortAnimationDuration.toLong())
                .setListener(object : AnimatorListenerAdapter() {
                    override fun onAnimationEnd(animation: Animator) {
                        loadingView.visibility = View.GONE
                    }
                })
    }
}

Java

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 so it doesn't
        // participate in layout passes.
        loadingView.animate()
                .alpha(0f)
                .setDuration(shortAnimationDuration)
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        loadingView.setVisibility(View.GONE);
                    }
                });
    }
}

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

카드플립은 뒤집히는 카드를 에뮬레이션하는 애니메이션을 표시하여 콘텐츠 뷰 간에 전환합니다. 여기에 표시된 카드플립 애니메이션에서는 FragmentTransaction를 사용합니다.

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

그림 2. 카드플립 애니메이션

애니메이터 객체 만들기

카드플립 애니메이션을 만들려면 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" />

    <!-- Halfway through the rotation, set the alpha to 1. See startOffset. -->
    <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" />

    <!-- Halfway through the rotation, set the alpha to 0. See startOffset. -->
    <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" />

    <!-- Halfway through the rotation, set the alpha to 1. See startOffset. -->
    <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" />

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

뷰 만들기

카드의 각 면은 원하는 콘텐츠를 포함할 수 있는 별도의 레이아웃입니다(예: 텍스트 뷰 2개, 이미지 2개, 뒤집을 뷰 조합 등). 나중에 애니메이션을 적용할 프래그먼트에서 두 레이아웃을 사용합니다. 다음 레이아웃은 텍스트를 보여주는 카드의 한 면을 만듭니다.

<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)
    }
}

Java

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()
        }
    }
    ...
}

Java

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, such as when the
                // system Back button is tapped.
                .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 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, letting users press
                // the Back button to get to the front of the card.
                .addToBackStack(null)

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

Java

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, such as 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 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, letting users press
                // Back to get to the front of the card.
                .addToBackStack(null)

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

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

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

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

Kotlin

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

// Check whether the runtime version is at least Android 5.0.
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 0.
    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
    // Android 5.0.
    myView.visibility = View.INVISIBLE
}

Java

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

// Check whether the runtime version is at least Android 5.0.
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 0.
    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
    // Android 5.0.
    myView.setVisibility(View.INVISIBLE);
}

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

이전 예에서는 초기 반지름이 0으로 설정되어 표시되는 뷰가 원에 의해 숨겨집니다. 마지막 매개변수는 원의 최종 반지름입니다. 뷰를 표시할 때 애니메이션이 완료되기 전에 뷰가 완전히 표시되도록 최종 반경을 뷰보다 크게 만듭니다.

이전에 표시된 뷰를 숨기려면 다음 단계를 따르세요.

Kotlin

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

// Check whether the runtime version is at least Android 5.0.
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 0.
    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
    // Android 5.0.
    myView.visibility = View.VISIBLE
}

Java

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

// Check whether the runtime version is at least Android 5.0.
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 0.
    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 Android
    // 5.0.
    myView.setVisibility(View.VISIBLE);
}

이 경우 애니메이션이 시작되기 전에 뷰가 표시되도록 클리핑 서클의 초기 반지름이 뷰와 같도록 설정됩니다. 애니메이션이 완료되면 뷰가 숨겨지도록 최종 반경을 0으로 설정합니다. 애니메이션이 완료될 때 뷰의 공개 상태를 INVISIBLE로 설정할 수 있도록 애니메이션에 리스너를 추가합니다.

추가 리소스