Animate Objects, not Views
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 ValueAnimator
s, 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.ViewHolder
s 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 :)