在Android中为汉堡导航菜单图标添加徽章计数器

25
我的问题与这个问题相同(不是这个问题的重复)。那个问题的唯一答案对我没有用,因为它并没有将默认汉堡图标更改为活动标题的左边,而是只在活动标题右边添加了一个额外的汉堡图标。

那么我该如何实现以下内容:

Android hamburger icon with badge counter

我整天都在钻研它,但却一无所获。

我看到Toolbar有一个setNavigationIcon(Drawable drawable)方法。理想情况下,我想使用一个包含汉堡图标和徽章视图的layout,而不是一个Drawable,但我不确定是否可行 - 或者是否有更好的方法?

NB- 这不是关于如何创建徽章视图的问题。我已经创建了它,并已将其实现在导航菜单项上。所以现在我只需要向默认汉堡图标添加类似的徽章视图。

1个回答

81
自从支持库24.2.0版本以来,ActionBarDrawerToggle的v7版本提供了setDrawerArrowDrawable()方法,以自定义切换图标。 DrawerArrowDrawable是提供默认图标的类,可以对其进行子类化以根据需要进行修改。
例如,BadgeDrawerArrowDrawable类覆盖draw()方法以在超类绘制自身后添加基本红色和白色徽章。这允许汉堡箭头动画在下面被保留。
import android.content.Context;
import android.graphics.Color;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.support.v7.graphics.drawable.DrawerArrowDrawable;
import java.util.Objects;

public class BadgeDrawerArrowDrawable extends DrawerArrowDrawable {

    // Fraction of the drawable's intrinsic size we want the badge to be.
    private static final float SIZE_FACTOR = .3f;
    private static final float HALF_SIZE_FACTOR = SIZE_FACTOR / 2;

    private Paint backgroundPaint;
    private Paint textPaint;
    private String text;
    private boolean enabled = true;

    public BadgeDrawerArrowDrawable(Context context) {
        super(context);

        backgroundPaint = new Paint();
        backgroundPaint.setColor(Color.RED);
        backgroundPaint.setAntiAlias(true);

        textPaint = new Paint();
        textPaint.setColor(Color.WHITE);
        textPaint.setAntiAlias(true);
        textPaint.setTypeface(Typeface.DEFAULT_BOLD);
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setTextSize(SIZE_FACTOR * getIntrinsicHeight());
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        if (!enabled) {
            return;
        }

        final Rect bounds = getBounds();
        final float x = (1 - HALF_SIZE_FACTOR) * bounds.width();
        final float y = HALF_SIZE_FACTOR * bounds.height();
        canvas.drawCircle(x, y, SIZE_FACTOR * bounds.width(), backgroundPaint);

        if (text == null || text.length() == 0) {
            return;
        }

        final Rect textBounds = new Rect();
        textPaint.getTextBounds(text, 0, text.length(), textBounds);
        canvas.drawText(text, x, y + textBounds.height() / 2, textPaint);
    }

    public void setEnabled(boolean enabled) {
        if (this.enabled != enabled) {
            this.enabled = enabled;
            invalidateSelf();
        }
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setText(String text) {
        if (!Objects.equals(this.text, text)) {
            this.text = text;
            invalidateSelf();
        }
    }

    public String getText() {
        return text;
    }

    public void setBackgroundColor(int color) {
        if (backgroundPaint.getColor() != color) {
            backgroundPaint.setColor(color);
            invalidateSelf();
        }
    }

    public int getBackgroundColor() {
        return backgroundPaint.getColor();
    }

    public void setTextColor(int color) {
        if (textPaint.getColor() != color) {
            textPaint.setColor(color);
            invalidateSelf();
        }
    }

    public int getTextColor() {
        return textPaint.getColor();
    }
}

任何时候在切换上设置此实例,可以直接在可绘制对象上设置徽章属性。如下所示,正如下面的 OP 所指出的那样,用于自定义 DrawerArrowDrawable 的 Context 应使用 ActionBar#getThemedContext() 或 Toolbar#getContext() 获取,以确保使用正确的样式值。
private ActionBarDrawerToggle toggle;
private BadgeDrawerArrowDrawable badgeDrawable;
...

toggle = new ActionBarDrawerToggle(this, ...);
badgeDrawable = new BadgeDrawerArrowDrawable(getSupportActionBar().getThemedContext());

toggle.setDrawerArrowDrawable(badgeDrawable);
badgeDrawable.setText("1");
...

屏幕截图


为了简化事物,最好也可以通过子类化ActionBarDrawerToggle并通过切换实例处理所有内容。

import android.app.Activity;
import android.content.Context;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.Toolbar;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class BadgeDrawerToggle extends ActionBarDrawerToggle {

    private BadgeDrawerArrowDrawable badgeDrawable;

    public BadgeDrawerToggle(Activity activity, DrawerLayout drawerLayout,
                             int openDrawerContentDescRes,
                             int closeDrawerContentDescRes) {
        super(activity, drawerLayout, openDrawerContentDescRes,
              closeDrawerContentDescRes);
        init(activity);
    }

    public BadgeDrawerToggle(Activity activity, DrawerLayout drawerLayout,
                             Toolbar toolbar, int openDrawerContentDescRes,
                             int closeDrawerContentDescRes) {
        super(activity, drawerLayout, toolbar, openDrawerContentDescRes,
              closeDrawerContentDescRes);
        init(activity);
    }

    private void init(Activity activity) {
        Context c = getThemedContext();
        if (c == null) {
            c = activity;
        }
        badgeDrawable = new BadgeDrawerArrowDrawable(c);
        setDrawerArrowDrawable(badgeDrawable);
    }

    public void setBadgeEnabled(boolean enabled) {
        badgeDrawable.setEnabled(enabled);
    }

    public boolean isBadgeEnabled() {
        return badgeDrawable.isEnabled();
    }

    public void setBadgeText(String text) {
        badgeDrawable.setText(text);
    }

    public String getBadgeText() {
        return badgeDrawable.getText();
    }

    public void setBadgeColor(int color) {
        badgeDrawable.setBackgroundColor(color);
    }

    public int getBadgeColor() {
        return badgeDrawable.getBackgroundColor();
    }

    public void setBadgeTextColor(int color) {
        badgeDrawable.setTextColor(color);
    }

    public int getBadgeTextColor() {
        return badgeDrawable.getTextColor();
    }

    private Context getThemedContext() {
        // Don't freak about the reflection. ActionBarDrawerToggle
        // itself is already using reflection internally.
        try {
            Field mActivityImplField = ActionBarDrawerToggle.class
                .getDeclaredField("mActivityImpl");
            mActivityImplField.setAccessible(true);
            Object mActivityImpl = mActivityImplField.get(this);
            Method getActionBarThemedContextMethod = mActivityImpl.getClass()
                .getDeclaredMethod("getActionBarThemedContext");
            return (Context) getActionBarThemedContextMethod.invoke(mActivityImpl);
        }
        catch (Exception e) {
            return null;
        }
    }
}

有了这个,自定义徽章图标将会自动设置,所有与切换相关的内容都可以通过一个对象来管理。

BadgeDrawerToggleActionBarDrawerToggle 的即插即用替代品,其构造函数完全相同。

private BadgeDrawerToggle badgeToggle;
...

badgeToggle = new BadgeDrawerToggle(this, ...);
badgeToggle.setBadgeText("1");
...

2
在迁移到AndroidX后,这个功能在2019年仍然可用,但不要忘记在您的Proguard配置中添加以下内容:-keep class androidx.appcompat.app.ActionBarDrawerToggle { *; }-keep class androidx.appcompat.app.ActionBarDrawerToggle$Delegate { *; } - Eric Sellin
这个能否在仍然调用 onSupportNavigateUp(): Boolean 的情况下实现? - AndroidKotlinNoob
@AndroidKotlinNoob 抱歉,但我真的不明白这怎么可能。这基本上只是在“ImageButton”上设置一个可绘制对象。在过去的约4.5年中,没有人提到过这样的问题,所以我猜测你的问题可能是特定于你的设置。 - Mike M.
1
@NimaAzhd 这可能不是针对这个解决方案的特定问题。我没有使用过它,但据我所知,导航组件需要自己处理切换,所以注入自己的行为可能不会完全正常工作,这并不奇怪。我甚至不确定你如何获得切换按钮,因为据我上次检查时它不是公开可访问的。如果你的意思是创建并设置自己的切换按钮,我相当确信你将不得不自行处理同步。 - Mike M.
1
@MikeM,你说得对。这种方法可能不适用于多片段架构和一个活动和导航组件。不幸的是,本地BadgeDrawable库仅支持menuItemId。 我想到的解决方案是:只为主页面的片段添加徽章值,而在其余内部片段中则不添加。我没有在切换上做任何额外的工作。除了这里写的代码之外。此外,我不使用操作栏,而是使用工具栏 "mDrawerToggle?.setDrawerArrowDrawable(BadgeDrawerToggle!!)",BadgeDrawerToggle与你的相同。 - NimaAzhd
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接