大概思路是先建立兩個 View 疊在一起,一前一後,前面的顯示,後面的隱藏。 旋轉的時候分三步驟, 第一步將前面的 View 從 0 度旋轉到 90 度, 第二步將前面的 View 隱藏,後面的 View 顯示, 第三將將後面的 View 從 -90 度旋轉到 0 度, 這樣就完成了一次卡片旋轉動畫,下面來看看實際的程式碼該怎麼寫。

第一步:建立佈局檔

# activity_main.xml:建立兩個 View 疊在一起,一前一後,前面的顯示,後面的隱藏。

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

                    <androidx.constraintlayout.widget.ConstraintLayout
                        android:id="@+id/viewFront"
                        android:layout_width="300dp"
                        android:layout_height="400dp"
                        android:background="#FF1E88A8"
                        app:layout_constraintDimensionRatio="1:1"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintTop_toTopOf="parent">

                        <Button
                            android:id="@+id/buttonFront"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:textSize="52sp"
                            android:textStyle="bold"
                            android:text="正面"
                            app:layout_constraintBottom_toBottomOf="parent"
                            app:layout_constraintEnd_toEndOf="parent"
                            app:layout_constraintStart_toStartOf="parent"
                            app:layout_constraintTop_toTopOf="parent" />

                    </androidx.constraintlayout.widget.ConstraintLayout>

                    <androidx.constraintlayout.widget.ConstraintLayout
                        android:id="@+id/viewBack"
                        android:layout_width="300dp"
                        android:layout_height="400dp"
                        android:background="#FFE16B8C"
                        android:visibility="gone"
                        app:layout_constraintDimensionRatio="1:1"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintTop_toTopOf="parent">

                        <Button
                            android:id="@+id/buttonBack"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:textSize="52sp"
                            android:textStyle="bold"
                            android:text="正面"
                            app:layout_constraintBottom_toBottomOf="parent"
                            app:layout_constraintEnd_toEndOf="parent"
                            app:layout_constraintStart_toStartOf="parent"
                            app:layout_constraintTop_toTopOf="parent" />

                    </androidx.constraintlayout.widget.ConstraintLayout>

                </androidx.constraintlayout.widget.ConstraintLayout>
            
        

第二步:建立動畫檔

# flip_out.xml:從 0 度旋轉到 90 度。

            
                <?xml version="1.0" encoding="utf-8"?>
                <set xmlns:android="http://schemas.android.com/apk/res/android">
                    <objectAnimator
                        android:duration="1000"
                        android:interpolator="@android:interpolator/accelerate_cubic"
                        android:propertyName="rotationY"
                        android:valueFrom="0"
                        android:valueTo="90" />
                </set>
            
        

# flip_in.xml:從 -90 度旋轉到 0 度。

            
                <?xml version="1.0" encoding="utf-8"?>
                <set xmlns:android="http://schemas.android.com/apk/res/android">
                    <objectAnimator
                        android:duration="1000"
                        android:interpolator="@android:interpolator/decelerate_cubic"
                        android:propertyName="rotationY"
                        android:valueFrom="-90"
                        android:valueTo="0" />
                </set>
            
        

第三步:撰寫動畫程式碼

在程式碼中使用動畫 XML 檔。
viewVisible:旋轉前在正面的 View。
viewInVisible:旋轉前在背面的 View。

            
                private void flipAnimation(Context context, View viewVisible, View viewInVisible) {
                    Animator animatorFlipOut = AnimatorInflater.loadAnimator(context, R.animator.flip_out);
                    animatorFlipOut.setTarget(viewVisible);

                    Animator animatorFlipIn = AnimatorInflater.loadAnimator(context, R.animator.flip_in);
                    animatorFlipIn.setTarget(viewInVisible);

                    animatorFlipOut.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animator) {
                            viewVisible.setVisibility(View.GONE);
                            viewInVisible.setVisibility(View.VISIBLE);
                            animatorFlipIn.start();
                        }
                    });

                    animatorFlipOut.start();
                }
            
        

如果不想使用動畫 XML 檔,也可以使用純程式碼達成,請參考以下範例。

            
                private void flipAnimation(View viewVisible, View viewInVisible) {
                    ObjectAnimator animatorFlipOut = ObjectAnimator.ofFloat(viewVisible, "rotationY", 0f, 90f);
                    animatorFlipOut.setInterpolator(new AccelerateInterpolator());
                    animatorFlipOut.setDuration(1000);

                    ObjectAnimator animatorFlipIn = ObjectAnimator.ofFloat(viewInVisible, "rotationY", -90f, 0f);
                    animatorFlipIn.setInterpolator(new DecelerateInterpolator());
                    animatorFlipIn.setDuration(1000);

                    animatorFlipOut.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animator) {
                            viewVisible.setVisibility(View.GONE);
                            viewInVisible.setVisibility(View.VISIBLE);
                            animatorFlipIn.start();
                        }
                    });

                    animatorFlipOut.start();
                }
            
        

第四步:調整鏡頭位置

基本上做到第三步驟就已經完成了卡片翻轉動畫,但如果還要吹毛求疵的話可以發現,旋轉的時候 View 都跑出整個螢幕畫面了。為了避免這種情況,我們可以為這兩個 View 加入一些相機距離。 根據 Google 文件的說明,如果要指定一個相機距離,並且能在各種密度下呈現相同視覺效果的話,必需使用以下公式:

            
                float scale = context.getResources().getDisplayMetrics().density;
                view.setCameraDistance(distance * scale);
            
        

稍微調整一下,完整程式碼範例如下:

            
                private void flipAnimation(Context context, View viewVisible, View viewInVisible) {
                    // 增加相機距離
                    float scale = context.getResources().getDisplayMetrics().density;
                    float cameraDist = 10000 * scale;
                    viewVisible.setCameraDistance(cameraDist);
                    viewInVisible.setCameraDistance(cameraDist);

                    // 翻轉動畫
                    Animator animatorFlipOut = AnimatorInflater.loadAnimator(context, R.animator.flip_out);
                    animatorFlipOut.setTarget(viewVisible);

                    Animator animatorFlipIn = AnimatorInflater.loadAnimator(context, R.animator.flip_in);
                    animatorFlipIn.setTarget(viewInVisible);

                    animatorFlipOut.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animator) {
                            viewVisible.setVisibility(View.GONE);
                            viewInVisible.setVisibility(View.VISIBLE);
                            animatorFlipIn.start();
                        }
                    });

                    animatorFlipOut.start();
                }
            
        

至此,就全部完成了,可以看出上方動畫在旋轉時,已經不會跑出螢幕了。在公式中的 distance 參數,我是使用 10000,但您可以自行調整這個參數,以達到您想要的效果。