Il est très facile d'implémenter Toolbar
avec hamburger pour animer une flèche. À mon avis, cette animation est inutile parce que, conformément à la conception du matériau, le tiroir de navigation couvre la Toolbar
lorsqu’il est ouvert. Ma question est de savoir comment désactiver correctement cette animation et afficher soit hamburger ou flèche arrière en utilisant getSupportActionBar().setDisplayHomeAsUpEnabled(true);
C'est comme ça que je l'ai fait, mais ça ressemble à un sale bidouille:
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());
}
Des indices sur la manière dont cela devrait être correctement implémenté pour utiliser uniquement setDisplayHomeAsUpEnabled
pour basculer entre les icônes hamburger et flèche retour?
Ceci désactivera l'animation. Lors de la création du tiroirToggle, remplacez 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
}
};
Si vous souhaitez supprimer complètement la flèche, vous pouvez ajouter
super.onDrawerSlide(drawerView, 0); // this disables the arrow @ completed state
à la fin de la fonction onDrawerOpened.
A mon avis, cette animation est inutile
ActionBarDrawerToggle
est destiné à être animé.
Vous pouvez personnaliser la bascule animée en définissant le drawArrowStyle dans votre thème ActionBar.
Tous les indices sur la manière dont cela devrait être correctement implémenté pour une utilisation juste setDisplayHomeAsUpEnabled pour basculer entre hamburger et flèche retour des icônes?
La ActionBarDrawerToggle
n’est qu’un moyen élégant d’appeler ActionBar.setHomeAsUpIndicator
. Donc, de toute façon, vous devrez appeler ActionBar.setDisplayHomeAsUpEnabled
à true
pour pouvoir l'afficher.
Si vous êtes convaincu que vous devez l’utiliser, nous vous conseillons d’appeler respectivement ActionBarDrawerToggle.onDrawerOpened(View drawerView)
_ et ActionBarDrawerToggle.onDrawerClosed(View drawerView)
.
Ceci positionnera la position DrawerIndicator
sur 1
ou 0
, en basculant entre les états flèche et hamburger du DrawerArrowDrawable
.
Et dans votre cas, il n'est même pas nécessaire d'attacher une ActionBarDrawerToggle
en tant que DrawerLayout.DrawerListener
. Un péché:
mYourDrawer.setDrawerListener(mYourDrawerToggle);
Mais une approche beaucoup plus avancée consisterait à appeler ActionBar.setHomeAsUpIndicator
une fois et à appliquer votre propre icône de hamburger. Vous pouvez également le faire via un style. Ensuite, lorsque vous souhaitez afficher la flèche de retour, appelez simplement ActionBar.setDisplayHomeAsUpEnabled
et laissez AppCompat ou le framework gérer le reste. D'après les commentaires que vous avez faits, je suis presque certain que c'est ce que vous recherchez.
Si vous ne savez pas quelle icône utiliser, la taille par défaut de DrawerArrowDrawable
est 24dp
, ce qui signifie que vous souhaitez récupérer le ic_menu_white_24dp
ou le ic_menu_black_24dp
à partir du ensemble d'icônes de navigation dans la documentation officielle de Google. pack d'icônes de conception.
Vous pouvez également copier la DrawerArrowDrawable
dans votre projet et laisser ensuite basculer les états flèche ou hamburger selon vos besoins. C'est autonome, moins quelques ressources.
Ceci est ma fonction pour contrôler ActionBarDrawableToggle situé dans le NavigationDrawerFragment, que j'appelle dans le rappel onActivityCreated de chaque fragment. les fonctions de post sont nécessaires. L'icône de hamburger se transforme en flèche vers l'arrière et la flèche vers l'arrière est cliquable Les changements d'orientation sont correctement gérés par les gestionnaires.
...
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();
}
}
});
}
}
Ceci est juste ma méthode onActivityCreated dans mon fragment de base.
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
navigationDrawerFragment.syncDrawerState();
}
J'avais une exigence similaire et j'ai passé du temps à parcourir le code ActionBarDrawerToggle
. Ce que vous avez actuellement est la meilleure voie à suivre.
Plus à venir:
L'animation de hamburger à flèche est fournie par une implémentation extractible - DrawerArrowDrawableToggle
. Actuellement, nous n’avons pas beaucoup de contrôle sur la façon dont ce dessinable réagit aux états des tiroirs. Voici ce que dit le constructeur d'accès de package pour actionVarDrawerToggle
:
/**
* 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)
En fournissant votre propre implémentation de slider
, vous pouvez contrôler sa réaction aux états des tiroirs. L'interface que slider
doit implémenter:
/**
* Interface for toggle drawables. Can be public in the future
*/
static interface DrawerToggle {
public void setPosition(float position);
public float getPosition();
}
setPosition(float)
est le point culminant ici - tous les changements d'état du tiroir l'appellent pour mettre à jour le voyant du tiroir.
Pour le comportement que vous souhaitez, la fonction setPosition(float position)
de votre implémentation slider
ne ferait rien.
Vous aurez encore besoin de:
if (showHomeAsUp) {
mDrawerToggle.setDrawerIndicatorEnabled(false);
// Can be set in theme
mDrawerToggle.setHomeAsUpIndicator(R.drawable.lib_ic_arrow_back_light);
mDrawerToggle.setToolbarNavigationClickListener(view -> finish());
}
Si vous n'avez pas setDrawerIndicatorEnabled(false)
, la OnClickListener
que vous avez définie avec setToolbarNavigationClickListener(view -> finish());
ne sera pas déclenchée.
Que pouvons-nous faire correctement maintenant?
En regardant de plus près, je trouve qu’il existe une disposition pour votre besoin dans ActionBarDrawerToggle
. Je trouve cette disposition encore plus difficile que ce que vous avez actuellement. Mais je vous laisse décider.
ActionBarDrawerToggle
vous permet de contrôler certains l'indicateur du tiroir via l'interface Delegate . Vous pouvez faire en sorte que votre activité implémente cette interface de la manière suivante:
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
utilisera setActionBarUpIndicator(Drawable, int)
fourni ici. Puisque nous ignorons que la Drawable
est passée, nous avons le plein contrôle sur ce qui sera affiché.
Catch: ActionBarDrawerToggle
laissera notre Activity
agir en tant que délégué si nous passons le paramètre Toolbar
à null ici:
public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout,
Toolbar toolbar, @StringRes int openDrawerContentDescRes,
@StringRes int closeDrawerContentDescRes) { .... }
Et, vous devrez remplacer getV7DrawerToggleDelegate()
dans votre activité:
@Nullable
@Override
public ActionBarDrawerToggle.Delegate getV7DrawerToggleDelegate() {
return this;
}
Comme vous pouvez le constater, s’y prendre à bon escient représente beaucoup de travail supplémentaire. Et nous n'avons pas encore fini.
La variable DrawerArrowDrawableToggle
animée peut être stylée à l’aide de ces attributs . Si vous voulez que vos états puissent être tirés (homeAsUp et hamburger) exactement comme les valeurs par défaut, vous devrez l'implémenter comme suit:
/**
* 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;
}
}
}
L’implémentation de CustomDrawerArrowDrawable's
a été empruntée à AOSP et simplifiée pour permettre l’attribution de deux états seulement: homeAsUp & hamburger. Vous pouvez basculer entre ces états en appelant setIndicator(float)
. Nous l'utilisons dans la Delegate
que nous avons implémentée. De plus, utiliser CustomDrawerArrowDrawable
vous permettra de le styler en xml: barSize
, color
etc. Même si vous n'en avez pas besoin, l'implémentation ci-dessus vous permet de fournir des animations personnalisées pour l'ouverture et la fermeture du tiroir.
Honnêtement, je ne sais pas si je devrais le recommander.
Si vous appelez ActionBarDrawerToggle#setHomeAsUpIndicator(...)
avec l'argument null
, choisissez le dessinable défini dans votre thème:
<item name="Android:homeAsUpIndicator">@drawable/some_back_drawable</item>
Actuellement, cela ne se produit pas à cause d'un bogue possible dans 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;
}
Rapport de bogue qui en parle librement (lire le cas 4): Lien
Si vous décidez de rester avec la solution que vous avez déjà, envisagez d'utiliser CustomDrawerArrowDrawable
à la place de pngs (R.drawable.lib_ic_arrow_back_light & R.drawable.lib_ic_menu_light). Vous n'aurez pas besoin de plusieurs éléments dessinables pour les compartiments densité/taille et le style se fera au format xml. En outre, le produit final sera le même que celui du cadre.
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);
Il existe maintenant une méthode dédiée pour désactiver l'animation: toggle.setDrawerSlideAnimationEnabled(false)
Voici un extrait que j'utilise:
@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();
}
La désactivation de l'appel de souper dans la méthode onDrawerSlide()
arrêtera l'animation entre Arrow et Burger. Vous ne verrez la commutation (sans animation) que lorsque le tiroir est complètement ouvert ou complètement fermé.
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);
Si vous ne voulez pas l'animation, n'utilisez pas ActionBarDrawerToggle
. Utilisez le code ci-dessous à la place.
toolbar.setNavigationIcon(R.drawable.ic_menu);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
drawer.openDrawer(GravityCompat.START);
}
});