Android 自定义消息右上角的数字提示或红点(类似微信或QQ的未读消息提示)

Android 一个可以自由定制外观、支持拖拽消除的MaterialDesign风格Android BadgeView

完成消息右上角的数字提示或红点,下面上图:

1.类似微信或QQ的未读消息提示,下面看看如何实现:

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:background="#f9f9f9"
    android:orientation="vertical">

    <View
        android:layout_width="match_parent"
        android:layout_height="0.2dp"
        android:background="#dfdfdd" />

    <!-- 底部导航 -->

    <LinearLayout
        android:id="@+id/main_bottom"
        android:layout_width="match_parent"
        android:layout_height="53.5dp"
        android:background="#f9f9f9"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <RelativeLayout
            android:id="@+id/seal_chat"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1">


            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_centerInParent="true">

                <RelativeLayout
                    android:id="@+id/ll"
                    android:layout_marginBottom="2dp"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_centerHorizontal="true"
                    android:gravity="center">

                    <ImageView
                        android:id="@+id/tab_img_chats"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_centerHorizontal="true"
                        android:background="#ffffff"
                        android:focusable="false"
                        android:scaleType="centerInside" />

                    <TextView
                        android:id="@+id/tab_text_chats"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_below="@id/tab_img_chats"
                        android:layout_centerHorizontal="true"
                        android:layout_marginTop="2dp"
                        android:text="@string/chat"
                        android:textColor="#abadbb"
                        android:textSize="12sp" />

                </RelativeLayout>

                <cn.rongcloud.im.ui.widget.DragPointView
                    android:id="@+id/seal_num"
                    android:layout_width="19dp"
                    android:layout_height="19dp"
                    android:layout_gravity="right"
                    android:layout_marginTop="2dp"
                    android:layout_marginRight="19dp"
                    android:layout_toRightOf="@id/ll"
                    android:textColor="@android:color/white"
                    android:textSize="12sp"
                    android:visibility="invisible" />


            </FrameLayout>


        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/seal_contact_list"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:padding="3dp">

            <RelativeLayout
                android:layout_width="60dp"
                android:layout_height="match_parent"
                android:layout_centerHorizontal="true"
                android:gravity="center">

                <ImageView
                    android:id="@+id/tab_img_contact"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerHorizontal="true"
                    android:background="#ffffff"
                    android:focusable="false"
                    android:scaleType="centerInside" />
                <!--android:src="@drawable/tab_contact_list"-->

                <TextView
                    android:id="@+id/tab_text_contact"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@id/tab_img_contact"
                    android:layout_centerHorizontal="true"
                    android:layout_marginTop="3dp"
                    android:text="@string/contacts"
                    android:textColor="#abadbb"
                    android:textSize="12sp" />


            </RelativeLayout>
        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/seal_find"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:padding="3dp">

            <RelativeLayout
                android:layout_width="60dp"
                android:layout_height="match_parent"
                android:layout_centerHorizontal="true"
                android:gravity="center">

                <ImageView
                    android:id="@+id/tab_img_find"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerHorizontal="true"
                    android:background="#ffffff"
                    android:focusable="false"
                    android:scaleType="centerInside" />
                <!--android:src="@drawable/tab_find"-->

                <TextView
                    android:id="@+id/tab_text_find"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@id/tab_img_find"
                    android:layout_centerHorizontal="true"
                    android:layout_marginTop="3dp"
                    android:text="@string/discover"
                    android:textColor="#abadbb"
                    android:textSize="12sp" />


            </RelativeLayout>
        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/seal_me"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:padding="3dp">

            <FrameLayout
                android:layout_width="60dp"
                android:layout_height="match_parent"
                android:layout_centerHorizontal="true"
                android:gravity="center">


                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:orientation="vertical"
                    android:padding="3dp">

                    <ImageView
                        android:id="@+id/tab_img_me"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_centerHorizontal="true"
                        android:background="#ffffff"
                        android:focusable="false"
                        android:scaleType="centerInside" />


                    <TextView
                        android:id="@+id/tab_text_me"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_below="@id/tab_img_me"
                        android:layout_centerHorizontal="true"
                        android:layout_marginTop="2.2dp"
                        android:text="@string/me"
                        android:textColor="#abadbb"
                        android:textSize="12sp" />

                </RelativeLayout>

                <ImageView
                    android:id="@+id/mine_red"
                    android:layout_width="10dp"
                    android:layout_height="10dp"
                    android:layout_gravity="right"
                    android:layout_marginRight="16dp"
                    android:layout_toRightOf="@id/tab_img_me"
                    android:background="@drawable/aii"
                    android:visibility="gone" />

            </FrameLayout>
        </RelativeLayout>

        <!-- <RelativeLayout
             android:id="@+id/seal_me"
             android:layout_width="0dp"
             android:layout_height="match_parent"
             android:layout_weight="1"
             android:padding="3dp">

             <ImageView
                 android:id="@+id/tab_img_me"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_centerHorizontal="true"
                 android:background="#ffffff"
                 android:focusable="false"
                 android:scaleType="centerInside"
                  />
             &lt;!&ndash;android:src="@drawable/tab_me"&ndash;&gt;

             <TextView
                 android:id="@+id/tab_text_me"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_below="@id/tab_img_me"
                 android:layout_centerHorizontal="true"
                 android:layout_marginTop="3dp"
                 android:text="@string/me"
                 android:textColor="#9A9A9A"
                 android:textSize="12sp" />
         </RelativeLayout>-->
    </LinearLayout>
</LinearLayout>

来看看主要的部分控件:

  <RelativeLayout
            android:id="@+id/seal_chat"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1">


            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_centerInParent="true">

                <RelativeLayout
                    android:id="@+id/ll"
                    android:layout_marginBottom="2dp"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_centerHorizontal="true"
                    android:gravity="center">

                    <ImageView
                        android:id="@+id/tab_img_chats"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_centerHorizontal="true"
                        android:background="#ffffff"
                        android:focusable="false"
                        android:scaleType="centerInside" />

                    <TextView
                        android:id="@+id/tab_text_chats"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_below="@id/tab_img_chats"
                        android:layout_centerHorizontal="true"
                        android:layout_marginTop="2dp"
                        android:text="@string/chat"
                        android:textColor="#abadbb"
                        android:textSize="12sp" />

                </RelativeLayout>

                <cn.rongcloud.im.ui.widget.DragPointView
                    android:id="@+id/seal_num"
                    android:layout_width="19dp"
                    android:layout_height="19dp"
                    android:layout_gravity="right"
                    android:layout_marginTop="2dp"
                    android:layout_marginRight="19dp"
                    android:layout_toRightOf="@id/ll"
                    android:textColor="@android:color/white"
                    android:textSize="12sp"
                    android:visibility="invisible" />


            </FrameLayout>


        </RelativeLayout>

上面可以看出控件的结构,只要是我们自定义了一个DragPointView类:

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.AbsListView;
import android.widget.ScrollView;
import android.widget.TextView;


public class DragPointView extends TextView {
    private boolean initBgFlag;
    private OnDragListencer dragListencer;
    private int backgroundColor = Color.parseColor("#f43530");
    private PointView pointView;
    private int x, y, r;
    private ViewGroup scrollParent;
    private int[] p = new int[2];

    public DragPointView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initbg();
    }

    public OnDragListencer getDragListencer() {
        return dragListencer;
    }

    public void setDragListencer(OnDragListencer dragListencer) {
        this.dragListencer = dragListencer;
    }

    public int getBackgroundColor() {
        return backgroundColor;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int w = getMeasuredWidth();
        int h = getMeasuredHeight();
        if (w != h) { // 简单的将宽高搞成一样的,如果有更好的方法欢迎在我博客下方留言!
            int x = Math.max(w, h);
            setMeasuredDimension(x, x);
        }
    }

    @SuppressWarnings("deprecation")
    public void setBackgroundColor(int backgroundColor) {
        this.backgroundColor = backgroundColor;
        DragPointView.this.setBackgroundDrawable(createStateListDrawable((getHeight() > getWidth() ? getHeight()
                : getWidth()) / 2, backgroundColor));
    }

    private void initbg() {
        setGravity(Gravity.CENTER);
        getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {

            @SuppressWarnings("deprecation")
            @Override
            public boolean onPreDraw() {
                if (!initBgFlag) {
                    DragPointView.this.setBackgroundDrawable(createStateListDrawable(
                                (getHeight() > getWidth() ? getHeight() : getWidth()) / 2, backgroundColor));
                    initBgFlag = true;
                    return false;
                }
                return true;
            }
        });
    }


    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        View root = getRootView();
        if (root == null || !(root instanceof ViewGroup)) {
            return false;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                root.getLocationOnScreen(p);
                scrollParent = getScrollParent();
                if (scrollParent != null) {
                    scrollParent.requestDisallowInterceptTouchEvent(true);
                }
                int location[] = new int[2];
                getLocationOnScreen(location);
                x = location[0] + (getWidth() / 2) - p[0];
                y = location[1] + (getHeight() / 2) - p[1];
                r = (getWidth() + getHeight()) / 4;
                pointView = new PointView(getContext());
                pointView.setLayoutParams(new ViewGroup.LayoutParams(root.getWidth(), root.getHeight()));
                setDrawingCacheEnabled(true);
                pointView.catchBitmap = getDrawingCache();
                pointView.setLocation(x, y, r, event.getRawX() - p[0], event.getRawY() - p[1]);
                ((ViewGroup) root).addView(pointView);
                setVisibility(View.INVISIBLE);
                break;
            case MotionEvent.ACTION_MOVE:
                pointView.refrashXY(event.getRawX() - p[0], event.getRawY() - p[1]);
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (scrollParent != null) {
                    scrollParent.requestDisallowInterceptTouchEvent(false);
                }
                if (!pointView.broken) { // 没有拉断
                    pointView.cancel();
                } else if (pointView.nearby) {// 拉断了,但是又回去了
                    pointView.cancel();
                } else { // 彻底拉断了
                    pointView.broken();
                }
                break;
            default:
                break;
        }
        return true;
    }

    private ViewGroup getScrollParent() {
        View p = this;
        while (true) {
            View v;
            try {
                v = (View) p.getParent();
            } catch (ClassCastException e) {
                return null;
            }
            if (v == null)
                return null;
            if (v instanceof AbsListView || v instanceof ScrollView || v instanceof ViewPager) {
                return (ViewGroup) v;
            }
            p = v;
        }
    }

    public interface OnDragListencer {
        void onDragOut();
    }

    class PointView extends View {
        private Bitmap catchBitmap;
        private Circle c1;
        private Circle c2;
        private Paint paint;
        private Path path = new Path();
        private int maxDistance = 8; // 10倍半径距离视为拉断
        private boolean broken; // 是否拉断过
        private boolean out; // 放手的时候是否拉断
        private boolean nearby;
        private int brokenProgress;

        public PointView(Context context) {
            super(context);
            init();
        }

        public void init() {
            paint = new Paint();
            paint.setColor(backgroundColor);
            paint.setAntiAlias(true);
        }

        public void setLocation(float c1X, float c1Y, float r, float endX, float endY) {
            broken = false;
            c1 = new Circle(c1X, c1Y, r);
            c2 = new Circle(endX, endY, r);
        }

        public void refrashXY(float x, float y) {
            c2.x = x;
            c2.y = y;
            // 以前的半径应该根据距离缩小点了
            // 计算出距离
            double distance = c1.getDistance(c2);
            int rate = 10;
            c1.r = (float) ((c2.r * c2.r * rate) / (distance + (c2.r * rate)));
            Log.i("info", "c1: " + c1.r);
            invalidate();
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawColor(Color.TRANSPARENT);
            if (out) {
                float dr = c2.r / 2 + c2.r * 4 * (brokenProgress / 100f);
                Log.i("info", "dr" + dr);
                canvas.drawCircle(c2.x, c2.y, c2.r / (brokenProgress + 1), paint);
                canvas.drawCircle(c2.x - dr, c2.y - dr, c2.r / (brokenProgress + 2), paint);
                canvas.drawCircle(c2.x + dr, c2.y - dr, c2.r / (brokenProgress + 2), paint);
                canvas.drawCircle(c2.x - dr, c2.y + dr, c2.r / (brokenProgress + 2), paint);
                canvas.drawCircle(c2.x + dr, c2.y + dr, c2.r / (brokenProgress + 2), paint);
            } else {
                // 绘制手指跟踪的圆形
                if (catchBitmap == null || (catchBitmap != null && catchBitmap.isRecycled())) {
                    return;
                }
                canvas.drawBitmap(catchBitmap, c2.x - c2.r, c2.y - c2.r, paint);
                path.reset();
                float deltaX = c2.x - c1.x;
                float deltaY = -(c2.y - c1.y);
                double distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
                double sin = deltaY / distance;
                double cos = deltaX / distance;
                nearby = distance < c2.r * maxDistance;
                if (nearby && !broken) {
                    canvas.drawCircle(c1.x, c1.y, c1.r, paint);
                    path.moveTo((float) (c1.x - c1.r * sin), (float) (c1.y - c1.r * cos));
                    path.lineTo((float) (c1.x + c1.r * sin), (float) (c1.y + c1.r * cos));
                    path.quadTo((c1.x + c2.x) / 2, (c1.y + c2.y) / 2, (float) (c2.x + c2.r * sin), (float) (c2.y + c2.r
                                * cos));
                    path.lineTo((float) (c2.x - c2.r * sin), (float) (c2.y - c2.r * cos));
                    path.quadTo((c1.x + c2.x) / 2, (c1.y + c2.y) / 2, (float) (c1.x - c1.r * sin), (float) (c1.y - c1.r
                                * cos));
                    canvas.drawPath(path, paint);
                } else {
                    broken = true; // 已经拉断了
                }
            }

        }

        public void cancel() {
            int duration = 150;
            AnimatorSet set = new AnimatorSet();
            ValueAnimator animx = ValueAnimator.ofFloat(c2.x, c1.x);
            animx.setDuration(duration);
            animx.setInterpolator(new OvershootInterpolator(2));
            animx.addUpdateListener(new AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    c2.x = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            ValueAnimator animy = ValueAnimator.ofFloat(c2.y, c1.y);
            animy.setDuration(duration);
            animy.setInterpolator(new OvershootInterpolator(2));
            animy.addUpdateListener(new AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    c2.y = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            set.playTogether(animx, animy);
            set.addListener(new AnimatorListenerAdapter() {

                @Override
                public void onAnimationEnd(Animator animation) {
                    ViewGroup vg = (ViewGroup) PointView.this.getParent();
                    vg.removeView(PointView.this);
                    DragPointView.this.setVisibility(View.VISIBLE);
                }
            });
            set.start();

        }

        public void broken() {
            out = true;
            int duration = 500;
            ValueAnimator a = ValueAnimator.ofInt(0, 100);
            a.setDuration(duration);
            a.setInterpolator(new LinearInterpolator());
            a.addUpdateListener(new AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    brokenProgress = (int) animation.getAnimatedValue();
                    invalidate();
                }
            });
            a.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    ViewGroup vg = (ViewGroup) PointView.this.getParent();
                    vg.removeView(PointView.this);
                }
            });
            a.start();
            if (dragListencer != null) {
                dragListencer.onDragOut();
            }
        }

        class Circle {
            float x;
            float y;
            float r;

            public Circle(float x, float y, float r) {
                this.x = x;
                this.y = y;
                this.r = r;
            }

            public double getDistance(Circle c) {
                float deltaX = x - c.x;
                float deltaY = y - c.y;
                double distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
                return distance;
            }
        }

    }

    /**
     * @param radius 圆角角度
     * @param color  填充颜色
     * @return StateListDrawable 对象
     * @author zy
     */
    public static StateListDrawable createStateListDrawable(int radius, int color) {
        StateListDrawable bg = new StateListDrawable();
        GradientDrawable gradientStateNormal = new GradientDrawable();
        gradientStateNormal.setColor(color);
        gradientStateNormal.setShape(GradientDrawable.RECTANGLE);
        gradientStateNormal.setCornerRadius(50);
        gradientStateNormal.setStroke(0, 0);
        bg.addState(View.EMPTY_STATE_SET, gradientStateNormal);
        return bg;
    }

}

2.代码中实现:

 DragPointView mUnreadNumView = (DragPointView) findViewById(R.id.seal_num);
        mUnreadNumView.setOnClickListener(this);
        mUnreadNumView.setDragListencer(this);
 if (count == 0) {
            mUnreadNumView.setVisibility(View.GONE);
        } else if (count > 0 && count < 100) {
            mUnreadNumView.setVisibility(View.VISIBLE);
            mUnreadNumView.setText(String.valueOf(count));
        } else {
            mUnreadNumView.setVisibility(View.VISIBLE);
            mUnreadNumView.setText(R.string.no_read_message);
        }

进而设置显示样式和是否显示,通常上面是和融云结合使用的

 

相似案例:

https://github.com/qstumn/BadgeView

https://github.com/Dalanger/MyBaseProject

https://github.com/MonkeyMushroom/DragBubbleView

https://github.com/younfor/BubbleDrag

https://github.com/chenchengyin/QQPresentation

https://github.com/A-Miracle/Bubble-Drag

https://github.com/AlexLiuSheng/BadgeView

https://github.com/stefanjauker/BadgeView

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值