Animate Objects, not Views

Bogdan Voiko
AndroidPub
Published in
3 min readSep 6, 2017

--

Admit, we all love fancy animations. No customer loves dry jaggy apps with no transitions and animations. But same time no developer loves to spend hours and hours dealing with ValueAnimators, especially if you need to create bunch of them to achieve desired effect.

So why wouldn’t we use power of DataBinding to animate some complex stuff?

As all you know we have BaseObservable that can contain fields marked with @Bindable annotations.

Like this:

public class ExpandStateItem extends BaseObservable {

@Bindable
private float padding;
@Bindable
private boolean expanded;
@Bindable
private boolean fast;
}

And if you call notifyPropertyChanged(BR.field_name) any view that uses this field will be notified and then updated. So only one thing left is to bind right stuff to right values.

In my example there is 3 properties that can be bound. Lets look at them one by one.

First one is padding that we need to bind like this

<LinearLayout 
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@{state.padding}"
android:paddingTop="@{state.padding}">
....
</LinearLayout>

state variable is a variable of type ExpandStateItem declared in layout file

Android DataBinding library even know how to set padding attributes, so you don’t need to create @BindingAdapter by your own.

By now if you set ExpandStateItem objects padding to some value you’ll see that all views that it corresponds to will actually change it’s padding.

And if you create some Animator and change this values on animation callback, all views will animate accordingly.

Here is little example of it. states is an array that is bound to RecyclerView.ViewHolders in RecyclerView .

private ValueAnimator paddingAnimator = ValueAnimator.ofFloat(MAX_MARGIN, MIN_MARGIN);paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
for (ExpandStateItem item :
states) {
item.setPading((float) animation.getAnimatedValue());
}
}
});

Next variable is expanded . If you noticed GIF image in the top of an article, it has some expandable cards, they are expanding/collapsing with this variable change. It bound this way:

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
app:expand="@{state.expanded}"
app:fastExpand="@{state.fast}">
...
</LinearLayout>

Also you can notice app:fastExpand there, I’ll tell you about this little bit later.

For our custom app:expand attribute we need to write our own @BindingAdapter .

@BindingAdapter({"expand", "fastExpand"})
public static void expandView(View view, boolean expand, boolean fast) {
if (expand) {
ViewAnimationUtils.expand(view, null, fast);
} else if (view.getHeight() != 0) {
ViewAnimationUtils.collapse(view, null, fast);
}
}

And now if I show you ViewAnimationUtils class I bet you’ll figure out why do we need fastExpand attribute.

By specifying fastExpand as true there will be no animation time.

But why do we need to remove animation time? Actually you don’t really need to do so if you would use this in lists with recycling. When view will be recycled and used again (in RecyclerView it’s onBindViewHolder) all animations will occur again for this specific view, so we need to specify that we just want to expand view, with no progress exposing.

That was my example how you can interact (animate) with view without touching the view (actually you will, but indirectly). Thing that you can do with it are limited only by your imagination. View margins, paddings, width, height, TextView lines count, color, taste (no), thousands of them.

Source code for my example available on GitHub here.

Thanks for attention and happy coding :)

--

--