使用動畫顯示或隱藏視圖

試用 Compose
Jetpack Compose 是 Android 推薦的 UI 工具包。瞭解如何在 Compose 中使用動畫。

使用應用程式時,新資訊會一併顯示在畫面上,不會改變 系統就會移除相關資訊您可以直接變更螢幕上顯示的內容 讓使用者可能錯過突然出現的新內容。動畫速度緩慢 套用變更,並用動作來勾勒出使用者的視角 更加醒目

有三種常見的動畫可用來顯示或隱藏檢視畫面:顯示 動畫、交叉漸變動畫和 CardFlip 動畫。

建立交叉漸變動畫

交叉漸變動畫 (又稱為溶解液) 逐漸淡出 一張 View 或 同時ViewGroup 或淡出淡出的火花如果您要在部分情況下 在應用程式中切換內容或檢視模式這裡顯示的交叉漸變動畫會使用 ViewPropertyAnimator、 適用於 Android 3.1 (API 級別 12) 以上版本。

以下是從進度指標到文字內容的交叉漸變範例:

圖 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_longAnimTime敬上 和 config_mediumAnimTime。 您也可以使用

以下範例使用先前程式碼片段的版面配置, 活動內容檢視畫面:

KotlinJava
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. 針對淡入效果的檢視區塊,為其 Alpha 值 0 到 1 加上動畫效果。對於 並以 1 到 0 的動畫方式設定 Alpha 值
  3. 使用 onAnimationEnd()敬上 風格 Animator.AnimatorListener, 設定淡出至 GONE 的檢視畫面的顯示設定。雖然 Alpha 值為 0,將檢視畫面的瀏覽權限設為 GONE 會防止檢視畫面出現 不會因為使用版面配置空間而省略了版面配置,所以在計算版面配置時 後續處理作業

以下方法舉例說明相關做法:

KotlinJava
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

卡片外觀如下:

圖 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>

建立檢視畫面

資訊卡的每一面都是獨立的版面配置,可包含 例如兩個文字檢視區塊、兩張圖片,或是任意翻轉的組合 。稍後在製作動畫的片段中使用兩個版面配置。 下列版面配置會建立資訊卡的一側,顯示文字:

<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()敬上 方法。接著,您就可以在父項活動中建立這個片段的例項 並在您要顯示資訊卡的時間點顯示資訊卡

以下範例顯示父項活動中的巢狀片段類別 使用 API

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

在活動程式碼中,將內容檢視畫面設為您建立的版面配置。 建議您在建立活動時顯示預設片段。 以下範例活動會說明如何按 預設:

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

卡片正面會顯示在卡片正面, 在適當時機翻轉動畫。建立顯示 執行以下作業的卡片:

  • 設定您建立的自訂動畫,用於片段轉換功能。
  • 將顯示的片段替換成新片段,並為此事件加上動畫效果 這部分就會顯示自訂動畫
  • 將先前顯示的片段新增至片段返回堆疊, 使用者輕觸返回按鈕時,卡片就會翻回。
KotlinJava
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) 以上版本。

以下範例說明如何顯示先前隱藏的檢視畫面:

KotlinJava
// 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 座標 社交圈。通常是檢視畫面的中心,但您也可以使用 在使用者輕觸之後,動畫就會從他們選取的位置開始播放。 第四個參數是剪裁圓的起始半徑。

在上一個範例中,初始半徑設為 0,因此檢視畫面會顯示 目前由社交圈隱藏。最後一個參數是最終的半徑 圓圈。顯示檢視畫面時,最終半徑應大於 ,這樣檢視畫面就能在動畫結束前完整顯示。

如要隱藏之前可見的檢視畫面,請執行下列步驟:

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

在這種情況下,剪裁圓形的初始半徑會設為大 讓畫面在動畫開始播放前顯示。最後一個 dp 設為 0,以便在動畫結束後隱藏檢視畫面。 在動畫中新增事件監聽器,以便將檢視畫面的顯示設定設為 動畫播放時為 INVISIBLE 完成。

其他資源

  • 使用 Jetpack Compose 進行動畫
  • 使用 Jetpack Compose 建立手勢