It's very easy to implement Toolbar
with hamburger to back arrow animation. In my opinion this animation is pointless because as per material design spec navigation drawer covers the Toolbar
when opened. My question is how to properly disable this animation and show either hamburger or back arrow using getSupportActionBar().setDisplayHomeAsUpEnabled(true);
This is how I did it, but it looks like a dirty hack:
mDrawerToggle.setDrawerIndicatorEnabled(false);
if (showHomeAsUp) {
mDrawerToggle.setHomeAsUpIndicator(R.drawable.lib_ic_arrow_back_light);
mDrawerToggle.setToolbarNavigationClickListener(view -> finish());
} else {
mDrawerToggle.setHomeAsUpIndicator(R.drawable.lib_ic_menu_light);
mDrawerToggle.setToolbarNavigationClickListener(view -> toggleDrawer());
}
Any clues how this should be properly implemented to use just setDisplayHomeAsUpEnabled
to switch between hamburger and back arrow icons?
This will disable the animation, when creating the drawerToggle, override onDrawerSlide():
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout,
getToolbar(), R.string.open, R.string.close) {
@Override
public void onDrawerClosed(View view) {
super.onDrawerClosed(view);
}
@Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
}
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, 0); // this disables the animation
}
};
If you want to remove the arrow completely, you can add
super.onDrawerSlide(drawerView, 0); // this disables the arrow @ completed state
at the end of the onDrawerOpened function.
In my opinion this animation is pointless
Well, ActionBarDrawerToggle
is meant to be animated.
You can customize the the animated toggle by defining the drawerArrowStyle in your ActionBar theme.
Any clues how this should be properly implemented to use just setDisplayHomeAsUpEnabled to switch between hamburger and back arrow icons?
The ActionBarDrawerToggle
is just a fancy way of calling ActionBar.setHomeAsUpIndicator
. So, either way you're going to have to call ActionBar.setDisplayHomeAsUpEnabled
to true
in order to display it.
If you're convinced that you have to use it, then I'd suggest just calling ActionBarDrawerToggle.onDrawerOpened(View drawerView)
and ActionBarDrawerToggle.onDrawerClosed(View drawerView)
respectively.
This will set the DrawerIndicator
position to 1
or 0
, switching between the arrow and the hamburger states of the DrawerArrowDrawable
.
And in your case, there's no need to even attach an ActionBarDrawerToggle
as a DrawerLayout.DrawerListener
. As in:
mYourDrawer.setDrawerListener(mYourDrawerToggle);
But a much more forward approach would be to call ActionBar.setHomeAsUpIndicator
once and apply your own hamburger icon, you could also do this via a style. Then when you want to display the back arrow, just call ActionBar.setDisplayHomeAsUpEnabled
and let AppCompat or the framework handle the rest. From the comments you've made, I'm pretty sure this is what you're looking for.
If you're unsure which icon to use, the default DrawerArrowDrawable
size is 24dp
, which means you'd want to grab the ic_menu_white_24dp
or ic_menu_black_24dp
from the navigation icon set in Google's official Material design icon pack.
You could also copy the DrawerArrowDrawable
into your project and let then toggle the arrow or hamburger states as you need them. It's self contained, minus a few resources.
I had a similar requirement and spent some time going through ActionBarDrawerToggle
code. What you currently have is the best way forward.
More to come:
The hamburger to arrow animation is provided by a drawable implementation - DrawerArrowDrawableToggle
. Currently, we don't have much control over how this drawable reacts to drawer states. Here's what the package-access constructor for actionVarDrawerToggle
says:
/**
* In the future, we can make this constructor public if we want to let developers customize
* the
* animation.
*/
<T extends Drawable & DrawerToggle> ActionBarDrawerToggle(Activity activity, Toolbar toolbar,
DrawerLayout drawerLayout, T slider,
@StringRes int openDrawerContentDescRes,
@StringRes int closeDrawerContentDescRes)
By providing your own implementation of slider
, you can control how it reacts to drawer states. The interface that slider
must implement:
/**
* Interface for toggle drawables. Can be public in the future
*/
static interface DrawerToggle {
public void setPosition(float position);
public float getPosition();
}
setPosition(float)
is the highlight here - all drawer state changes call it to update the drawer indicator.
For the behavior you want, your slider
implementation's setPosition(float position)
would do nothing.
You will still need:
if (showHomeAsUp) {
mDrawerToggle.setDrawerIndicatorEnabled(false);
// Can be set in theme
mDrawerToggle.setHomeAsUpIndicator(R.drawable.lib_ic_arrow_back_light);
mDrawerToggle.setToolbarNavigationClickListener(view -> finish());
}
If you don't setDrawerIndicatorEnabled(false)
, the OnClickListener
you set with setToolbarNavigationClickListener(view -> finish());
will not fire.
What can we do right now ?
On closer inspection, I find that there is a provision for your requirement in ActionBarDrawerToggle
. I find this provision even more of an hack than what you currently have. But, I'll let you decide.
ActionBarDrawerToggle
lets you have some control over the drawer indicator through interface Delegate . You can have your activity implement this interface in the following manner:
public class TheActivity extends ActionBarActivity implements ActionBarDrawerToggle.Delegate {
....
@Override
public void setActionBarUpIndicator(Drawable drawableNotUsed, int i) {
// First, we're not using the passed drawable, the one that animates
// Second, we check if `displayHomeAsUp` is enabled
final boolean displayHomeAsUpEnabled = (getSupportActionBar().getDisplayOptions()
& ActionBar.DISPLAY_HOME_AS_UP) == ActionBar.DISPLAY_HOME_AS_UP;
// We'll control what happens on navigation-icon click
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (displayHomeAsUpEnabled) {
finish();
} else {
// `ActionBarDrawerToggle#toggle()` is private.
// Extend `ActionBarDrawerToggle` and make provision
// for toggling.
mDrawerToggle.toggleDrawer();
}
}
});
// I will talk about `mToolbarnavigationIcon` later on.
if (displayHomeAsUpEnabled) {
mToolbarNavigationIcon.setIndicator(
CustomDrawerArrowDrawable.HOME_AS_UP_INDICATOR);
} else {
mToolbarNavigationIcon.setIndicator(
CustomDrawerArrowDrawable.DRAWER_INDICATOR);
}
mToolbar.setNavigationIcon(mToolbarNavigationIcon);
mToolbar.setNavigationContentDescription(i);
}
@Override
public void setActionBarDescription(int i) {
mToolbar.setNavigationContentDescription(i);
}
@Override
public Drawable getThemeUpIndicator() {
final TypedArray a = mToolbar.getContext()
.obtainStyledAttributes(new int[]{android.R.attr.homeAsUpIndicator});
final Drawable result = a.getDrawable(0);
a.recycle();
return result;
}
@Override
public Context getActionBarThemedContext() {
return mToolbar.getContext();
}
....
}
ActionBarDrawerToggle
will use setActionBarUpIndicator(Drawable, int)
provided here. Since, we are ignoring the Drawable
being passed, we have full control over what will be displayed.
Catch: ActionBarDrawerToggle
will let our Activity
act as a delegate if we pass the Toolbar
parameter as null here:
public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout,
Toolbar toolbar, @StringRes int openDrawerContentDescRes,
@StringRes int closeDrawerContentDescRes) { .... }
And, you will need to override getV7DrawerToggleDelegate()
in your activity:
@Nullable
@Override
public ActionBarDrawerToggle.Delegate getV7DrawerToggleDelegate() {
return this;
}
As you can see, going about the proper way is a lot of extra work. And we're not done yet.
The animating DrawerArrowDrawableToggle
can be styled using these attributes . If you want your drawable states(homeAsUp & hamburger) exactly like the defaults, you will need to implement it as such:
/**
* A drawable that can draw a "Drawer hamburger" menu or an Arrow
*/
public class CustomDrawerArrowDrawable extends Drawable {
public static final float DRAWER_INDICATOR = 0f;
public static final float HOME_AS_UP_INDICATOR = 1f;
private final Activity mActivity;
private final Paint mPaint = new Paint();
// The angle in degress that the arrow head is inclined at.
private static final float ARROW_HEAD_ANGLE = (float) Math.toRadians(45);
private final float mBarThickness;
// The length of top and bottom bars when they merge into an arrow
private final float mTopBottomArrowSize;
// The length of middle bar
private final float mBarSize;
// The length of the middle bar when arrow is shaped
private final float mMiddleArrowSize;
// The space between bars when they are parallel
private final float mBarGap;
// Use Path instead of canvas operations so that if color has transparency, overlapping sections
// wont look different
private final Path mPath = new Path();
// The reported intrinsic size of the drawable.
private final int mSize;
private float mIndicator;
/**
* @param context used to get the configuration for the drawable from
*/
public CustomDrawerArrowDrawable(Activity activity, Context context) {
final TypedArray typedArray = context.getTheme()
.obtainStyledAttributes(null, R.styleable.DrawerArrowToggle,
R.attr.drawerArrowStyle,
R.style.Base_Widget_AppCompat_DrawerArrowToggle);
mPaint.setAntiAlias(true);
mPaint.setColor(typedArray.getColor(R.styleable.DrawerArrowToggle_color, 0));
mSize = typedArray.getDimensionPixelSize(R.styleable.DrawerArrowToggle_drawableSize, 0);
mBarSize = typedArray.getDimension(R.styleable.DrawerArrowToggle_barSize, 0);
mTopBottomArrowSize = typedArray
.getDimension(R.styleable.DrawerArrowToggle_topBottomBarArrowSize, 0);
mBarThickness = typedArray.getDimension(R.styleable.DrawerArrowToggle_thickness, 0);
mBarGap = typedArray.getDimension(R.styleable.DrawerArrowToggle_gapBetweenBars, 0);
mMiddleArrowSize = typedArray
.getDimension(R.styleable.DrawerArrowToggle_middleBarArrowSize, 0);
typedArray.recycle();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.SQUARE);
mPaint.setStrokeWidth(mBarThickness);
mActivity = activity;
}
public boolean isLayoutRtl() {
return ViewCompat.getLayoutDirection(mActivity.getWindow().getDecorView())
== ViewCompat.LAYOUT_DIRECTION_RTL;
}
@Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
final boolean isRtl = isLayoutRtl();
// Interpolated widths of arrow bars
final float arrowSize = lerp(mBarSize, mTopBottomArrowSize, mIndicator);
final float middleBarSize = lerp(mBarSize, mMiddleArrowSize, mIndicator);
// Interpolated size of middle bar
final float middleBarCut = lerp(0, mBarThickness / 2, mIndicator);
// The rotation of the top and bottom bars (that make the arrow head)
final float rotation = lerp(0, ARROW_HEAD_ANGLE, mIndicator);
final float topBottomBarOffset = lerp(mBarGap + mBarThickness, 0, mIndicator);
mPath.rewind();
final float arrowEdge = -middleBarSize / 2;
// draw middle bar
mPath.moveTo(arrowEdge + middleBarCut, 0);
mPath.rLineTo(middleBarSize - middleBarCut, 0);
final float arrowWidth = Math.round(arrowSize * Math.cos(rotation));
final float arrowHeight = Math.round(arrowSize * Math.sin(rotation));
// top bar
mPath.moveTo(arrowEdge, topBottomBarOffset);
mPath.rLineTo(arrowWidth, arrowHeight);
// bottom bar
mPath.moveTo(arrowEdge, -topBottomBarOffset);
mPath.rLineTo(arrowWidth, -arrowHeight);
mPath.moveTo(0, 0);
mPath.close();
canvas.save();
if (isRtl) {
canvas.rotate(180, bounds.centerX(), bounds.centerY());
}
canvas.translate(bounds.centerX(), bounds.centerY());
canvas.drawPath(mPath, mPaint);
canvas.restore();
}
@Override
public void setAlpha(int i) {
mPaint.setAlpha(i);
}
// override
public boolean isAutoMirrored() {
// Draws rotated 180 degrees in RTL mode.
return true;
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getIntrinsicHeight() {
return mSize;
}
@Override
public int getIntrinsicWidth() {
return mSize;
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
public void setIndicator(float indicator) {
mIndicator = indicator;
invalidateSelf();
}
/**
* Linear interpolate between a and b with parameter t.
*/
private static float lerp(float a, float b, float indicator) {
if (indicator == HOME_AS_UP_INDICATOR) {
return b;
} else {
return a;
}
}
}
CustomDrawerArrowDrawable's
implementation has been borrowed from AOSP, and stripped down to allow drawing of only two states: homeAsUp & hamburger. You can toggle between these states by calling setIndicator(float)
. We use this in the Delegate
we implemented. Moreover, using CustomDrawerArrowDrawable
will allow you to style it in xml: barSize
, color
etc. Even though you don't need this, the implementation above lets you provide custom animations for drawer opening and closing .
I honestly don't know if I should recommend this.
If you call ActionBarDrawerToggle#setHomeAsUpIndicator(...)
with argument null
, it should pick the drawable defined in your theme:
<item name="android:homeAsUpIndicator">@drawable/some_back_drawable</item>
Currently, this does not happen because of a possible bug in ToolbarCompatDelegate#getThemeUpIndicator()
:
@Override
public Drawable getThemeUpIndicator() {
final TypedArray a = mToolbar.getContext()
// Should be new int[]{android.R.attr.homeAsUpIndicator}
.obtainStyledAttributes(new int[]{android.R.id.home});
final Drawable result = a.getDrawable(0);
a.recycle();
return result;
}
Bug report that loosely discusses this (read Case 4): Link
If you decide to stick with the solution you already have, please consider using CustomDrawerArrowDrawable
in place of pngs(R.drawable.lib_ic_arrow_back_light & R.drawable.lib_ic_menu_light). You won't be needing multiple drawables for density/size buckets and styling would be done in xml. Also, the final product will be the same as the framework's.
mDrawerToggle.setDrawerIndicatorEnabled(false);
CustomDrawerArrowDrawable toolbarNavigationIcon
= new CustomDrawerArrowDrawable(this, mToolbar.getContext());
if (showHomeAsUp) {
toolbarNavigationIcon.setIndicator(
CustomDrawerArrowDrawable.HOME_AS_UP_INDICATOR);
mDrawerToggle.setToolbarNavigationClickListener(view -> finish());
} else {
mToolbarNavigationIcon.setIndicator(
CustomDrawerArrowDrawable.DRAWER_INDICATOR);
mDrawerToggle.setToolbarNavigationClickListener(view -> toggleDrawer());
}
mDrawerToggle.setHomeAsUpIndicator(toolbarNavigationIcon);
This is my function for controlling the ActionBarDrawableToggle located in the NavigationDrawerFragment, which I call in the onActivityCreated callback of every fragment. post functions are necessary. The hamburger icon changes into the back arrow and the back arrow is clickable. Orientation changes are properly handled by the handlers.
...
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBarDrawerToggle;
...
public class NavigationDrawerFragment extends Fragment
{
private ActionBarDrawerToggle mDrawerToggle;
...
public void syncDrawerState()
{
new Handler().post(new Runnable()
{
@Override
public void run()
{
final ActionBar actionBar = activity.getSupportActionBar();
if (activity.getSupportFragmentManager().getBackStackEntryCount() > 1 && (actionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != ActionBar.DISPLAY_HOME_AS_UP)
{
new Handler().post(new Runnable()
{
@Override
public void run()
{
mDrawerToggle.setDrawerIndicatorEnabled(false);
actionBar.setDisplayHomeAsUpEnabled(true);
mDrawerToggle.setToolbarNavigationClickListener(onToolbarNavigationClickListener());
}
});
} else if (activity.getSupportFragmentManager().getBackStackEntryCount() <= 1 && (actionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) == ActionBar.DISPLAY_HOME_AS_UP)
{
actionBar.setHomeButtonEnabled(false);
actionBar.setDisplayHomeAsUpEnabled(false);
mDrawerToggle.setDrawerIndicatorEnabled(true);
mDrawerToggle.syncState();
}
}
});
}
}
This is just my onActivityCreated method in my base fragment.
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
navigationDrawerFragment.syncDrawerState();
}
Now there is dedicated method to disable the animation: toggle.setDrawerSlideAnimationEnabled(false)
Here's a snippet I use:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
[...]
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
toggle.setDrawerSlideAnimationEnabled(false);
drawer.addDrawerListener(toggle);
toggle.syncState();
}
Disabling the supper call in onDrawerSlide()
method will stop the animation between Arrow and Burger. You will only see the switching (without animation) when drawer is fully open or fully closed.
mActionBarDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.open, R.string.closed) {
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
//super.onDrawerSlide(drawerView, slideOffset);
}
};
mDrawerLayout.setDrawerListener(mActionBarDrawerToggle);
If you don't want the animation, don't use ActionBarDrawerToggle
. Use the code below instead.
toolbar.setNavigationIcon(R.drawable.ic_menu);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
drawer.openDrawer(GravityCompat.START);
}
});
To remove the hamberger menu animation, you can do like:
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, mDrawer, mToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
toggle.setDrawerSlideAnimationEnabled(false);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.