当前位置 博文首页 > 文章内容

    Android仿QQ微信未读消息小红点BadgeHelper

    作者:shunshunshun18 栏目:未分类 时间:2021-09-09 14:43:19

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    Android 小红点 未读消息功能 BadgeHelper

    因为最近的项目需求,翻遍github上的未读消息红点开源库, 发现大部分
    不能适配不同情况的布局, 所以我写了一个能兼容全部的 !
    网上的写法是 继承TextView然后生成一个小红点drawable,设置到背景中去, 然后把目标view外层加一层FrameLayout,然后把小红点添加进去
    但这样做的问题来了, 小红点与目标View 会叠起来!, 挡住文字,!!! 看得我瞎了~~~ 而且 他们提供的setOffsetX setpadding 之类的没卵用,你如果想要偏移小红点让它不与下面的View重叠,那是不可能的
    所以我的写法是 为了更好的性能,直接继承View然后画小红点背景, 然后把目标view外层加一层LinearLayout 让小红点View放目标的右边,这样就不会重叠
    同时 我也支持设置 重叠模式和非重叠模式

    这是效果图

    这里写图片描述

    由于github账号出问题了,没法上传, 所以写到博客上算了

    这只是一个小工具类 供大家学习下我的思路, 就不多说了, 具体实现注释里写的很清楚,不太清楚的可以回复问我

    #核心类:
    ##BadgeHelper .java

     
    package com.truescend.gofit.views;
    
    import android.content.Context;
    import android.content.res.Resources;
    import android.graphics.Canvas;
    import android.graphics.Paint;
    import android.graphics.Rect;
    import android.graphics.RectF;
    import android.support.annotation.IntDef;
    import android.support.design.widget.TabLayout;
    import android.util.Log;
    import android.util.TypedValue;
    import android.view.Gravity;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.FrameLayout;
    import android.widget.LinearLayout;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.reflect.Field;
    
    /**
     * 作者:东芝(2018/8/23).
     * 支持 重叠目标模式 和 放目标右上角但不重叠的模式 两种模式 通过setOverlap 设置,  默认为false=不重叠
     */
    
    public class BadgeHelper extends View {
        private static final String TAG = "BadgeHelper";
        private float density;
        private Paint mTextPaint;
        private Paint mBackgroundPaint;
        private String text = "0";
        private int number;
    
        @Type
        private int type = Type.TYPE_POINT;
        private boolean isOverlap;
        private final RectF rect = new RectF();
        private int badgeColor = 0xFFD3321B; //默认的小红点颜色
        private int textColor = 0xFFFFFFff;
        private float textSize;
        private int w;
        private int h;
        private boolean isSetup;
        private boolean mIgnoreTargetPadding;
        private boolean isCenterVertical;
        private int leftMargin;
        private int topMargin;
        private int rightMargin;
        private int bottomMargin;
    
        @IntDef({Type.TYPE_POINT, Type.TYPE_TEXT})
        @Retention(RetentionPolicy.SOURCE)
        public @interface Type {
            int TYPE_POINT = 0;
            int TYPE_TEXT = 1;
        }
    
    
        public BadgeHelper(Context context) {
            super(context);
    
        }
    
    
        private void init(@Type int type, boolean isOverlap) {
            this.type = type;
            this.isOverlap = isOverlap;
            density = getResources().getDisplayMetrics().density;
    
            switch (type) {
                case Type.TYPE_POINT:
                    mBackgroundPaint = new Paint();
                    mBackgroundPaint.setStyle(Paint.Style.FILL);
                    mBackgroundPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
                    mBackgroundPaint.setColor(badgeColor);
                    //计算小红点无文本情况下的小红点大小,  按屏幕像素计算, 如果你有你自己认为更好的算法, 改这里即可
                    w = h = Math.round(density * 7f);
                    break;
                case Type.TYPE_TEXT:
                    mBackgroundPaint = new Paint();
                    mBackgroundPaint.setStyle(Paint.Style.FILL);
                    mBackgroundPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
                    mBackgroundPaint.setColor(badgeColor);
    
                    mTextPaint = new Paint();
                    mTextPaint.setStyle(Paint.Style.FILL);
                    mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
                    mTextPaint.setColor(textColor);//文本颜色
                    if (textSize == 0) {
                        mTextPaint.setTextSize(density * 10);//文本大小按屏幕像素 计算, 没写死是为了适配各种屏幕,  但如果你有你认为更合理的计算方式 你可以改这里
                    } else {
                        mTextPaint.setTextSize(textSize);//使用自定义大小
                    }
    
                    //计算小红点有文本情况下的小红点大小,  按文本宽高计算, 如果你有你自己认为更好的算法, 改这里即可
                    float textWidth = getTextWidth("99", mTextPaint);
                    w = h = Math.round(textWidth * 1.4f);//让背景比文本大一点
                    break;
            }
        }
    
        /**
         * 设置Margin 可用于做偏移
         * @param left
         * @param top
         * @param right
         * @param bottom
         * @return
         */
        public BadgeHelper setBadgeMargins(int left, int top, int right, int bottom) {
            leftMargin = left;
            topMargin = top;
            rightMargin = right;
            bottomMargin = bottom;
            return this;
        }
        /**
         * 设置Gravity居中
    
         * @return
         */
        public BadgeHelper setBadgeCenterVertical(  ) {
            isCenterVertical = true;
            return this;
        }
    
        /**
         * 设置小红点类型
         *
         * @param type
         * @return
         */
        public BadgeHelper setBadgeType(@Type int type) {
            this.type = type;
            return this;
        }
    
        /**
         * 设置小红点大小, 默认自动适配
         *
         * @param textSize
         * @return
         */
        public BadgeHelper setBadgeTextSize(int textSize) {
            Context c = getContext();
            Resources r;
            if (c == null) {
                r = Resources.getSystem();
            } else {
                r = c.getResources();
            }
            this.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSize, r.getDisplayMetrics());
            return this;
        }
    
    
        /**
         * 设置小红点文字颜色, 默认白
         *
         * @param textColor
         * @return
         */
        public BadgeHelper setBadgeTextColor(int textColor) {
            this.textColor = textColor;
            return this;
        }
    
        /**
         * 设置重叠模式, 默认是false(不重叠)
         *
         * @param isOverlap 是否把小红点重叠到目标View之上
         * @return
         */
        public BadgeHelper setBadgeOverlap(boolean isOverlap) {
            return setBadgeOverlap(isOverlap, false);
        }
    
        /**
         * 设置重叠模式, 默认是false(不重叠)
         *
         * @param isOverlap             是否把小红点重叠到目标View之上
         * @param isIgnoreTargetPadding 是否忽略目标View的padding
         * @return
         */
        public BadgeHelper setBadgeOverlap(boolean isOverlap, boolean isIgnoreTargetPadding) {
            this.isOverlap = isOverlap;
            this.mIgnoreTargetPadding = isIgnoreTargetPadding;
            if (!isOverlap && isIgnoreTargetPadding) {
                Log.w(TAG, "警告:只有重叠模式isOverlap=true 设置mIgnoreTargetPadding才有意义");
            }
            return this;
        }
    
        /**
         * 设置小红点颜色
         *
         * @param mBadgeColor
         * @return
         */
        public BadgeHelper setBadgeColor(int mBadgeColor) {
            this.badgeColor = mBadgeColor;
            return this;
        }
    
        /**
         * 设置小红点大小
         *
         * @param w
         * @param h
         * @return
         */
        public BadgeHelper setBadgeSize(int w, int h) {
            this.w = w;
            this.h = h;
            return this;
        }
    
        /**
         * 是否显示
         * @param enable
         */
        public void setBadgeEnable(boolean enable) {
           setVisibility(enable?VISIBLE:INVISIBLE);
        }
    
    
        /**
         * 设置小红点的文字
         *
         * @param number
         */
        public void setBadgeNumber(int number) {
            this.number = number;
            this.text = String.valueOf(number);
            if (isSetup) {
                if(number==0){
                    setVisibility(INVISIBLE);
                }else{
                    setVisibility(VISIBLE);
                }
                invalidate();
            }
        }
    
        public void bindToTargetView(TabLayout target, int tabIndex) {
            TabLayout.Tab tab = target.getTabAt(tabIndex);
            View targetView = null;
            View tabView = null;
            try {
                Field viewField = TabLayout.Tab.class.getDeclaredField("mView");
                viewField.setAccessible(true);
                targetView = tabView = (View) viewField.get(tab);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            try {
                if (tabView != null) {
                    Field mTextViewField = tabView.getClass().getDeclaredField("mTextView");//"mIconView"
                    mTextViewField.setAccessible(true);
                    targetView = (View) mTextViewField.get(tabView);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (targetView != null) {
                bindToTargetView(targetView);
            }
        }
    
    
        /**
         * 绑定小红点到目标View的右上角
         *
         * @param target
         */
        public void bindToTargetView(View target) {
            init(type, isOverlap);
            if (getParent() != null) {
                ((ViewGroup) getParent()).removeView(this);
            }
            if (target == null) {
                return;
            }
            if (target.getParent() instanceof ViewGroup) {
                ViewGroup parent = (ViewGroup) target.getParent();
    
                int groupIndex = parent.indexOfChild(target);
                parent.removeView(target);
    
                if (isOverlap) {//[小红点与目标View重叠]模式
                    FrameLayout badgeContainer = new FrameLayout(getContext());
                    ViewGroup.LayoutParams targetLayoutParams = target.getLayoutParams();
                    badgeContainer.setLayoutParams(targetLayoutParams);
    
                    target.setLayoutParams(new ViewGroup.LayoutParams(
                            ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
    
                    parent.addView(badgeContainer, groupIndex, targetLayoutParams);
                    badgeContainer.addView(target);
                    badgeContainer.addView(this);
    
                    FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
                    if(isCenterVertical) {
                        layoutParams.gravity =  Gravity.CENTER_VERTICAL ;
                    }else{
                        layoutParams.gravity = Gravity.END | Gravity.TOP;
                    }
                    if (mIgnoreTargetPadding) {
                        layoutParams.rightMargin = target.getPaddingRight() - w;
                        layoutParams.topMargin = target.getPaddingTop() - h / 2;
    
                    }
    
                    setLayoutParams(layoutParams);
    
    
                } else {//[小红点放右侧]模式
                    LinearLayout badgeContainer = new LinearLayout(getContext());
                    badgeContainer.setOrientation(LinearLayout.HORIZONTAL);
                    ViewGroup.LayoutParams targetLayoutParams = target.getLayoutParams();
                    badgeContainer.setLayoutParams(targetLayoutParams);
    
    
                    target.setLayoutParams(new ViewGroup.LayoutParams(
                            ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
    
                    parent.addView(badgeContainer, groupIndex, targetLayoutParams);
                    badgeContainer.addView(target);
                    badgeContainer.addView(this);
                    if(isCenterVertical) {
                        badgeContainer.setGravity(Gravity.CENTER_VERTICAL);
                    }
                }
                boolean hasSetMargin = leftMargin>0||topMargin>0||rightMargin>0||bottomMargin>0;
                if (hasSetMargin&&getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
                    ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) getLayoutParams();
                    p.setMargins(leftMargin, topMargin, rightMargin, bottomMargin);
                    setLayoutParams(p);
                }
                isSetup = true;
            } else if (target.getParent() == null) {
                throw new IllegalStateException("目标View不能没有父布局!");
            }
    
            if(number==0){
                setVisibility(INVISIBLE);
            }else{
                setVisibility(VISIBLE);
            }
    
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (w > 0 && h > 0) {
                setMeasuredDimension(w, h);
            } else {
                throw new IllegalStateException("如果你自定义了小红点的宽高,就不能设置其宽高小于0 ,否则请不要设置!");
            }
        }
    
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //这里不用解释了 很简单 就是画一个圆形和文字
            rect.left = 0;
            rect.top = 0;
            rect.right = getWidth();
            rect.bottom = getHeight();
            canvas.drawRoundRect(rect, getWidth() / 2, getWidth() / 2, mBackgroundPaint);
    
            if (type == Type.TYPE_TEXT) {
                float textWidth = getTextWidth(text, mTextPaint);
                float textHeight = getTextHeight(text, mTextPaint);
                canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2 + textHeight / 2, mTextPaint);
            }
        }
    
        private float getTextWidth(String text, Paint p) {
            return p.measureText(text, 0, text.length());
        }
    
        private float getTextHeight(String text, Paint p) {
            Rect rect = new Rect();
            p.getTextBounds(text, 0, text.length(), rect);
            return rect.height();
        }
    
    
    }
    
    
    

    #使用示例:

    public class Main2Activity extends AppCompatActivity {
    
        private TextView mVTypeA;
        private TextView mVTypeB;
        private TextView mVTypeC;
        private TextView mVTypeD;
        private ImageView mVTypeE;
        private TabLayout mTabLayout;
    
        private void initView() {
            mVTypeA = (TextView) findViewById(R.id.vTypeA);
            mVTypeB = (TextView) findViewById(R.id.vTypeB);
            mVTypeC = (TextView) findViewById(R.id.vTypeC);
            mVTypeD = (TextView) findViewById(R.id.vTypeD);
            mVTypeE = (ImageView) findViewById(R.id.vTypeE);
            mTabLayout = (TabLayout) findViewById(R.id.tabLayout);
        }
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main2);
            initView();
    
    
            //情况A(有margin时)
            new BadgeHelper(this)
                    .setBadgeType(BadgeHelper.Type.TYPE_POINT)
                    .setBadgeOverlap(false)
                    .bindToTargetView(mVTypeA);
    
            //情况B(有padding时)
            new BadgeHelper(this)
                    .setBadgeType(BadgeHelper.Type.TYPE_POINT)
                    .setBadgeOverlap(true, true)//重叠模式+忽略目标ViewPadding 效果, 什么意思? 你可以把第二个参数写成false看看会发生什么
                    .bindToTargetView(mVTypeB);
    
            //情况C(默认情况)
            BadgeHelper badgeHelperC = new BadgeHelper(this)
                    .setBadgeType(BadgeHelper.Type.TYPE_TEXT)
                    .setBadgeOverlap(false);
            badgeHelperC.bindToTargetView(mVTypeC);
            badgeHelperC.setBadgeNumber(6);//数字模式
    
    
            //情况D(有权重时)
            new BadgeHelper(this)
                    .setBadgeColor(0xff0f00ff)//顺便演示下 小红点颜色的设置
                    .setBadgeType(BadgeHelper.Type.TYPE_POINT)
                    .setBadgeOverlap(false)
                    .bindToTargetView(mVTypeD);
    
    
            //情况E(小红点与图片重叠)+ 数字模式
            BadgeHelper badgeHelperE = new BadgeHelper(this)
                    .setBadgeType(BadgeHelper.Type.TYPE_TEXT)
                    .setBadgeOverlap(true, true);//注意 isIgnoreTargetPadding=true时 和 setBadgeMargins 不能同时使用
            //.setBadgeSize(100,100)//设置小红点的大小, 如果未设置则使用默认宽高, 一般默认就好
            //.setBadgeTextSize(15) //设置文本大小,  不设置 则使用默认, 一般默认就好
            badgeHelperE.bindToTargetView(mVTypeE);
            //任意地方可以刷新数字
            badgeHelperE.setBadgeNumber(58);//数字
    
    
            //情况F(TabLayout兼容)
            BadgeHelper badgeHelperF = new BadgeHelper(this)
                    .setBadgeType(BadgeHelper.Type.TYPE_TEXT)
                    .setBadgeCenterVertical()//红点居中
                    .setBadgeMargins(10,0,0,0)//偏移一下
                    .setBadgeOverlap(false);
            badgeHelperF.bindToTargetView(mTabLayout, 0);
            badgeHelperF.setBadgeNumber(5);
    
            new BadgeHelper(this)
                    .setBadgeType(BadgeHelper.Type.TYPE_POINT)
                    .setBadgeCenterVertical()//红点居中
                    .setBadgeMargins(10,0,0,0)//偏移一下
                    .setBadgeOverlap(false)
                    .bindToTargetView(mTabLayout,1);
    
        }
    
    }
    
    

    #布局:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp"
        tools:context="com.mx.test12.Main2Activity">
    
    
        <TextView
            android:background="#220000ff"
            android:id="@+id/vTypeA"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:text="情况A(有margin时)" />
    
        <TextView
            android:background="#2200ff00"
            android:id="@+id/vTypeB"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="30dp"
            android:text="情况B(有padding时)" />
    
        <TextView
            android:id="@+id/vTypeC"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="情况C(默认情况)" />
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center_vertical"
            android:orientation="horizontal">
    
            <TextView
                android:background="#2200ffff"
                android:id="@+id/vTypeD"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="情况D(有权重时)" />
    
            <View
                android:layout_width="0dp"
                android:layout_height="0dp" />
        </LinearLayout>
    
    
        <ImageView
            android:id="@+id/vTypeE"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:contentDescription="情况E(与图片重叠)"
            android:padding="20dp"
            android:src="@mipmap/ic_launcher" />
    
        <android.support.design.widget.TabLayout
            android:id="@+id/tabLayout"
            android:layout_width="match_parent"
            android:layout_height="55dp"
            app:layout_scrollFlags="scroll"
            app:tabIndicatorColor="#057523"
            app:tabIndicatorHeight="2.0dp"
            app:tabMode="fixed"
            app:tabSelectedTextColor="#057523"
            app:tabTextColor="#ced0d3">
    
            <android.support.design.widget.TabItem
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="聊天列表" />
    
            <android.support.design.widget.TabItem
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="好友列表" />
    
            <android.support.design.widget.TabItem
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="设置" />
    
        </android.support.design.widget.TabLayout>
    
    </LinearLayout>