【问题标题】:Add badge counter to hamburger navigation menu icon in Android将徽章计数器添加到 Android 中的汉堡导航菜单图标
【发布时间】:2017-05-09 23:01:41
【问题描述】:

我的问题与this question 相同(不是与this question 重复)。

该问题的only answer 对我不起作用,因为它没有将默认汉堡图标更改为活动标题的左侧,它只是添加了一个我的活动标题右侧的附加汉堡图标。

那么我该如何得到这个:

我整天都在找它,但一无所获。

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

注意 - 这不是关于如何创建徽章视图的问题。我已经创建了它并在导航菜单项本身上实现了它。所以我现在只需要为默认的汉堡图标添加一个类似的徽章视图。

【问题讨论】:

    标签: android android-toolbar badge navigation-drawer android-navigationview


    【解决方案1】:

    从支持库的 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;
            }
        }
    }
    

    这样,自定义徽章drawable将自动设置,所有与切换相关的内容都可以通过单个对象进行管理。

    BadgeDrawerToggle 是ActionBarDrawerToggle 的直接替代品,其构造函数完全相同。

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

    【讨论】:

    • 这在 2019 年迁移到 AndroidX 后仍然有效,但不要忘记在您的 Proguard 配置中添加以下内容:-keep class androidx.appcompat.app.ActionBarDrawerToggle { *; }-keep class androidx.appcompat.app.ActionBarDrawerToggle$Delegate { *; }
    • 是否可以在onSupportNavigateUp(): Boolean 仍然被调用的情况下完成这项工作?
    • @AndroidKotlinNoob 不清楚你在问什么,因为这个解决方案对 AppCompatActivity 方法没有任何作用。如果您实际上是在询问 Jetpack Navigation 组件,那么这确实应该是一个单独的帖子,但我会提到上次我检查时,这是不可能的,因为 Navigation 使用的 ActionBarDrawerToggle 无法访问,IIRC。不过,从那以后情况可能发生了变化。我自己不使用导航。
    • 我试过实现这个,它不再调用AppCompatActivity函数。
    • @AndroidKotlinNoob 抱歉,我真的不明白这怎么可能。这基本上只是在ImageButton 上设置一个drawable。在过去的大约 4.5 年里没有人提到过这样的问题,所以我不得不猜测你的问题是特定于你的设置的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-06
    • 2018-10-03
    • 2018-02-16
    相关资源
    最近更新 更多