禁用汉堡包到工具栏上的后退箭头动画

Posted

技术标签:

【中文标题】禁用汉堡包到工具栏上的后退箭头动画【英文标题】:Disable hamburger to back arrow animation on Toolbar 【发布时间】:2015-01-22 21:32:27 【问题描述】:

用汉堡包到返回箭头动画很容易实现Toolbar。在我看来,这个动画毫无意义,因为根据材料设计规范,导航抽屉在打开时会覆盖Toolbar。我的问题是如何正确禁用此动画并使用 getSupportActionBar().setDisplayHomeAsUpEnabled(true); 显示汉堡包或后退箭头

我就是这样做的,但它看起来像一个肮脏的黑客:

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());

任何线索应该如何正确实施以仅使用 setDisplayHomeAsUpEnabled 在汉堡包和后退箭头图标之间切换?

【问题讨论】:

【参考方案1】:

这将禁用动画,创建drawerToggle时,覆盖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 
    
;

如果要完全去掉箭头,可以加

 super.onDrawerSlide(drawerView, 0); // this disables the arrow @ completed state

在 onDrawerOpened 函数的末尾。

【讨论】:

这对我禁用动画很有用。但是,setDisplayHomeAsUpEnabled 不再起作用。 Ik 一直在展示汉堡包。幸运的是,我不需要后退箭头。 遗憾的是,这可能是真的。我只在根本不使用汉堡包的地方使用 homeasup。 您也可以通过在 onDrawerOpened 函数末尾添加 super.onDrawerOpened(drawerView); 而不是添加 super.onDrawerSlide(drawerView, 0); 来完全删除箭头。【参考方案2】:

在我看来这个动画毫无意义

嗯,ActionBarDrawerToggle 应该是动画的。

From the docs:

您可以通过定义 animated 开关来自定义 您的 ActionBar 主题中的drawerArrowStyle。

任何线索应该如何正确实施以仅使用 setDisplayHomeAsUpEnabled 在汉堡包和后退箭头之间切换 图标?

ActionBarDrawerToggle 只是调用ActionBar.setHomeAsUpIndicator 的一种奇特方式。因此,无论哪种方式,您都必须调用 ActionBar.setDisplayHomeAsUpEnabledtrue 才能显示它。

如果您确信必须使用它,那么我建议您分别致电ActionBarDrawerToggle.onDrawerOpened(View drawerView)ActionBarDrawerToggle.onDrawerClosed(View drawerView)

这会将DrawerIndicator 位置设置为10,在DrawerArrowDrawable 的箭头和汉堡包状态之间切换。

在您的情况下,甚至不需要将ActionBarDrawerToggle 附加为DrawerLayout.DrawerListener。如:

mYourDrawer.setDrawerListener(mYourDrawerToggle);

但更先进的方法是调用一次ActionBar.setHomeAsUpIndicator 并应用您自己的汉堡包图标,您也可以通过样式来做到这一点。然后当你想显示后退箭头时,只需调用ActionBar.setDisplayHomeAsUpEnabled 并让 AppCompat 或框架处理其余部分。从你制作的 cmets 来看,我很确定这就是你要找的。​​p>

如果您不确定要使用哪个图标,the default DrawerArrowDrawable size is 24dp,这意味着您需要从 Google 官方 Material Design 图标包中的 navigation icon set 中获取 ic_menu_white_24dpic_menu_black_24dp

您还可以将DrawerArrowDrawable 复制到您的项目中,然后根据需要切换箭头或汉堡包状态。它是独立的,减去一些资源。

【讨论】:

也许 ActionBarDrawerToggle 应该是动画的,但是如果你看一下谷歌的应用程序,它遵循材料设计规则,这个动画是禁用的。我需要抽屉监听器,因为当抽屉打开/关闭时,动作按钮的可见性必须改变。我已经在使用 ic_menu_white_24dp 资源(只是名称不同)。如果您设置自己的资源图标,则 setDisplayHomeAsUpEnabled 方法将不再起作用。要显示后退箭头,我必须将图标交换为 lib_ic_arrow_back_light。抱歉,但在阅读了您的答案后,我仍然不知道哪些参数设置为 true 或 false 谢谢。我实际上更喜欢答案的第一个建议,因为我不必提供可能与未来框架样式不匹配的自定义资源。 @tomrozb 哦,我明白了。事实是,你必须调用ActionBar.setHomeAsUpIndicator 来显示你想要的图标,所以要么换掉汉堡包和后退箭头资源,复制DrawerArrowDrawable 并让它为你绘制它们,或者调用ActionBarDrawerToggle.onDrawerOpened,等等就像我提到的。您是在暗示 Google 的应用实际上使用了ActionBarDrawerToggle,但事实可能并非如此。 @adneal 你说得对,谷歌可能根本不使用切换。如果我找到一些空闲时间,我会尝试你的解决方案。 Jeff Gilfelt - ActionBarDrawerToggle 的后退箭头与资源中的箭头不同,仅供参考。如果您使用没有切换的活动,请记住后退箭头会有所不同。【参考方案3】:

我有类似的要求,并花了一些时间查看ActionBarDrawerToggle 代码。你目前拥有的是最好的前进方式。

更多内容:

汉堡到箭头的动画由可绘制实现提供 - DrawerArrowDrawableToggle。目前,我们对这个可绘制对象如何响应抽屉状态没有太多控制。 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)

通过提供您自己的slider 实现,您可以控制它对抽屉状态的反应方式。 slider必须实现的接口:

/**
 * Interface for toggle drawables. Can be public in the future
 */
static interface DrawerToggle 

    public void setPosition(float position);

    public float getPosition();

setPosition(float) 是这里的亮点 - 所有抽屉状态更改都会调用它来更新抽屉指示器。

对于您想要的行为,您的 slider 实现的 setPosition(float position) 不会做任何事情。

你仍然需要:

if (showHomeAsUp) 
    mDrawerToggle.setDrawerIndicatorEnabled(false);
    // Can be set in theme
    mDrawerToggle.setHomeAsUpIndicator(R.drawable.lib_ic_arrow_back_light);
    mDrawerToggle.setToolbarNavigationClickListener(view -> finish());

如果你不setDrawerIndicatorEnabled(false),你用setToolbarNavigationClickListener(view -&gt; finish());设置的OnClickListener不会触发。

我们现在可以做些什么

经过仔细检查,我发现ActionBarDrawerToggle 中有满足您的要求的规定。我发现这个条款比你现在拥有的更多。但是,我会让你决定。

ActionBarDrawerToggle 允许您通过接口Delegate 对抽屉指示器进行一些控制。您可以通过以下方式让您的活动实现此接口:

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 将使用此处提供的setActionBarUpIndicator(Drawable, int)。由于我们忽略了正在传递的Drawable,因此我们可以完全控制将显示的内容。

Catch:如果我们在此处将 Toolbar 参数作为 null 传递,ActionBarDrawerToggle 将让我们的 Activity 充当代理:

public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout,
        Toolbar toolbar, @StringRes int openDrawerContentDescRes,
        @StringRes int closeDrawerContentDescRes)  .... 

而且,您需要在活动中覆盖 getV7DrawerToggleDelegate()

@Nullable
@Override
public ActionBarDrawerToggle.Delegate getV7DrawerToggleDelegate() 
    return this;

如您所见,走正确的路需要做很多额外的工作。我们还没有完成。

动画DrawerArrowDrawableToggle 可以使用these attributes 设置样式。如果您希望您的可绘制状态(homeAsUp & hamburger)完全像默认值一样,您需要这样实现它:

/**
 * 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 的实现是从 AOSP 借来的,并被精简为只允许绘制两个状态:homeAsUp 和 hamburger。您可以通过调用setIndicator(float) 在这些状态之间切换。我们在我们实现的Delegate 中使用了它。此外,使用CustomDrawerArrowDrawable 将允许您在xml 中设置样式:barSizecolor 等。即使您不需要它,上面的实现让您为抽屉打开和关闭提供自定义动画。

我真的不知道我是否应该推荐这个。


如果您使用参数null 调用ActionBarDrawerToggle#setHomeAsUpIndicator(...),它应该选择在您的主题中定义的drawable:

<item name="android:homeAsUpIndicator">@drawable/some_back_drawable</item>

目前,这不会发生,因为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;

松散讨论此问题的错误报告(阅读案例 4):Link


如果您决定坚持使用已有的解决方案,请考虑使用CustomDrawerArrowDrawable 代替 pngs(R.drawable.lib_ic_arrow_back_light & R.drawable.lib_ic_menu_light)。您将不需要用于密度/大小桶的多个可绘制对象,并且样式将在 xml 中完成。此外,最终产品将与框架的相同。

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

【讨论】:

我也花了一些时间,发现这是唯一但丑陋的解决方案。 “可以在主题中设置”是什么意思?您想使用自定义 attrs 还是一些内置样式?请记住,我需要能够在两个不同的可绘制对象之间切换。我们将等待一段时间来接受答案,也许有人找到了更好的方法。 @tomrozb 您在主题中设置的属性是android:homeAsUpIndicator。之后,您可以调用mDrawerToggle.setHomeAsUpIndicator(null) 并使用xml 中给出的drawable。由于可能的错误,目前不会发生这种情况。我已经用更多信息更新了我的答案,并添加了错误报告的链接。【参考方案4】:

这是我用于控制位于 NavigationDrawerFragment 中的 ActionBarDrawableToggle 的函数,我在每个片段的 onActivityCreated 回调中调用该函数。岗位职能是必要的。汉堡图标变为后退箭头,后退箭头可点击。处理程序正确处理方向更改。

...

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();
                
            
        );      
    

这只是我的基础片段中的 onActivityCreated 方法。

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState)

    super.onActivityCreated(savedInstanceState);
    navigationDrawerFragment.syncDrawerState();

【讨论】:

天啊...我在 2 小时后发现了这个答案。我不确定你是如何使用它的,但我在 OnBackStackChangedListener 的主要活动中实现了它,一切都开始工作了。对于在主要活动中处理片段堆栈的每个人:pastebin.com/YqKBTiXa【参考方案5】:

现在有专门的方法来禁用动画:toggle.setDrawerSlideAnimationEnabled(false)

这是我使用的一个 sn-p:

@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();

【讨论】:

【参考方案6】:

onDrawerSlide() 方法中禁用晚餐调用将停止箭头和汉堡之间的动画。只有抽屉完全打开或完全关闭时,您才会看到切换(无动画)。

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

【讨论】:

我试过这样,但屏幕旋转后后退箭头可见。打开抽屉,旋转屏幕,关闭抽屉 - 将显示一个后退箭头而不是汉堡包。 我无法复制。似乎其他代码与此案例有关。 我在 onPostCreate 中调用 mDrawerToggle.syncState() 并在 onDrawerOpened/Closed 中调用 invalidateOptionsMenu()。你也调用这些方法吗? 没有 mDrawerToggle.syncState() 汉堡包图标甚至不显示,invalidateOptionsMenu 没有任何改变。能否请您发布整个代码(或 GitHub 上的示例示例项目)。 @tomrozb 正如所承诺的,这里是布局 pastebin.com/7PTsbN5D 和 MainActivity pastebin.com/4r2vRSHV【参考方案7】:

如果您不想要动画,请不要使用ActionBarDrawerToggle。请改用下面的代码。

toolbar.setNavigationIcon(R.drawable.ic_menu);
toolbar.setNavigationOnClickListener(new View.OnClickListener() 
                @Override
                public void onClick(View view) 
                    drawer.openDrawer(GravityCompat.START);
                
            );

【讨论】:

请阅读问题。它说:“我的问题是如何正确禁用此动画并使用 getSupportActionBar().setDisplayHomeAsUpEnabled(true); 显示汉堡包或后退箭头;” 您可以使用 Toolbar.setNavigationIcon() 和来自google/material-design-icons的图标设置汉堡包或后退箭头图标【参考方案8】:

要删除汉堡菜单动画,您可以这样做:

 ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, mDrawer,  mToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);

 toggle.setDrawerSlideAnimationEnabled(false); 

【讨论】:

以上是关于禁用汉堡包到工具栏上的后退箭头动画的主要内容,如果未能解决你的问题,请参考以下文章

工具栏导航汉堡图标丢失

使用导航组件处理工具栏后退按钮

如何在使用Android Jetpack导航时禁用导航图标

汉堡菜单旋转到新活动的箭头

ActionBar在发布版本中缺少后退箭头,但没有调试版本

发布版本时 ActionBar 缺少后退箭头,但调试版本没有