Tag: android

Android: Using Physics-based Animations in Custom Views (SpringAnimation)

Android: Using Physics-based Animations in Custom Views (SpringAnimation)

Learn how to use physics-based animations in a Custom View implementation for natural looking animations in your app.

You’ve used all the standard Android animation techniques, but you find that they sometimes just don’t give you that extra sparkle you are looking for. You’ve wondered how to get more natural looking animations and had no luck thinking about how to do it yourself. So here you are, reading this article in the hope that you will learn how to create beautiful, natural, physics-based animations in your app. 🌈

The Problem 🕵🏽‍♀️

The physics-based animation library is not new, but it was largely unexplored territory for me. Having always used the “standard” animation options (i.e. view.animate()), I had never found a need to use the physics-based animations, until I started with this particular custom view animation. This animation required that we animate a view between two points, decided by the user. Using the standard ValueAnimator, the result was not good enough for the polish that our app requires.

Here is how I previously animated the custom ColourDropperView using the ValueAnimator and PropertyValuesHolder class:

private fun animateToPoint(point: Point) {
    val propertyX = PropertyValuesHolder.ofFloat(ColorDropperView.PROPERTY_X, dropperPoint.x, point.x)
    val propertyY = PropertyValuesHolder.ofFloat(ColorDropperView.PROPERTY_Y, dropperPoint.y, point.y)

    val animator = ValueAnimator()
    animator.setValues(propertyX, propertyY)
    animator.interpolator = OvershootInterpolator()
    animator.duration = 100
    animator.addUpdateListener { animation ->
        val animatedX = animation.getAnimatedValue(ColorDropperView.PROPERTY_X) as Float
        val animatedY = animation.getAnimatedValue(ColorDropperView.PROPERTY_Y) as Float
        setPoint(Point(animatedX, animatedY))
    }
    animator.start()
}

The PropertyValuesHolder is useful when creating custom animations on our own properties. When using it, the animated property values can be fetched from the animation in the AnimationUpdateListener callback. At this point, the values are interpolated between the start and end values we initially provide. We can then go ahead and perform the draw operation (by calling invalidate() on our custom view) using these new animated values and the view will animate 🤩. In our case, the setPoint() method calls invalidate()and the draw() function uses the new point values to draw itself.

ValueAnimator Example

Don’t get me wrong — the above animation is okay in most contexts but we would like it to look more fluid. We need to animate it elegantly between these two positions.

One of the problems with the above animation is that we needed to specify a duration that the animation should take. We specified 100ms which moved the view at a high speed. But you may also notice that the ColorDropperViewmoves a lot faster when the distance between the start and end point is larger. We could play around with the duration property until it looked more acceptable. Ideally, we want the velocity to remain the same and the animation to look consistent, no matter the distance between the two points.

The Solution: SpringAnimation ✨

In order to make the animation more fluid, we need to switch to using the SpringAnimation class (documentation can be found here). The SpringAnimation class allows us to set the property which we will be animating, the velocity and the end value that the property should use.

To use the SpringAnimation class, we need to include the dependency in our build.gradle file:

implementation "androidx.dynamicanimation:dynamicanimation:1.0.0"

There are a bunch of built-in properties that we can use to achieve some standard effects, such as SCALE_XROTATION and ALPHA properties (check the documentation for the full list here). In our case, we needed to animate a custom property — the colour dropper’s X and Y point (the underlying data structure that the view depends on for drawing). So we need to do things a bit differently.

We need to take a look at the SpringAnimation constructor that takes in the FloatPropertyCompat object as an argument. This property will link up our custom view implementation to the animation. The SpringAnimation class uses this object to call into our custom view class on every change of the float value. Here is the implementation of the two custom FloatPropertyCompatobjects for the X position and the Y position on screen:

private val floatPropertyAnimX = object : FloatPropertyCompat<ColorDropperView>(PROPERTY_X) {
    override fun setValue(dropper: ColorDropperView?, value: Float) {
        dropper?.setDropperX(value)
    }

    override fun getValue(dropper: ColorDropperView?): Float {
        return dropper?.getDropperX() ?: 0f
    }
}

private val floatPropertyAnimY = object : FloatPropertyCompat<ColorDropperView>(PROPERTY_Y) {
    override fun setValue(dropper: ColorDropperView?, value: Float) {
        dropper?.setDropperY(value)
    }

    override fun getValue(dropper: ColorDropperView?): Float {
        return dropper?.getDropperY() ?: 0f
    }
}

These two objects access two custom methods on the ColorDropperView class and the setValue methods will be called whilst the animation is running, which will set the new interpolated values. In this case, setDropperX() and setDropperY() are custom methods in our ColorDropperView class. When these methods are invoked, they change the underlying value and call invalidate() which will trigger another redraw of the view.

Once we have our properties defined, we can then go on to implement the SpringAnimation effect with these properties.

Now we can see, our animateToPoint() function uses the SpringAnimationclass, passes in the reference to the ColorDropperView (this) and we can set a few properties on the animation (such asstiffness and dampingRatio). We then call start() and the animation will run.

private fun animateToPoint(point: Point) {

    SpringAnimation(this, floatPropertyAnimX, point.x).apply {
        spring.stiffness = SpringForce.STIFFNESS_MEDIUM       
        spring.dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
        start()
    }

    SpringAnimation(this, floatPropertyAnimY, point.y).apply {
        spring.stiffness = SpringForce.STIFFNESS_MEDIUM
        spring.dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
        start()
    }
}

It is worth noting, that we don’t need to (and we can’t) specify the duration of this animation, which makes total sense! In the real world, when something is falling or moving, we can only calculate how long it’ll take based on its mass, stiffness, velocity and other factors. We cannot tell the object how long it should take.

Here is a recording of how the animation works now using the SpringAnimation class. Much smoother and more natural looking, don’t you think?

SpringAnimation in action

Damping Ratio (bouncy-ness) 🎾

The dampingRatio that we can set on a SpringAnimation determines how much bounce the animation will have. There are some built-in options for the dampingRatio that’ll produce different results:

  • DAMPING_RATIO_NO_BOUNCY
  • DAMPING_RATIO_LOW_BOUNCY
  • DAMPING_RATIO_MEDIUM_BOUNCY (default)
  • DAMPING_RATIO_HIGH_BOUNCY

We are also able to set a value between 0 and 1 for this ratio if preferred. Here are three examples of the effect the dampingRatio has on our custom view:

dampingRatio example

Stiffness

Another property that we can set on a SpringAnimation is the stiffnessof the spring force. Like the dampingRatio, we can choose from a few predefined options:

  • STIFFNESS_LOW
  • STIFFNESS_VERY_LOW
  • STIFFNESS_MEDIUM (default)
  • STIFFNESS_HIGH

The stiffness affects how long the animation will take: if the spring is very stiff (STIFFNESS_HIGH) the animation will perform quicker than if the stiffness is low.

Below is an example of some of the different stiffness values in action:

Stiffness examples

Velocity 🚗

SpringAnimations can also have their startVelocity set using setStartVelocity(). This value is specified in pixels per second. If you would like to specify it, you should convert from a dp value into pixels to ensure the animation looks consistent across different devices. The default startVelocity is 0.

Here is an example of how to set the startVelocity to 5000dp per second:

SpringAnimation(this, floatPropertyAnimY, point.y).apply {
    setStartVelocity(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5000f, resources.displayMetrics))
    start()
}

This is what different start velocities look like on the custom view:

startVelocity options

Cancel SpringAnimation ✋🏾

Another great part about physics-based animations is that they can be cancelled midway through the animation if required. Calling SpringAnimation#cancel() will terminate the animation. There is also the option toSpringAnimation#skipToEnd() which will immediately show the end state of the animation (this can cause a visual jump — as if the animation wasn’t implied).

Dynamic Animation — KTX Functions

There are currently some extension functions provided by the following KTX dependency (check for the latest version here):

implementation "androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha01"

The alpha version was released on the 9th February 2019, but there are some new changes that haven’t been released yet, which will clean up this code quite a bit. Take a look here at the new extension functions that will be provided soon. The clean up of creating the FloatPropertyCompat objects is particularly interesting and will help clean up this code in the future.

Finally 🧚🏼‍♀️

The physics-based animations in Android are great alternatives when you have animations that need to look more natural. They are also great for when you are moving things around on the screen and you aren’t sure how long the animation should take. Rather don’t try to guess those duration values, use physics animations instead. 👩🏻‍🔬

For our animation, we ended up going with the DAMPING_RATIO_MEDIUM_BOUNCY ,STIFFNESS_MEDIUM and the startVelocity set at 0. This was the final animation that we stuck with:

Final animation

Where else have you found physics-based animations to be useful? Let me know your thoughts on Twitter! 🌈

Thanks to Josh LeibsteinGarima JainNick Rout and Dan Galasko for reviewing this post. 💚

Android MotionLayout Tutorial – Collapsing View

Android MotionLayout Tutorial – Collapsing View

MotionLayout is a layout class that extends from ConstraintLayout. MotionLayout has all the features of ConstraintLayout. On top of that, it also provides the ability to easily animate changes to your UI, without needing to know much about UI interactions and the Android Animation Frameworks.

Take for example this subtle animation of a view being scrolled and the profile picture shrinking. Before MotionLayout, this would be a tedious task to complete. We may have needed a CollapsingToolbar and some other custom animation code to ensure the profile picture scales correctly. Now with MotionLayout, this is really easy to achieve with one extra XML file.

In this article, we will be looking at implementing a simple swipe action on a RecyclerView and how we can achieve the scaling animation with MotionLayout.

Add MotionLayout as a Gradle Dependency

To get started with MotionLayout, you need to make sure we have the latest version in your build.gradlefile. (Note: MotionLayout is still in alpha at the time of writing)

 implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3'

Create your layout XML as per usual

The great part about MotionLayout is that it uses the same constructs as ConstraintLayout. Everything you’ve previously learnt about ConstraintLayout (ie barriers, chains etc) is applicable to layouts that we build with MotionLayout.

To get started, open up your editor and change the root element of your layout to use MotionLayout. Add a RecyclerView and an ImageView to your layout. Make sure the RecyclerView is constrained to the bottom of the ImageView.

RecyclerView with ImageView above

The XML behind the following layout should look similar to this:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout 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:orientation="vertical"                                android:layout_width="match_parent"                                     app:showPaths="false"                          app:layoutDescription="@xml/motion_layout_example"                                android:layout_height="match_parent">
    <View android:layout_width="0dp" android:layout_height="0dp"
          app:layout_constraintStart_toStartOf="parent"
          android:background="@color/colorPrimary"
          android:id="@+id/background"
          app:layout_constraintBottom_toBottomOf="@id/space"
          app:layout_constraintTop_toTopOf="parent"
          app:layout_constraintEnd_toEndOf="parent" android:alpha="0"
    />

    <ImageView
            android:layout_width="140dp"
            android:layout_height="0dp"
            android:scaleType="centerCrop"
            android:id="@+id/imageViewAvatar"
            android:layout_marginTop="16dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="16dp"
            app:layout_constraintDimensionRatio="h,1:1"
            app:srcCompat="@drawable/veruca"/>


    <TextView
            android:text="@string/veruca_salt_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:id="@+id/textViewName"
            android:fontFamily="@font/willywonka"
            app:layout_constraintStart_toEndOf="@+id/imageViewAvatar"
            android:layout_marginStart="16dp"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="16dp"
            android:textAppearance="@style/TextAppearance.AppCompat.Display1"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toTopOf="@+id/imageViewAvatar" 
            android:layout_marginBottom="8dp"
            app:layout_constraintBottom_toTopOf="@+id/space"/>

    <androidx.recyclerview.widget.RecyclerView
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintBottom_toBottomOf="parent"
            tools:listitem="@layout/list_item_status"
            android:id="@+id/recyclerViewStatus" android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/imageViewAvatar">

    </androidx.recyclerview.widget.RecyclerView>
    <Space
            android:layout_width="0dp"
            android:layout_height="8dp"
            android:id="@+id/space"
            app:layout_constraintStart_toEndOf="@+id/imageViewAvatar"
            app:layout_constraintTop_toBottomOf="@id/imageViewAvatar"
            app:layout_constraintEnd_toEndOf="@+id/imageViewAvatar"
            app:layout_constraintStart_toStartOf="@+id/imageViewAvatar"/>
</androidx.constraintlayout.motion.widget.MotionLayout>

Create the MotionScene XML

In order to animate this layout we need to describe how views should animate in the layout. To do this, create an XML file in the xml folder of your application. We will call it motion_scene.xml.

The first thing that we will do, is define the Transition for this MotionScene. We set the reference to the start and end ConstraintSets on the Transition object. We can also set the duration of the transition.

<?xml version="1.0" encoding="utf-8"?>
<MotionScene
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android">

    <Transition
            app:constraintSetStart="@id/start"
            app:constraintSetEnd="@id/end"
            app:duration="1000">
        <OnSwipe
                app:touchAnchorId="@+id/recyclerViewStatus"
                app:touchAnchorSide="top"
                app:dragDirection="dragUp" />
    </Transition>
    <ConstraintSet android:id="@+id/start">
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
    </ConstraintSet>
</MotionScene>

The next step, that can be seen in the code snippet above, is to create the OnSwipe declaration. This indicates to the MotionLayout that it should monitor the layout for a swipe movement. When the user performs a dragUp gesture on the specified touchAnchorId, the MotionScene will start interpolating between the two states defined (start and end). In this case, it is the recyclerViewStatus view that it will be monitoring for the dragUp gesture.

In the code snippet above, you may notice that there are two ConstraintSet tags defined in the MotionScene. These tags are for the start and end constraints of the view. The beautiful part about MotionLayout is that it will automatically interpolate between these two states and produce some pretty magical animations. At the moment, we don’t have any constraint changes applied, so let’s change that.

The first view we want to animate is the ImageView. We want the size of the view to decrease as a user scrolls up on the RecyclerView. To do that, we will add an end Constraint to the ImageView to adjust the width and height to 40dp .

<?xml version="1.0" encoding="utf-8"?>
<MotionScene
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android">

    <Transition
            app:constraintSetStart="@id/start"
            app:constraintSetEnd="@id/end"
            app:duration="1000">
        <OnSwipe
                app:touchAnchorId="@+id/recyclerViewStatus"
                app:touchAnchorSide="top"
                app:dragDirection="dragUp" />
    </Transition>

    <ConstraintSet android:id="@+id/start">

    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        
        <Constraint android:id="@id/imageViewAvatar"
                    android:layout_width="40dp"
                    android:layout_height="40dp" android:layout_marginTop="16dp"
                    app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
                    android:layout_marginStart="16dp">
        </Constraint>

    </ConstraintSet>
</MotionScene>

Linking the MotionScene to the Layout

In order to link the MotionScene to the Layout, you will need to set the property app:layoutDescription="@xml/motion_layout_example" to point to your newly created XML file on the root MotionLayout element.

Running this on device, you will observe that as you scroll up on the RecyclerView , the ImageView decreases in size.

Animating Visibility of a View

Now if you wanted to animate the background to go from invisible to visible, we can just add a Property animation to the start and end constraints. The final XML will look as follows:

<?xml version="1.0" encoding="utf-8"?>
<MotionScene
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android">

    <Transition
            app:constraintSetStart="@id/start"
            app:constraintSetEnd="@id/end"
            app:duration="1000">
        <OnSwipe
                app:touchAnchorId="@+id/recyclerViewStatus"
                app:touchAnchorSide="top"
                app:dragDirection="dragUp" />
    </Transition>

    <ConstraintSet android:id="@+id/start">

        <Constraint android:id="@id/background">
            <PropertySet app:alpha="0"/>
        </Constraint>
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">

        <Constraint android:id="@id/imageViewAvatar"
                    android:layout_width="40dp"
                    android:layout_height="40dp"
                    android:layout_marginTop="16dp"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    android:layout_marginStart="16dp">
        </Constraint>

        <Constraint android:id="@id/background">
            <PropertySet app:alpha="1"/>
        </Constraint>
    </ConstraintSet>
</MotionScene>

It’s a wrap!

As you can see from the short example above, using MotionLayout can decrease the amount of code you need to write in order to achieve delightful animations. In the next few posts, we will be covering more of the features of MotionLayout, as this article has just touched the surface. Till next time!

Follow me on Twitter – @riggaroo.

Android ConstraintLayout 2.0: ConstraintLayoutStates

Android ConstraintLayout 2.0: ConstraintLayoutStates

With the introduction of ConstraintLayout 2.0, there is an interesting new feature called ConstraintLayoutStates. ConstraintLayoutStates allow you to create a layout with different states and switch between them easily. Typically, most layouts contain a loading state, initial state, end state and error state. Using ConstraintLayoutStates, there is a clean way to switch between these different states.

How do you use this new feature? Glad you asked!

Step 1: Create your different states in separate XML files

Create the different state layout files that your layout needs. Each layout must include the same views, they can just have different properties such as their visibility, or their constraints.

ConstraintLayoutStates example layouts
3 layouts for the different layout states.

Step 2: Create a ConstraintLayoutStates XML file

In the xml resource folder, create an xml file containing your states. In this example, we will call it constraint_layout_states_example.xml. Inside this file place all the different representations of your layout. Give them meaningful ids such as startloading etc. and then link them to the relevant constraint files.

<?xml version="1.0" encoding="utf-8"?>
<ConstraintLayoutStates xmlns:android="http://schemas.android.com/apk/res/android"
                        xmlns:app="http://schemas.android.com/apk/res-auto">
    <State
            android:id="@+id/start"
            app:constraints="@layout/activity_cl_states_start"/>
    <State
            android:id="@+id/loading"
            app:constraints="@layout/activity_cl_states_loading"/>
    <State
            android:id="@+id/end"
            app:constraints="@layout/activity_cl_states_end"/>
</ConstraintLayoutStates>

 

Step 3: Switch between the states

In your activity/fragment, you can now easily switch between these states based on different conditions. You will first need to load the state description using loadLayoutDescription() on your ConstraintLayout object. Once you have done that, you can call constraintLayout.setState() with any of the states that you have defined in the previous states file.

In the example below, we are setting the state to the loading state. Then after some time (in this example I am just posting a delayed runnable, but this could easily be a network call that returns at a later stage), we are setting it to the end state.

class ConstraintLayoutStatesExampleActivity : AppCompatActivity() {

    val handler = Handler()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_cl_states_start)
        stateConstraintLayout.loadLayoutDescription(R.xml.constraint_layout_states_example)
        var changed = false
        buttonBakeCake.setOnClickListener {
            stateConstraintLayout.setState(R.id.loading, 0, 0)
            postDelayed(handler,  {
                stateConstraintLayout.setState(if (changed) R.id.start else R.id.end,0, 0)
                changed = !changed
            }, null, 3000L)
        }
    }
}

The above example running on a device:ConstraintLayoutStates example

That’s it! You should now be able to use ConstraintLayoutStates in your own apps. This example can be found on Github here.

Enjoyed this post? Let me know your thoughts on Twitter!

ConstraintLayout 2.0: ImageFilterView

ConstraintLayout 2.0: ImageFilterView

Whilst browsing through the various examples online with the new ConstraintLayout 2.0, I stumbled upon ImageFilterView. This got my attention immediately and I decided to investigate further.

An ImageFilterView allows you to perform some common image filtering techniques on an ImageView, including saturation, contrast, warmth and crossfade. 

If you have tried to implement these image filters before, you may have run into ColorMatrix. If you look at the source of ImageFilterView you will see that these methods have just been nicely wrapped up with simpler API usage.

For example, if you would like to adjust the warmth, contrast or saturation, all you need to do is set a property on the ImageFilterView:

<androidx.constraintlayout.utils.widget.ImageFilterView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:id="@+id/imageView"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        app:warmth="1.2"
        app:contrast="1.0"
        app:saturation="2.0"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintDimensionRatio="16:9"
        tools:srcCompat="@tools:sample/avatars"
        />

You can also access this programmatically, so you could add a SeekBar to control these values.

seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
    override fun onProgressChanged(seekbar: SeekBar?, progress: Int, p2: Boolean) {
        val percentage = (progress / 100.0f)
        imageView.saturation = (percentage) + 1
    }

    override fun onStartTrackingTouch(seekbar: SeekBar?) {

    }

    override fun onStopTrackingTouch(seekbar: SeekBar?) {

    }
})

There is also the ability to crossfade between two different images using the crossfade method defined on ImageFilterView. This allows you to merge two images together.

If you are looking for a quick way to add some basic image effects, ImageFilterView is definitely something to consider. It is fast to use and execute since it is backed by ColorMatrix which uses the GPU (and not the CPU) to process the resultant image.

Here is an example of ImageFilterView in action:

Realtime Image Processing with ImageFilterView
Realtime Image Processing with ImageFilterView

 

The downside to using this approach is that you are not in full control of the exact pixel values that are going to be used, which could be problematic if you are developing an image editing application.

Overall, I’m really excited about the ImageFilterView class! I hope it is the start of some awesome Image effects offered by the Android Team.

Check out the ConstraintLayout demo repository for the code used in the above example.

Follow me on Twitter for more.

 

Android ConstraintLayout — Build a layout from Scratch + Q&A — Sunday (26 Nov) 5PM GMT+2 [Live Stream]

Android ConstraintLayout — Build a layout from Scratch + Q&A — Sunday (26 Nov) 5PM GMT+2 [Live Stream]

I’ve seen quite a lot of misuse/misunderstanding of how to use ConstraintLayout practically. I was contemplating the best way to talk about these common “Don’ts” that I see people do.

I’ve decided to try do something a bit different than a blog post. I’ll be live streaming on Sunday, 26 November at 5PM GMT+2 (that is today 😄 ) on YouTube and Twitch.

Join me and ask questions whilst I code this layout below. I’ll also cover some common mistakes that I’ve seen people make when using ConstraintLayout.

Follow me on YouTubeTwitch and Twitter to get more updates!

Hope to see you there

ConstraintLayout Android Example
ConstraintLayout Android Example