- 
                Notifications
    You must be signed in to change notification settings 
- Fork 2
Animation
Explore some basic animations.
Let's explore some animations.
With a card flip animation, we can make it look like your reading list and edit book form are two sides of a card (like from a deck of cards).
For this, we will use the XML resources in the Android Developer site tutorial. Unfortunately, that tutorial is missing a few pieces that we will need to add.
Right-click on your res folder and choose New | Android resource directory.  On the Resource type field, choose animator then click OK.
You may have to scroll up to see
animator. They are alphabetical.
Right-click on the new animator directory and choose New | Animator resource file.
Give it a name of card_flip_left_in.xml and click OK.
Copy over this snippet from the Android tutorial:
<?xml version="1.0" encoding="utf-8"?>
<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" />
    <!-- Half-way through the rotation (see startOffset), set the alpha to 1. -->
    <objectAnimator
        android:valueFrom="0.0"
        android:valueTo="1.0"
        android:propertyName="alpha"
        android:startOffset="@integer/card_flip_time_half"
        android:duration="1" />
</set>There are three property changes that happen as part of this animation resource.
The first one changes the alpha (ie: transparency) from full (1.0) to none (0.0) with no delay (duration of 0).
The second one rotates around the Y axis from -180 degrees to 0 degrees. IE we are going to flip to the back of the card.
The third one sets the alpha back to full, but with a duration, which means we'll see it happen.
The IDE will give you an error because @integer/card_flip_time_full and @integer/card_flip_time_half are not defined.
With a little bit of sleuthing, we can determine what the intention was for these values. The AOSP shows that full should be 300 and half should be 150.
Let's create them.
Click the card_flip_time_full and use the IDE Quick Fix to Create integer value resource.
Remember you can access the IDE quick-fix by clicking on the light bulb or pressing Alt+Enter ( Option+Enter on Mac)
Set the value to 300.  Note that this screen doesn't tell you which value you are editing, so if you are unsure, cancel and redo the process.
Do the same process for the card_flip_time_half, setting it to 150.
To confirm what it created, Ctrl+Click on card_flip_time_half to open the new integers.xml file.
Create another animation resource, card_flip_left_out.xml.
<?xml version="1.0" encoding="utf-8"?>
<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" />
    <!-- Half-way through the rotation (see startOffset), set the alpha to 0. -->
    <objectAnimator
        android:valueFrom="1.0"
        android:valueTo="0.0"
        android:propertyName="alpha"
        android:startOffset="@integer/card_flip_time_half"
        android:duration="1" />
</set>This one rotates the Y axes from 0 back to 180, with a delay turning off the transparency.
Next we create card_flip_right_in.xml.
<?xml version="1.0" encoding="utf-8"?>
<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" />
    <!-- Half-way through the rotation (see startOffset), set the alpha to 1. -->
    <objectAnimator
        android:valueFrom="0.0"
        android:valueTo="1.0"
        android:propertyName="alpha"
        android:startOffset="@integer/card_flip_time_half"
        android:duration="1" />
</set>This one is similar to the first one but starts at 180 degrees instead of -180 degrees.
Lastly, create the card_flip_right_out.xml.
<?xml version="1.0" encoding="utf-8"?>
<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" />
    <!-- Half-way through the rotation (see startOffset), set the alpha to 0. -->
    <objectAnimator
        android:valueFrom="1.0"
        android:valueTo="0.0"
        android:propertyName="alpha"
        android:startOffset="@integer/card_flip_time_half"
        android:duration="1" />
</set>As you might expect, this one is similar to our second one, but uses -180 degrees instead of 180 degrees.
There are two places in BookListFragment where we call beginTransaction.  For each of them, we will want to specify our custom animation.
Open BookListFragment.
Update your viewAdapter
private val viewAdapter = ReadingListAdapter(
    bookClicked = { book ->
        app?.let { readingListApp ->
            fragManager?.let {
                it.beginTransaction()
                    .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(R.id.container, EditBookFragment.newInstance(
                    app = readingListApp,
                    source= book))
                .addToBackStack(null)
                .commit()
            }
        }
    }
)Update your fab in onCreateView
binding.fab.setOnClickListener {
    app?.let { readingListApp ->
        fragManager?.let {
            it.beginTransaction()
                .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(R.id.container, EditBookFragment.newInstance(app = readingListApp))
            .addToBackStack(null)
            .commit()
        }
    }
}Deploy your application and edit a book.
You'll notice that only the reading list portion of the page swaps. The title bar remains in place. That's because the title bar is defined at the activity-level and the reading list is at the fragment level.
What if you would like to cross fade between two different views?
For our application, let's look at the idea of providing some feedback while we are saving a book (after we edit it).
We'll start by editing fragment_edit_book.xml.  We want to wrap our existing TableLayout in a FrameLayout, like so...
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="book"
            type="com.aboutobjects.curriculum.readinglist.model.EditableBook" />
    </data>
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TableLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
...
        </TableLayout>
    </FrameLayout>
</layout>The easiest way to do that is to add the FrameLayout in (match_parent for height and width), then cut-n-paste the TableLayout into it. The IDE will auto-adjust the formatting.
While we are here, add an id to the TableLayout
android:id="@+id/details"Inside the FrameLayout, but after the TableLayout, let's add a wait message.
    </TableLayout>
    <LinearLayout
        android:id="@+id/please_wait"
        android:orientation="vertical"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            style="@style/MyTitle"
            android:text="Please wait..."
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <ProgressBar
            style="?android:progressBarStyleLarge"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />
    </LinearLayout>
</FrameLayout>We are just creating a standalone layout that shows a text message and a progress bar. The progress bar is getting its' style from the Android framework.
Use the IDE Quick Fix to Extract string resource and name it wait_title.
We've edited the XML, so redeploy to regenerate the binding classes.
Open your EditBookFragment.
We only want to show the new pleaseWait layout when we are saving.  In your onCreateView let's hide it temporarily
binding.pleaseWait.apply {
    visibility = View.GONE
    alpha = 0f
}Then, in our onOptionsItemSelected, we will show it (by changing the alpha) over the course of... how about 3 seconds?  Then, if there is an error, we will gradually hide it (again, change the alpha) a bit quicker... say, 1 second?
override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return when(item.itemId) {
        R.id.action_save -> {
            binding.book?.let { editableBook ->
                bookListService?.let { service ->
                    binding.pleaseWait.apply {
                        visibility = View.VISIBLE
                        animate()
                            .alpha(1f)
                            .setDuration(3000)
                            .setListener(null)
                    }
                    service.edit(
                        source = editableBook.source,
                        edited = editableBook.edited()
                    )
                        .subscribeOn(Schedulers.newThread())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribeBy(
                            onComplete = {
                                // It's updated, so we are done
                                activity?.supportFragmentManager?.popBackStack()
                            },
                            onError = { t ->
                                t.message?.let { msg ->
                                    binding.pleaseWait.animate()
                                        .alpha(0f)
                                        .setDuration(1000)
                                        .setListener(object: AnimatorListenerAdapter() {
                                            override fun onAnimationEnd(animation: Animator?) {
                                                binding.pleaseWait.visibility = View.GONE
                                                Snackbar.make(binding.root, msg, Snackbar.LENGTH_LONG)
                                                    .show()
                                            }
                                        })
                                }
                            }
                        )
                }
            }
            true
        }
        else -> super.onOptionsItemSelected(item)
    }
}If you deploy your application, you are unlikely to see the change.
You can temporarily test it by having BookListService.edit immediately return error(IllegalArgumentException("TESTING"))
Why don't we change the alpha back in the onComplete callback?
Since we are immediately popping the backstack, it seemed an unnecessary delay for the end user.  This entire Fragment is about to go away.  In the case of the onError, the Fragment stays on the screen with the error message in the Snackbar - so having the progress indicator go away gracefully is a nice touch.
For more information on Animation, please see the Android Developer site.


