Design2Code, Part 1 — Vector Animation using Shape Shifter

Ahmed Talaat
Goodpatch Global
Published in
4 min readMar 12, 2018

Back in October last year, we at Goodpatch gave our interns a design challenge of coming up with a daily concept and post it on our instagram account. the output was great!

Then I thought that it would be great to make these designs come to life!

So, in this series I’ll be explaining the UI/UX behind each concept along with implementation details on Android.

Concept

This is an image that can be morphed into different cup sizes based on the users action. This UI component can be added to a coffee delivery app for example.

If you look at it from an Android developer perspective, you’ll notice that it can be implemented using a Seekbar (aka Slider) that can control the animated vector drawable of the cup with three states using ObjectAnimator or with what is introduced in Lollipop “AnimatedStateListDrawable”.

This will require generating 3 vector drawables for each state, “VectorDrawable” lets you create a drawable based on an XML vector graphic. I’ll be generating those with the help of tools like Sketch and Android Studio’s vector asset tool.

drawable/vd_cup_small.xml
drawable/vd_cup_medium.xml
drawable/vd_cup_large.xml

Now as the concept refers we have to map theses states with users’ slider action so that it changes its shape accordingly

Animated states

The cup morphing will be as follows:

  • Small to medium
  • Medium to large
  • Large to medium
  • Medium to small

I used a tool called Shape Shifter by Alex Lockwood to generate those states. It helps you animate SVG based images and export those animations into an animated vector drawable. All you have to do is alter the path data, also Shapeshifter has a feature called autofix that can fix the path data automagically using Needleman–Wunsch algorithm

drawable/avd_cup_small_medium.xml
drawable/avd_cup_medium_small.xml
drawable/avd_cup_medium_large.xml
drawable/avd_cup_large_medium.xml

Then the AnimatedStateDrawable will be like this. We’ll define 3 <item> tags that would represent our frames and each will be composed of a vector drawable. We’ll also define 3 <transition> tags that will show how an item transitions from one to another.

<?xml version="1.0" encoding="utf-8"?>
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/frame_0"
android:drawable="@drawable/vd_cup_small"
app:state_large="false"
app:state_medium="false" />
<item
android:id="@+id/frame_1"
android:drawable="@drawable/vd_cup_medium"
app:state_medium="true" />
<item
android:id="@+id/frame_2"
android:drawable="@drawable/vd_cup_large"
app:state_large="true" />
<transition
android:drawable="@drawable/avd_anim_cup_small_medium"
android:fromId="@id/frame_0"
android:toId="@id/frame_1" />
<transition
android:drawable="@drawable/avd_anim_cup_medium_large"
android:fromId="@id/frame_1"
android:toId="@id/frame_2" />
<transition
android:drawable="@drawable/avd_anim_cup_large_medium"
android:fromId="@id/frame_2"
android:toId="@id/frame_1" />
<transition
android:drawable="@drawable/avd_anim_cup_medium_small"
android:fromId="@id/frame_1"
android:toId="@id/frame_0" />
</animated-selector>

Now we have 2 views to be added to our layout

  • ImageView: and make the AnimatedStateDrawable it’s source resource
  • Seekbar: will look different than the iOS concept as by Material design guidelines on Android. We will divide the Seekbar to 3 sections and update the states based on progress.
<?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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.goodpatch.design2code_part1.MainActivity">

<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:src="@drawable/asl_cup"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
android:max="2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />

</android.support.constraint.ConstraintLayout>

Using the “in” operator introduced in Kotlin, we can check if a value belongs to a range. This will allow us to check if a progress belongs to a specific range.

private val STATE_SMALL = intArrayOf(
R.attr.state_small, -R.attr.state_medium, -R.attr.state_large
)
private val STATE_MEDIUM = intArrayOf(
-R.attr.state_small, R.attr.state_medium, -R.attr.state_large
)
private val STATE_LARGE = intArrayOf(
-R.attr.state_small, -R.attr.state_medium, R.attr.state_large
)
private fun handleCupAnimationState(progress: Int) {
val max = seekBar.max
val cupType = when (progress) {
in 0..max / 3 -> STATE_SMALL
in max / 3..max / 2 -> STATE_MEDIUM
in max / 2..max -> STATE_LARGE
else -> throw IllegalStateException()
}
imageView.setImageState(cupType, true)
}

Und…Voilà

Thanks for reading this article.
Be sure to recommend and share it if you found it helpful and tune in for the next one!

Github repository

References

Fragmented 107: Shape shifting SVGs with Alex Lockwood

droidcon SF 2017 — In-depth path morphing w/ Shape Shifter

From design to android, part 2

--

--