Android Native - How To Animate View LayoutParams

dimitrilc 2 Tallied Votes 171 Views Share

Introduction

Every Android View has a layoutParams property. This property tells the parent ViewGroup how a View wants to be laid out; it is also often used to change the size of a View.

In this tutorial, we will learn how to animate Views while modifying their layoutParams.

Goals

At the end of the tutorial, you would have learned:

  1. How to animate Views when modifying layoutParams.

Tools Required

  1. Android Studio. The version used in this tutorial is Android Studio Dolphin | 2021.3.1.

Prerequisite Knowledge

  1. Intermediate Android.

Project Setup

To follow along with the tutorial, perform the steps below:

  1. Create a new Android project with the default Empty Activity.

  2. Add the ic_baseline_sports_basketball_24 vector asset to your project.

  3. Replace the content of activity_main.xml with the code below.

     <?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">
    
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_baseline_sports_basketball_24"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
     </androidx.constraintlayout.widget.ConstraintLayout>

Animate Using ValueAnimator

The first method that we are going to learn in this tutorial would be to use the class ValueAnimator. ValueAnimator has many different static factory methods, but we will focus on the ofInt() method here.

ValueAnimator’s ofInt() or ofFloat() methods can be used to animate/iterate from a starting number to an ending number. When ValueAnimator is iterating over the numbers, we can set a listener when the number updates. You can access the current value using the property animatedValue.

To animate the basketball doubling in size after it is clicked, use the code below.

   findViewById<ImageView>(R.id.imageView).setOnClickListener { image ->
       Log.d(TAG, "Starting Animation, Width: ${image.width}, Height: ${image.height}")

       ValueAnimator.ofInt(image.height, image.height * 2).apply {
           // Adds listener when value updates
           addUpdateListener { animation ->
               Log.d(TAG, "Animated Value: ${animation.animatedValue as Int}")

               // Updates LayoutParams here
               image.updateLayoutParams<ViewGroup.LayoutParams> {
                   height = animation.animatedValue as Int
                   width = animation.animatedValue as Int
               }
           }

           duration = 2000

           //Must call start to start the animation
           start()
       }
   }

The following animation illustrates how the app behaves after clicks.

daniweb_animated_basketball_1.gif

I have also added the logs, so we can see what happens at each iteration.

---------------------------- PROCESS STARTED (12083) for package com.hoang.daniwebanimateviewlayoutparams ----------------------------
2022-09-29 15:52:18.303 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Starting Animation, Width: 66, Height: 66
2022-09-29 15:52:18.304 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 66
2022-09-29 15:52:18.306 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 66
2022-09-29 15:52:18.325 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 66
2022-09-29 15:52:18.341 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 67
2022-09-29 15:52:18.356 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 70
2022-09-29 15:52:18.375 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 73
2022-09-29 15:52:18.390 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 77
2022-09-29 15:52:18.409 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 82
2022-09-29 15:52:18.424 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 87
2022-09-29 15:52:18.440 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 93
2022-09-29 15:52:18.458 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 99
2022-09-29 15:52:18.474 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 104
2022-09-29 15:52:18.490 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 110
2022-09-29 15:52:18.507 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 115
2022-09-29 15:52:18.523 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 120
2022-09-29 15:52:18.542 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 124
2022-09-29 15:52:18.560 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 127
2022-09-29 15:52:18.573 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 129
2022-09-29 15:52:18.592 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 131
2022-09-29 15:52:18.609 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 132
2022-09-29 16:43:42.966 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Starting Animation, Width: 132, Height: 132
2022-09-29 16:43:42.966 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 132
2022-09-29 16:43:42.975 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 132
2022-09-29 16:43:42.990 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 133
2022-09-29 16:43:43.007 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 136
2022-09-29 16:43:43.026 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 140
2022-09-29 16:43:43.041 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 147
2022-09-29 16:43:43.057 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 155
2022-09-29 16:43:43.073 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 165
2022-09-29 16:43:43.091 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 175
2022-09-29 16:43:43.108 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 186
2022-09-29 16:43:43.124 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 198
2022-09-29 16:43:43.141 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 209
2022-09-29 16:43:43.157 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 221
2022-09-29 16:43:43.173 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 231
2022-09-29 16:43:43.191 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 240
2022-09-29 16:43:43.207 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 248
2022-09-29 16:43:43.224 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 255
2022-09-29 16:43:43.240 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 260
2022-09-29 16:43:43.257 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 263
2022-09-29 16:43:43.273 12083-12083 MAIN_ACTIVITY           com...aniwebanimateviewlayoutparams  D  Animated Value: 264

Animate LayoutParams Using ObjectAnimator

If you do not like the solution above for some reason, you can actually animate the LayoutParams object directly using ObjectAnimator (or ValueAnimator.ofObject()). ObjectAnimator is a subclass of ValueAnimator.

ObjectAnimator provides a large amount of static factory methods, but they all work similarly. The only downside to this method as opposed to a simple value animator is that you will have to write more code. The upside is that you do not have to create a listener. This method is probably more efficient because each frame is not calculated twice (my assumption is based on the Java docs of addUpdateListener).

For demonstration, here is an example implementation of the method public static ObjectAnimator ofObject (T target, Property<T, V> property, TypeEvaluator<V> evaluator, V... values)

findViewById<ImageView>(R.id.imageView).setOnClickListener { image ->
   image.updateLayoutParams {
       height = image.height
       width = image.width
   }

   Log.d(TAG, "Starting height expected: ${image.layoutParams.height * 2}")
   Log.d(TAG, "Starting width expected: ${image.layoutParams.width * 2}")

   ObjectAnimator.ofObject(
       image,
       Property.of(View::class.java, LayoutParams::class.java, "layoutParams"),
       { fraction, next, finalTarget ->
           // next will change as the animation progress
           // finalTarget will stay the same throughout the entire animation
           next.apply {
               height = ((fraction + 1) * finalTarget.height - height).toInt()
               width = ((fraction + 1) * finalTarget.width - width).toInt()
           }
       },
       // Copy constructor. This is final target
       LayoutParams(image.layoutParams).apply {
           height *= 2
           width *= 2
       }
   ).apply {
       doOnEnd {
           Log.d(TAG, "Final height: ${image.height}")
           Log.d(TAG, "Final width: ${image.width}")
           // If you want to, you can set the exact LayoutParams value
           // here to make up for loss precision when converting Float to Int
       }
       duration = 2000
       start()
   }
}

The complexity added here are:

  1. You will have to define a Property object.
  2. You will have to define a TypeEvaluator.
  3. Since the property layoutParams is mutable, you need to be careful where you are passing it.

Summary

We have learned how to animate LayoutParams in this tutorial. The full project code can be found at https://github.com/dmitrilc/DaniwebAnimateViewLayoutParams.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.