Simple Property Animation

Now that you have the scene set up, it is time to make it do your bidding by moving parts of it around. You are going to animate the sun down below the horizon.

But before you start animating, you will want a few bits of information handy in your fragment. Inside of onCreateView(…), pull out a couple of views into fields on SunsetFragment.

Listing 32.6  Pulling out view references (SunsetFragment.java)

public class SunsetFragment extends Fragment {

    private View mSceneView;
    private View mSunView;
    private View mSkyView;

    public static SunsetFragment newInstance() {
        return new SunsetFragment();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                           Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_sunset, container, false);

        mSceneView = view;
        mSunView = view.findViewById(R.id.sun);
        mSkyView = view.findViewById(R.id.sky);

        return view;
    }
}

Now that you have those, you can write your code to animate the sun. Here is the plan: Smoothly move mSunView so that its top is right at the edge of the top of the sea. You will do this by translating the location of the top of mSunView to the bottom of its parent.

The first step is to find where the animation should start and end. Write this first step in a new method called startAnimation().

Listing 32.7  Getting tops of views (SunsetFragment.java)

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                           Bundle savedInstanceState) {
        ...
    }

    private void startAnimation() {
        float sunYStart = mSunView.getTop();
        float sunYEnd = mSkyView.getHeight();
    }

The getTop() method is one of four methods on View that return the local layout rect for that view: getTop(), getBottom(), getRight(), and getLeft(). A view’s local layout rect is the position and size of that view in relation to its parent, as determined when the view was laid out. It is possible to change the location of the view onscreen by modifying these values, but it is not recommended. They are reset every time a layout pass occurs, so they tend not to hold their value.

In any event, the animation will start with the top of the view at its current location. It needs to end with the top at the bottom of mSunView’s parent, mSkyView. To get it there, it should be as far down as mSkyView is tall, which you find by calling getHeight(). The getHeight() method returns the same thing as getBottom() - getTop().

Now that you know where the animation should start and end, create and run an ObjectAnimator to perform it.

Listing 32.8  Creating a sun animator (SunsetFragment.java)

private void startAnimation() {
    float sunYStart = mSunView.getTop();
    float sunYEnd = mSkyView.getHeight();

    ObjectAnimator heightAnimator = ObjectAnimator
            .ofFloat(mSunView, "y", sunYStart, sunYEnd)
            .setDuration(3000);

    heightAnimator.start();
}

Then hook up startAnimation() so that it is called every time the user presses anywhere in the scene.

Listing 32.9  Starting animation on press (SunsetFragment.java)

public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_sunset, container, false);

    mSceneView = view;
    mSunView = view.findViewById(R.id.sun);
    mSkyView = view.findViewById(R.id.sky);

    mSceneView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            startAnimation();
        }
    });

    return view;
}

Run Sunset and press anywhere on the scene to run the animation (Figure 32.2).

Figure 32.2  Setting sun

Screenshot shows Sunset screen in Android phone. The screen reads the title, Sunset on the top left. Half set sun is shown at the center.

You should see the sun move below the horizon.

Here is how it works: ObjectAnimator is a property animator. Instead of knowing specifically about how to move a view around the screen, a property animator repeatedly calls property setter methods with different values.

The following method call creates an ObjectAnimator:

    ObjectAnimator.ofFloat(mSunView, "y", 0, 1)

When that ObjectAnimator is started, it will then repeatedly call mSunView.setY(float) with values starting at 0 and moving up. Like this:

mSunView.setY(0);
mSunView.setY(0.02);
mSunView.setY(0.04);
mSunView.setY(0.06);
mSunView.setY(0.08);
...

… and so on, until it finally calls mSunView.setY(1). This process of finding values in between a starting and ending point is called interpolation. Between each interpolated value, a little time will pass, which makes it look like the view is moving.

View transformation properties

Property animators are great, but with them alone it would be impossible to animate a view as easily as you just did. Modern Android property animation works in concert with transformation properties.

Your view has a local layout rect, which is the position and size it is assigned from the layout process. You can move the view around after that by setting additional properties on the view, called transformation properties. You have three properties to rotate the view (rotation, pivotX, and pivotY, shown in Figure 32.3), two properties to scale the view vertically and horizontally (scaleX and scaleY, shown in Figure 32.4), and two properties to move the view around the screen (translationX and translationY, shown in Figure 32.5).

Figure 32.3  View rotation

Figure shows rotation.

Figure 32.4  View scaling

Figure shows scaling view.

Figure 32.5  View translation

Figure shows View Translation.

All of these properties have getters and setters. For example, if you wanted to know the current value of translationX, you would call getTranslationX(). If you wanted to set it, you would call setTranslationX(float).

So what does the y property do? The x and y properties are conveniences built on top of local layout coordinates and the transformation properties. They allow you to write code that simply says, Put this view at this X coordinate and this Y coordinate. Under the hood, these properties will modify translationX or translationY to put the view where you want it to be. That means that a call to mSunView.setY(50) really means this:

    mSunView.setTranslationY(50 - mSunView.getTop())

Using different interpolators

Your animation, while pretty, is abrupt. If the sun was really sitting there perfectly still in the sky, it would take a moment for it to accelerate into the animation you see. To add this sensation of acceleration, all you need to do is use a TimeInterpolator. TimeInterpolator has one role: to change the way your animation goes from point A to point B.

Add a line of code to startAnimation() to make your sun speed up a bit at the beginning using an AccelerateInterpolator.

Listing 32.10  Adding acceleration (SunsetFragment.java)

private void startAnimation() {
    float sunYStart = mSunView.getTop();
    float sunYEnd = mSkyView.getHeight();

    ObjectAnimator heightAnimator = ObjectAnimator
            .ofFloat(mSunView, "y", sunYStart, sunYEnd)
            .setDuration(3000);
    heightAnimator.setInterpolator(new AccelerateInterpolator());

    heightAnimator.start();
}

Run Sunset one more time and press to see your animation. Your sun should now start moving slowly and accelerate to a quicker pace as it moves toward the horizon.

There are a lot of styles of motion you might want to use in your app, so there are a lot of different TimeInterpolators. To see all the interpolators that ship with Android, look at the Known Indirect Subclasses section in the reference documentation for TimeInterpolator.

Color evaluation

Now that your sun is animating down, let’s animate the sky to a sunset-y color. Inside of onCreateView(…), pull all of the colors you defined in colors.xml into instance variables.

Listing 32.11  Pulling out sunset colors (SunsetFragment.java)

public class SunsetFragment extends Fragment {
    ...
    private View mSkyView;

    private int mBlueSkyColor;
    private int mSunsetSkyColor;
    private int mNightSkyColor;
    ...
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        ...
        mSkyView = view.findViewById(R.id.sky);

        Resources resources = getResources();
        mBlueSkyColor = resources.getColor(R.color.blue_sky);
        mSunsetSkyColor = resources.getColor(R.color.sunset_sky);
        mNightSkyColor = resources.getColor(R.color.night_sky);

        mSceneView.setOnClickListener(new View.OnClickListener() {
            ...
        });

        return view;
    }

Now add an additional animation to startAnimation() to animate the sky from mBlueSkyColor to mSunsetSkyColor.

Listing 32.12  Animating sky colors (SunsetFragment.java)

private void startAnimation() {
    float sunYStart = mSunView.getTop();
    float sunYEnd = mSkyView.getHeight();

    ObjectAnimator heightAnimator = ObjectAnimator
            .ofFloat(mSunView, "y", sunYStart, sunYEnd)
            .setDuration(3000);
    heightAnimator.setInterpolator(new AccelerateInterpolator());

    ObjectAnimator sunsetSkyAnimator = ObjectAnimator
            .ofInt(mSkyView, "backgroundColor", mBlueSkyColor, mSunsetSkyColor)
            .setDuration(3000);

    heightAnimator.start();
    sunsetSkyAnimator.start();
}

This seems like it is headed in the right direction, but if you run it you will see that something is amiss. Instead of moving smoothly from blue to orange, the colors will kaleidoscope wildly.

The reason this happens is that a color integer is not a simple number. It is four smaller numbers schlupped together into one int. So for ObjectAnimator to properly evaluate which color is halfway between blue and orange, it needs to know how that works.

When ObjectAnimator’s normal understanding of how to find values between the start and end is insufficient, you can provide a subclass of TypeEvaluator to fix things. A TypeEvaluator is an object that tells ObjectAnimator what value is, say, a quarter of the way between a start value and end value. Android provides a subclass of TypeEvaluator called ArgbEvaluator that will do the trick here.

Listing 32.13  Providing ArgbEvaluator (SunsetFragment.java)

private void startAnimation() {
    float sunYStart = mSunView.getTop();
    float sunYEnd = mSkyView.getHeight();

    ObjectAnimator heightAnimator = ObjectAnimator
            .ofFloat(mSunView, "y", sunYStart, sunYEnd)
            .setDuration(3000);
    heightAnimator.setInterpolator(new AccelerateInterpolator());

    ObjectAnimator sunsetSkyAnimator = ObjectAnimator
            .ofInt(mSkyView, "backgroundColor", mBlueSkyColor, mSunsetSkyColor)
            .setDuration(3000);
    sunsetSkyAnimator.setEvaluator(new ArgbEvaluator());

    heightAnimator.start();
    sunsetSkyAnimator.start();
}

Run your animation one more time, and you should see the sky fade to a beautiful orange color (Figure 32.6).

Figure 32.6  Changing sunset color

Screenshot shows Sunset screen in Android phone. The screen reads the title, Sunset on the top left. Half set sun is shown at the center.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.142.248