Tampilkan atau sembunyikan tampilan menggunakan animasi

Mencoba cara Compose
Jetpack Compose adalah toolkit UI yang direkomendasikan untuk Android. Pelajari cara menggunakan Animasi di Compose.

Saat aplikasi Anda digunakan, informasi baru akan muncul di layar dan informasi lama informasi akan dihapus. Mengubah apa yang segera ditampilkan di layar bisa menjadi memuaskan, dan pengguna dapat melewatkan konten baru yang muncul tiba-tiba. Animasi lambat menurunkan perubahan dan menarik perhatian pengguna dengan gerakan sehingga pembaruan sudah jelas.

Ada tiga animasi umum yang dapat Anda gunakan untuk menampilkan atau menyembunyikan tampilan: mengungkap animasi, animasi {i>crossfade<i}, dan animasi {i>cardflip<i}.

Membuat animasi crossfade

Animasi crossfade—juga dikenal sebagai disresolve—secara bertahap memudar satu View atau ViewGroup sekaligus yang memudar menjadi satu lagi. Animasi ini berguna untuk situasi saat Anda ingin mengubah konten atau tampilan di aplikasi Anda. Animasi {i>crossfade<i} yang ditampilkan di sini menggunakan ViewPropertyAnimator, yang tersedia untuk Android 3.1 (API level 12) dan yang lebih tinggi.

Berikut adalah contoh crossfade dari indikator progres ke konten teks:

Gambar 1. Animasi crossfade.

Membuat tampilan

Buat dua tampilan yang ingin Anda crossfade. Contoh berikut membuat indikator progres dan tampilan teks yang dapat di-scroll:

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

Menyiapkan animasi crossfade

Untuk menyiapkan animasi crossfade, lakukan langkah berikut:

  1. Buat variabel anggota untuk tampilan yang ingin di-crossfade. Anda perlu referensi ini nanti saat memodifikasi tampilan selama animasi.
  2. Atur visibilitas tampilan yang akan diperjelas ke GONE Hal ini mencegah tampilan dari penggunaan ruang tata letak dan menghilangkannya dari penghitungan tata letak, yang mempercepat proses naik
  3. Menyimpan config_shortAnimTime ke dalam cache properti sistem dalam variabel anggota. Properti ini menentukan "short" standar dari durasi animasi tersebut. Durasi ini ideal untuk animasi yang halus atau animasi yang sering muncul. config_longAnimTime dan config_mediumAnimTime juga tersedia.

Berikut ini contoh penggunaan tata letak dari cuplikan kode sebelumnya sebagai tampilan konten aktivitas:

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

Meng-crossfade tampilan

Jika tampilan sudah disiapkan dengan benar, lakukan crossfade dengan melakukan langkah berikut:

  1. Untuk tampilan yang diperjelas, setel nilai alfa ke 0 dan visibilitas ke VISIBLE dari setelan GONE. Hal ini membuat tampilan terlihat tetapi transparan.
  2. Untuk tampilan yang diperjelas, animasikan nilai alfa dari 0 hingga 1. Untuk yang memudar, animasikan nilai alfa dari 1 sampai 0.
  3. Menggunakan onAnimationEnd() di Animator.AnimatorListener, setel visibilitas tampilan yang memudar menjadi GONE. Meskipun nilai alfa adalah 0, menyetel visibilitas tampilan ke GONE akan mencegah tampilan dari penggunaan ruang tata letak dan menghilangkannya dari penghitungan tata letak, yang mempercepat ke atas.

Metode berikut menunjukkan contoh cara melakukannya:

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

Membuat animasi card flip

Kartu membalik mengalihkan tampilan konten dengan menampilkan animasi yang mengemulasi kartu terbalik. Animasi {i>card flip<i} yang ditampilkan di sini menggunakan FragmentTransaction

Seperti inilah tampilan animasi card flip:

Gambar 2. Animasi card flip.

Membuat objek animator

Untuk membuat animasi card flip, Anda memerlukan empat animator. Dua animator ketika bagian depan kartu bergerak keluar dan ke kiri serta saat bergerak dari dalam dan dari kiri. Dua animator lainnya digunakan ketika bagian belakang kartu bergerak ke dalam dan dari kanan dan saat bergerak ke luar dan ke kanan.

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>

Membuat tampilan

Setiap sisi kartu adalah tata letak terpisah yang dapat berisi konten apa pun yang diinginkan, seperti dua tampilan teks, dua gambar, atau kombinasi tampilan apa pun untuk dibalik di antara keduanya. Gunakan dua tata letak dalam fragmen yang nanti Anda animasikan. Tujuan tata letak berikut membuat satu sisi kartu, yang menampilkan teks:

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

Dan tata letak berikutnya menciptakan sisi lain kartu, yang menampilkan 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" />

Membuat fragmen

Buat class fragmen untuk bagian depan dan belakang kartu. Dalam fragmen Anda menampilkan tata letak yang Anda buat dari onCreateView() . Anda kemudian dapat membuat instance fragmen ini di aktivitas induk tempat Anda ingin menampilkan kartu.

Contoh berikut menunjukkan class fragmen bertingkat di dalam aktivitas induk yang menggunakannya:

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

Menganimasikan card flip

Menampilkan fragmen di dalam aktivitas induk. Untuk melakukannya, buat tata letak untuk aktivitas Anda. Contoh berikut membuat FrameLayout yang dapat Anda tambahkan fragmen ke saat runtime:

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

Dalam kode aktivitas, tetapkan tampilan konten menjadi tata letak yang Anda buat. Sebaiknya tampilkan fragmen default saat aktivitas dibuat. Tujuan contoh aktivitas berikut menunjukkan cara menampilkan bagian depan kartu dengan default:

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

Dengan bagian depan kartu ditampilkan, Anda dapat menunjukkan bagian belakang kartu dengan animasi membalik pada waktu yang tepat. Buat metode untuk menunjukkan sisi lain dari kartu yang melakukan hal-hal berikut:

  • Menetapkan animasi kustom yang Anda buat untuk transisi fragmen.
  • Mengganti fragmen yang ditampilkan dengan fragmen baru dan menganimasikan peristiwa ini dengan animasi kustom yang Anda buat.
  • Menambahkan fragmen yang sebelumnya ditampilkan ke data sebelumnya pada fragmen, sehingga saat pengguna mengetuk tombol Kembali, kartu akan terbalik.

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

Membuat animasi reveal melingkar

Animasi pengungkapan memberi pengguna kontinuitas visual saat menampilkan atau menyembunyikan grup elemen UI. Tujuan ViewAnimationUtils.createCircularReveal() memungkinkan Anda menganimasikan lingkaran pemotongan untuk menampilkan atau menyembunyikan tampilan. Ini animasi ini tersedia di Class ViewAnimationUtils, yang tersedia untuk Android 5.0 (API level 21) dan yang lebih tinggi.

Berikut adalah contoh yang menunjukkan cara menampilkan tampilan yang sebelumnya tidak terlihat:

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

Animasi ViewAnimationUtils.createCircularReveal() membutuhkan lima parameter. Parameter pertama adalah tampilan yang ingin Anda sembunyikan atau tampilkan di layar. Tujuan dua parameter berikutnya adalah koordinat X dan Y untuk pusat kliping lingkaran. Biasanya, ini adalah bagian tengah tampilan, tetapi Anda juga dapat menggunakan ketika pengguna mengetuk sehingga animasinya dimulai dari tempat yang mereka pilih. Tujuan parameter keempat adalah radius awal dari lingkaran pemotongan.

Pada contoh sebelumnya, radius awal diatur ke nol sehingga tampilan yang ditampilkan disembunyikan oleh lingkaran. Parameter terakhir adalah radius akhir dari lingkaran itu. Saat menampilkan tampilan, buat radius akhir lebih besar dari sehingga tampilan dapat diperlihatkan sepenuhnya sebelum animasi selesai.

Untuk menyembunyikan tampilan yang sebelumnya terlihat, lakukan hal berikut:

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

Dalam hal ini, radius awal dari lingkaran pemotongan diatur sebesar tampilan sehingga tampilan akan terlihat sebelum animasi dimulai. Final radius disetel ke nol sehingga tampilan disembunyikan saat animasi selesai. Tambahkan pemroses ke animasi sehingga visibilitas tampilan dapat disetel ke INVISIBLE saat animasi selesai.

Referensi lainnya