使用动画显示或隐藏视图

试用 Compose 方式
Jetpack Compose 是推荐在 Android 设备上使用的界面工具包。了解如何在 Compose 中使用动画。
<ph type="x-smartling-placeholder"></ph> 淡入淡出 →

在使用应用的过程中,屏幕上会显示新信息以及旧信息 信息。你可以立即更改屏幕上显示的内容 令人不悦,并且用户可能会错过突然出现的新内容。动画播放速度缓慢 向下移动并用运动吸引用户的注意力 更加明显

有三种常见的动画可用于显示或隐藏视图:显示 动画、淡入淡出动画和卡片翻转动画

创建淡入淡出动画

淡入淡出动画(也称为渐隐)会逐渐淡出 一个 ViewViewGroup(同时启用) 慢慢地淡入另一个此动画适合在以下情形下 切换内容或视图。此处显示的淡入淡出动画使用了 ViewPropertyAnimator、 适用于 Android 3.1(API 级别 12)及更高版本。

以下是从进度指示器切换到文本内容的淡入淡出示例:

<ph type="x-smartling-placeholder">
图 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

以下示例使用上一个代码段中的布局作为 活动内容视图:

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. 对于淡入的视图,请将 alpha 值设置为 0,并将可见性设置为 从初始值更改为 VISIBLE 设置为 GONE。这会使视图可见但处于透明状态。
  2. 对于淡入的视图,以动画形式呈现其 0 到 1 的 Alpha 值。对于 视图,以动画方式将 alpha 值从 1 到 0。
  3. 使用 onAnimationEnd()Animator.AnimatorListener, 将淡出视图的可见性设置为 GONE。虽然 alpha 值为 0,将视图的可见性设置为 GONE 会阻止视图 并将其从布局计算中省略, 处理。

以下方法通过示例说明了如何执行此操作:

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

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

卡片翻转的效果如下:

<ph type="x-smartling-placeholder">
图 2.卡片翻转动画。

创建 animator 对象

如需创建卡片翻转动画,您需要四个 Animator。两个 Animator 当卡片正面以动画形式呈现出来时,向左侧以动画形式呈现,以及当卡片以动画形式呈现时 左边和左边。另外两个 Animator 适用于在卡背面 分别以动画方式移入和移出右侧元素,以及向右侧以动画形式呈现。

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>

创建视图

卡片的每一面都是一个单独的布局,可以包含您在 例如要翻转的两个文本视图、两张图片或任意视图组合 。使用您稍后要添加动画效果的 fragment 中的两个布局。通过 以下布局会创建卡片的一侧(显示文本):

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

创建 Fragment

为卡片的正面和背面创建 Fragment 类。在 fragment 中 类,则返回从 onCreateView() 方法。然后,您可以在父 activity 中创建此 fragment 的实例 卡片的显示位置

以下示例展示了父 activity 中的嵌套 fragment 类 使用它们:

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

为卡片翻转添加动画

显示父 activity 内的 fragment。为此,请创建布局 以下示例创建了一个 您可以添加的FrameLayout fragment 在运行时:

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

在 activity 代码中,将内容视图设置为您创建的布局。 最好在创建 activity 时显示默认 fragment。通过 以下示例 activity 展示了如何按照 默认值:

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

在显示卡片正面时,您可以同时显示卡片背面和 在适当的时间播放翻转动画。创建方法以显示 可执行以下操作的卡片:

  • 设置您为 Fragment 转换创建的自定义动画。
  • 将显示的 fragment 替换为新的 fragment,并为此事件添加动画效果 自定义动画效果
  • 将之前显示的 fragment 添加到 fragment 返回堆栈中,这样当 当用户点按“返回”按钮时,卡片会翻转回来。
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()
   
}
}

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

创建圆形揭露动画

当您显示或隐藏组时,揭露动画可为用户提供视觉连续性 界面元素组成元素通过 ViewAnimationUtils.createCircularReveal() 方法,您可以通过动画方式呈现裁剪圆形来显示或隐藏视图。这个 中提供了 ViewAnimationUtils 类, 适用于 Android 5.0(API 级别 21)及更高版本。

以下示例展示了如何揭露之前不可见的视图:

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

// 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() 动画采用五个参数。 第一个参数是要在屏幕上隐藏或显示的视图。通过 接下来的两个参数是裁剪中心的 X 和 Y 坐标。 圆圈。这通常是视图的中心,但您也可以使用 用户点按的点,以便动画从用户选择的位置开始。通过 第四个参数是裁剪圆形的起始半径。

在前面的示例中,初始半径设置为零,使视图 正在显示的圆圈被圆圈隐藏了。最后一个参数是 圆圈。显示视图时,使最终半径大于 视图,以便在动画播放完毕之前完全显示视图。

如需隐藏之前可见的视图,请执行以下操作:

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

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

在本例中,裁剪圆形的初始半径设置为 视图,使视图在动画开始播放前可见。最后一个 radius 设置为 0,以便在动画播放完毕时隐藏视图。 为动画添加监听器,以便将视图的可见性设置为 INVISIBLE

其他资源

  • 使用 Jetpack Compose 实现动画
  • Jetpack Compose 的手势