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

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

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

布局文件:

<?xml version="1.0" encoding="utf-8"?>                                                                                                                                                                                                                                                                                                                                                                                                                                                                        

来看看主要的部分控件:

                                                                                                                          

上面可以看出控件的结构,只要是我们自定义了一个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. [Android(安卓)UI] shape和selector的结合使用
  2. Android学习笔记(16):绝对布局AbsoluteLayout、常用距离单位
  3. android 实现有阻尼下拉/上拉刷新列表
  4. Android(安卓)自定义View学习(3)--仿IOS风格滑动按钮
  5. android仿美团底部导航栏的点击效果——揭露动画
  6. 十一、Android坐标系
  7. padding与margin的区别
  8. Android(安卓)Shape自定义纯色圆角按钮
  9. Android实现ListView或GridView首行/尾行距离屏幕边缘距离

随机推荐

  1. android集合SSH搭建服务器客户端请求
  2. Android优质学习方法
  3. Android(安卓)UI属性大解
  4. 国内目前最全面的介绍——Android中的Bro
  5. android 建数据库 SQLite 存储sd 卡或者
  6. Android网络连接判断与处理
  7. 〖Android〗(how-to) fix k860/k860i bul
  8. Android应用程序进程启动过程的源代码分
  9. Python on Android
  10. android 中让activity全屏幕显示