web-dev-qa-db-fra.com

Désactiver hamburger pour revenir l'animation de la flèche dans la barre d'outils

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?

29
tomrozb

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.

47
Frank

A mon avis, cette animation est inutile

ActionBarDrawerToggle est destiné à être animé. 

De la documentation:

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. 

12
adneal

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();
}
4
r1m

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);
3
Vikram

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();
}
3
Kamil Seweryn

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);
1
Nikola Despotoski

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);
                }
            });
1
Mark Buikema