I’ve talked about Constraint Layout a lot over the last few weeks, and I promise this will be the last for a while (maybe). For a long time it’s been so much harder to create animations in Android than iOS. iOS, combining AutoLayout and UIView.animate(with duration: TimeInterval), makes simple animations super easy, just by changing the ‘constant’ value of a constraint and calling layoutIfNeeded() in the UIView.animate block. But now that Android has Constraint Layout and the TransitionManager, it’s almost as easy to do those same animations in Android now, and more complex animations that involve multiple constraint changes are a lot easier than in iOS.
Let’s start at the basics – moving a single view up and down the screen. In iOS, this would be as simple as constraining your view’s bottom edge, grabbing a reference to that constraint either through an IBOutlet or programatically, changing the constant value and then calling view.layoutIfNeeded() in the UIView.animate(). In Android, because constraints don’t have a constant value like in iOS, we instead need to make use of guidelines to do this. If you don’t know what guidelines are, check out my previous post.
In your xml, create a TextView, add a horizontal guideline, and constrain the top and bottom of the TextView to the guideline. Then constraint the start and end of your TextView to the parent. Now in your Activity, override onTouchEvent and return super.onTouchEvent(event) if event?.action != MotionEvent.ACTION_DOWN. Now we need to clone the current ConstraintSet used by our Constraint Layout, and use the ConstraintSet$setGuidelinePercent(viewId: Int, ratio: Float) method like this:
set.setGuidelinePercent(R.id.guideline, 0.2f)
Now we use the TransitionManager$beginDelayedTransition and pass our constraint layout, and then call ConstraintSet$applyTo(constraintSet). Now you can run your app and press anywhere on the screen and see your TextView animate.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MainActivity : AppCompatActivity() { | |
var hasAnimated = false | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_main) | |
} | |
override fun onTouchEvent(event: MotionEvent?): Boolean { | |
if (event?.action != MotionEvent.ACTION_DOWN) return super.onTouchEvent(event) | |
val set = ConstraintSet() | |
set.clone(constraint_layout) | |
if (!hasAnimated) { | |
set.setGuidelinePercent(R.id.guideline, 0.2f) | |
} else { | |
set.setGuidelinePercent(R.id.guideline, 0.5f) | |
} | |
TransitionManager.beginDelayedTransition(constraint_layout) | |
set.applyTo(constraint_layout) | |
hasAnimated = !hasAnimated | |
return true | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<android.support.constraint.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:id="@+id/constraint_layout" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent"> | |
<TextView | |
android:id="@+id/label" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:text="Hello World!" | |
app:layout_constraintBottom_toTopOf="@+id/guideline" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintHorizontal_bias="0.5" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="@+id/guideline" /> | |
<android.support.constraint.Guideline | |
android:id="@+id/guideline" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:orientation="horizontal" | |
app:layout_constraintGuide_percent="0.5" /> | |
</android.support.constraint.ConstraintLayout> |
Cool, so now we can make views move. You can also do this to change the alpha, visibility, rotation, etc. The difference than how it would usually be done is rather than calling View$visibility = View.GONE you do it on the ConstraintSet, like ConstraintSet$setVisibility(viewId: Int, visibility: Int)
Now here’s where Constraint Layout really wins over AutoLayout animations. Let’s say you want to perform some sort of complex animation where multiple view’s constraints are changed and animated. In Constraint Layout this is easy! You just need to shadow your xml and modify the constraints and then clone those constraints in code using ConstraintSet$clone, then apply your new constraint set to your existing constraint layout. I’ve already talked about this here:
https://jordan-dixon.com/2017/12/02/all-you-need-to-know-about-constraint-layout/
Just to note, you don’t have to recreate every view in the shadow xml, only those that change, but you’re gonna need to include views that don’t change if the views that do change have constraints that depend on them. This is much more difficult to do on iOS as constraints are treated like views rather than being contained in a separate object. After working with ConstraintLayout for a while and getting used to how it abstracts details away that AutoLayout provides as raw to the developer, I really wish this is how AutoLayout worked.
Until next time!