Translating the Toolbar
This topic describes how to translate the Toolbar, which are implemented in the following examples.
- ToolbarControlBaseActivity
- ToolbarControlGridViewActivity
- ToolbarControlListViewActivity
- ToolbarControlRecyclerViewActivity
- ToolbarControlScrollViewActivity
- ToolbarControlWebViewActivity
About the Toolbar
In this section we learn how to translate the Toolbar.
Toolbar was introduced on Android 5.0, and you can also use it on pre-Lollipop devices
by using v7 appcompat library
of the Android Support Library package.
Design of the examples
The existing examples above, ToolbarControlBaseActivity
has most of the codes to avoid writing duplicate codes.
If you use one of them, you don't have to use this structure: extending Activity is not required to achieve this effect.
Create layout file
In this topic, we use ObservableListView
and Toolbar
, and wrap them with FrameLayout
.FrameLayout
and RelativeLayout
are useful to translate views inside of it separately.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
app:popupTheme="@style/Theme.AppCompat.Light.DarkActionBar"
app:theme="@style/Toolbar" />
<com.github.ksoichiro.android.observablescrollview.ObservableListView
android:id="@+id/scrollable"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize" />
</FrameLayout>
How to translate the Toolbar
The basic idea about showing/hiding the Toolbar is exactly the same as the ActionBar.
However, the Toolbar class does not provide any convinient methods like show()
and hide()
which the ActionBar class has.
Therefore we should implement such methods to translate the Toolbar.
Our goal is to make the following codes work:
@Override
public void onUpOrCancelMotionEvent(ScrollState scrollState) {
if (scrollState == ScrollState.UP) {
if (toolbarIsShown()) { // TODO Not implemented
hideToolbar(); // TODO Not implemented
}
} else if (scrollState == ScrollState.DOWN) {
if (toolbarIsHidden()) { // TODO Not implemented
showToolbar(); // TODO Not implemented
}
}
}
Using NineOldAndroids
Before we begin, you should confirm whether you're going to support pre-Honeycomb devices. To translate the Toolbar, we would like to use the Property Animation APIs which are introduced in API level 11, so if you are going to support pre-Honeycomb devices, JakeWharton/NineOldAndroids might be useful (although it's marked as deprecated).
In this project, all the examples use NineOldAndroids.
So if you don't support pre-Honeycomb devices, please replace ViewHelper.methodName(viewObject)
to viewObject.methodName()
.
NineOldAndroids: ViewHelper.getTranslationY(mToolbar)
Platform API: mToolbar.getTranslationY()
If you use NineOldAndroids, add an entry to the dependencies
closure in your build.gradle
:
dependencies {
compile 'com.nineoldandroids:library:2.4.0'
}
toolbarIsShown()/toolbarIsHidden()
Now let's start from the easiest part.
To avoid redundant translation, we need methods to check if the Toolbar is shown or hidden.
With the property animation APIs (or NineOldAndroids), we just simply check the translationY
property.
private boolean toolbarIsShown() {
// Toolbar is 0 in Y-axis, so we can say it's shown.
return ViewHelper.getTranslationY(mToolbar) == 0;
}
private boolean toolbarIsHidden() {
// Toolbar is outside of the screen and absolute Y matches the height of it.
// So we can say it's hidden.
return ViewHelper.getTranslationY(mToolbar) == -mToolbar.getHeight();
}
Implement showToolbar()/hideToolbar()
Next, let's implement methods to animate the Toolbar.
Before thinking about details, write some pseudocodes to simplify the problem.
To show or hide the Toolbar, we just need one method to move the Toolbar.
private void showToolbar() {
moveToolbar(0);
}
private void hideToolbar() {
moveToolbar(-mToolbar.getHeight());
}
This should work, if we implement the moveToolbar
method correctly :)
Most of the animation codes are combination of property value calculations, and I think it's very hard to keep these information in my brain or imagine correctly. And this approach is useful to implement the complex animation.
Implement moveToolbar()
Although we named the method moveToolbar
, it's not everything we need to handle.
In ActionBar examples, not only the ActionBar is moved but also the height of the view (Observable*View
) is changed.
And we need to implement this behavior for the Toolbar.
To use the changing property values, we can use ValueAnimator
.ValueAnimator
has a callback method onAnimationUpdate
, and we can get the animation progress from it.ValueAnimator
itself does not animate anything, we need to animate something using a parameter of the callback.
ValueAnimator animator = ValueAnimator.ofFloat(0, 100).setDuration(200);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
// You can do whatever you want with the `value`.
}
});
In the example code above, the local variable value
changes from 0f
to 100f
in 200ms.
In this case, we should change the translationY
property of the Toolbar,
and change the height of the Observable*View
like this:
private void moveToolbar(float toTranslationY) {
ValueAnimator animator = ValueAnimator.ofFloat(ViewHelper.getTranslationY(mToolbar), toTranslationY).setDuration(200);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float translationY = (float) animation.getAnimatedValue();
ViewHelper.setTranslationY(mToolbar, translationY);
ViewHelper.setTranslationY((View) mScrollable, translationY);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) ((View) mScrollable).getLayoutParams();
lp.height = (int) -translationY + getScreenHeight() - lp.topMargin;
((View) mScrollable).requestLayout();
}
});
animator.start();
}
The translationY
local variable changes from ViewHelper.getTranslationY(mToolbar)
( == current translationY)
to toTranslationY
.
To translate the Toolbar, we just call ViewHelper.setTranslationY()
.
And to change the height of the wrapper view (FrameLayout
), set the height value of FrameLayout.LayoutParams
and update by calling requestLayout()
.
Avoid redundant animation
We'd better check the current translationY
value
and if it's already equal to toTranslationY
, stop the animation.
private void moveToolbar(float toTranslationY) {
// Check the current translationY
if (ViewHelper.getTranslationY(mToolbar) == toTranslationY) {
return;
}
// Codes after that are omitted
}
That's all.