ConstraintLayout Tutorial for Android: Complex Layouts

In this ConstraintLayout tutorial, you’ll learn how to dynamically position UI elements in relation to other elements on the screen and to animate your views. By Fuad Kamal.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 4 of 5 of this article. Click here to view the first page.

Setting up the Starting Layout for Your Animation

In your project, duplicate your layout file and name the duplicate keyframe1.xml. You’re going to need to alter the positions of elements in this new layout and set this new layout as the starting layout for the app.

To start, open keyframe1.xml and change the layout_constraintGuide_begin property of guideline1 from 200dp to 0dp. This moves the guide, the elements constrained to the guide, and all elements constrained to them higher up, so that some of them are are now offscreen.

Then change the layout_constraintGuide_percent property of guideline2 from .05 to 1. This moves the guide and the elements constrained to it to the far right so that they are offscreen as well.

Now, we’ve changed the layout by just moving a couple of guides, but we still need to make this new layout the starting layout for the app. To do this, open MainActivity.kt and modify the setContentView() call in the onCreate() function to pass in R.layout.keyframe1 instead of R.layout.activity_main:

setContentView(R.layout.keyframe1)

Build and run your app. The orange switch and label and the arrival and destination space ports no longer appear on the screen. Additionally, the rocket and universe icons have moved up:
altered guidelines

Animating the View

Change the following import statement in your MainActivity.kt Kotlin class:

import import kotlinx.android.synthetic.main.activity_main.*

to the following:

import kotlinx.android.synthetic.main.keyframe1.*

This allows you to reference UI elements in the new layout XML without any findViewById() craziness from the pre-historic days of Android development. :]

Next, add the following private properties to the class. You may need to add the android.support.constraint.ConstraintSet import:

private val constraintSet1 = ConstraintSet()
private val constraintSet2 = ConstraintSet()

private var isOffscreen = true

The first two properties are the constraint sets that you’ll use to animate your view. You will use the boolean to keep track of the layout state.

Transition Manager

You can use the Transition Manager class to handle transitioning from one keyframe to another. To create a layout animation, you simply provide Transition Manager with the ConstraintSet you want to animate and it will handle the rest. Optionally, you can provide it with custom animations to perform.

Now, add the following to the onCreate() function, importing TransitionManager:

constraintSet1.clone(constraintLayout) //1
constraintSet2.clone(this, R.layout.activity_main) //2

departButton.setOnClickListener { //3
  //apply the transition
  TransitionManager.beginDelayedTransition(constraintLayout) //4
  val constraint = if (!isOffscreen) constraintSet1 else constraintSet2
  isOffscreen = !isOffscreen
  constraint.applyTo(constraintLayout) //5
}
  1. This pulls the layout information from the initial layout into one of the constraint sets, constraintSet1. Since you added an ID to the ConstraintLayout earlier, you can refer to it directly from code now.
  2. This pulls the layout information from the final layout into constraintSet2. Since you are creating a ConstraintSet and you never actually inflate the second layout file, you avoid the overhead and performance hit of dealing with a second layout.
  3. This adds the animation in the listener for the button, for now, so that you can trigger the animation whenever it’s toggled.
  4. This calls Transition Manager’s beingDelayedTransition function.
  5. This applies the new ConstraintSet to the currently displayed ConstraintLayout.

Build and run the app. Click the button at the bottom of the screen repeatedly to see how the animation works.

Voila! The app loads with a bunch of elements offscreen. When you tap the button, the guide positions animate, which causes everything constrained to them to animate as well.
guidelines animated

Animating the Bounds of a View

Not only can you change the position of elements onscreen by affecting their constraints, but you can also change their size.

Open keyframe1.xml and select the galaxy icon, whose ID is galaxyIcon. Change the layout_height property from 90dp to 10dp.

Note: In activity_main.xml, the height is still set to 90dp.

Note: In activity_main.xml, the height is still set to 90dp.

Build and run the app and tap the button at the bottom repeatedly. Now you can witness the expansion of the galaxy in action! :]
view bounds animated

Using Custom Transitions to Make Animation Easier

You now have a couple of animations tied to the switch, but wouldn’t it be nice for the view to animate automatically when it first loads?

You’ll do that next, but first you’ll create a custom animation instead of using the default animation, and you’ll also customize the animation’s timing.

Add the following function to MainActivity.kt, adding the import for android.transition.AutoTransition if it isn’t added automatically:

override fun onEnterAnimationComplete() { //1
  super.onEnterAnimationComplete()

  constraintSet2.clone(this, R.layout.activity_main) //2

  //apply the transition
  val transition = AutoTransition() //3
  transition.duration = 1000 //4
  TransitionManager.beginDelayedTransition(constraintLayout, transition) //5

  constraintSet2.applyTo(constraintLayout) //6
}
  1. Activities can’t draw anything while the view is animating. onEnterAnimationComplete() is the point in the app life cycle where the view animation has completed and it’s safe to call on drawing code.
  2. This pulls the layout information from your final layout into constraintSet2.
  3. This creates a custom transition. In this case, you are using a built-in transition, AutoTransition(), which first fades out disappearing targets, then moves and resizes existing targets, and finally fades in appearing targets.
  4. This sets a duration of 1,000 milliseconds for the animation, so that it’s slow enough to be seen.
  5. This calls Transition Manager’s beingDelayedTransition function, but this time you also supply your custom transition.
  6. This applies the new ConstraintSet to the currently-displayed ConstraintLayout.

Build and run the app. Now, all of the animations occur as soon as the view loads.
animations on load

Animating the Circular Constraint

Remember that funny circular constraint you added earlier? Time to add the grand finale animation by flying the rocket around the galaxy!

To animate the rocket around the galaxy, you have to alter two properties: the angle of the circular constraint, which moves the position of the rocket around the circle, and the rotation of the rocket to complete the illusion. You also check the One Way / Round Trip switch value to determine whether the rocket should fly half a circle or one full circle.

Replace the click listener for the DEPART button in onCreate() as follows:

departButton.setOnClickListener {
  //1
  val layoutParams = rocketIcon.layoutParams as ConstraintLayout.LayoutParams
  val startAngle = layoutParams.circleAngle
  val endAngle = startAngle + (if (switch1.isChecked) 360 else 180)

  //2
  val anim = ValueAnimator.ofFloat(startAngle, endAngle)
  anim.addUpdateListener { valueAnimator ->
    
    //3
    val animatedValue = valueAnimator.animatedValue as Float
    val layoutParams = rocketIcon.layoutParams as ConstraintLayout.LayoutParams
    layoutParams.circleAngle = animatedValue
    rocketIcon.layoutParams = layoutParams

    //4
    rocketIcon.rotation = (animatedValue % 360 - 270)
  }
  //5
  anim.duration = if (switch1.isChecked) 2000 else 1000
  
  //6
  anim.interpolator = LinearInterpolator()
  anim.start()
}
  1. Set startAngle to the current angle of the rocket before animation start. Depending on One Way / Round Trip switch, endAngle is either 180 or 360 degree in addition to startAngle value.
  2. ValueAnimator class provides a simple timing engine for running animations between two values. Here you provide startAngle and endAngle to create the instance of ValueAnimator.
  3. Inside update listener of ValueAnimator instance, obtain the animated value and assign it to the rocket’s circleAngle in layoutParams.
  4. Rotate the rocket with animated value. This will make the rocket fly in more natural direction.
  5. This is quite straightforward. One way animation takes 1 second, while round trip animation takes 2 seconds.
  6. Choose LinearInterpolator to make sure passengers have pleasant flight. You can try AnticipateOvershootInterpolator to see what will happen :] Last but not least, start the animation!

Build and run the app. Click the DEPART button and toggle the switch to make the rocket fly around the galaxy:
fly around the galaxy

Congratulations! You’ve built a complex UI layout for a space travel Android app, and you’ve added some cool animations to it with just a few lines of code.