Android自定义View仿QQ消息拖拽气泡实现

来源:互联网 发布:域名访问tomcat项目 编辑:程序博客网 时间:2024/05/14 07:54

Android自定义View仿QQ消息拖拽气泡实现

很多小的效果看上去很酷炫,其实操作起来很简单,就是要注意细节以及分析状态,然后去绘制,这里会实现qq消息拖拽气泡的实现。

  • 画图分析
  • 源代码
  • 效果展示

画图分析

在画图之前,看一下原效果图,分析实现步骤

这里写图片描述

由图可知,它这里拖拽画的是图片,为了更接近原理以及自定义可扩展,我们将拖拽的99+也当成一个圆。如下图:

这里写图片描述

小圆是在原始位置,大圆是在当前手指的位置画出来的,我们这里的坐标系是根据平移画布得到的,代码里会标注;
两个圆的圆心分别是o、 p,圆半径分别为rbig、rsmall,两圆心的链接线为op,链接bc经过圆心垂直于op,链接ad垂直于op,设op的中心点为g,设p点的坐标为(x,y),那么贝塞尔曲线agb和dgc就可以很好的画出(如果贝塞尔曲线不了解的可以看我的其它博客)。那么主要就是求a、b、c、d四个点的坐标,由于p点坐标就是手势的坐标可以很好的求出;

解决a、b、c、d四个点的坐标

由图可知
1.∠epo=∠bpf(这不需要我推理把。。。。)那么∠poe=∠pbf;同理oa与y轴方向的夹角也可以求出(我这里没有标出点,您可以在草稿纸上标出,来求一下)
那么
2.sin∠poe=pe/op; cos∠poe=oe/op;
3.pe=0-y(这里都用起始坐标减去末尾坐标,以确定改点在哪个坐标系)oe=0-x
由以上结论可以求出ABCD四点的坐标
A :(0-rsmall*sin,0+rsmall*cos)
B :(x-rbig*sin,y+rbig*cos)
C :(x+rbig*sin,y-rbig*cos)
D :(0+rsmall*sin,0-rsmall*cos)

分析状态
四种状态:正常状态,拖拽状态,超出状态,恢复状态
NORMAL,DRAG,BEYOUND,COMEBACK
这里我们就提下拖拽状态的path:还是要让他成为一个封闭的路径,所以要满足方向闭合(以前博客有分析,我这里画一下,也就是Path.Direction)

这里写图片描述

如上图箭头所示,所以我在代码中设置了不同位置的坐标点来决定绘制的方向,已达到绘制出来的path是封闭图形


源代码

代码很简单,具体封装接口和回掉都没写,看你的操作了,代码有注释,很好理解,有什么不好的地方可以指出来,我会改正。 注意,如果想在控件之外展示,需要在其父控件写 android:clipChildren=”false”

public class BubbleView extends View {    private int mWidth,mHeight,paintextSize=20;    //相关坐标    private float[] startSmall=new float[2],endBig=new float[2],startBig=new float[2],            endSmall=new float[2],controlXY=new float[2],activeXY=new float[2],lastXY=new float[2];    //相关路径    private Path pathSmallC,pathBigC,pathStick,pathLine,pathText;    //画笔    private Paint paintNormal,paintText;    //矩阵    private Matrix matrix;    //区域的裁定    private Region regionClip,regionBubble;    //一些参数    private float radioSmall=10f,radioBig=25f,sin=2,cos=2,len=0,maxLen=200f,varySet=1;    private TYPE type=TYPE.NORMAL;    private ValueAnimator animatorBack,animatorGone;    private ValueAnimator.AnimatorUpdateListener uplistner;    //测量    private PathMeasure measure;    enum TYPE{        //四种状态:正常状态,拖拽状态,超出状态,恢复状态        NORMAL,DRAG,BEYOUND,COMEBACK    }    public BubbleView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        initPaint();        initValuAnimator();    }    /**     * 初始化画笔     */    private void initPaint() {        paintNormal=new Paint();        paintNormal.setStyle(Paint.Style.FILL);        paintNormal.setAntiAlias(true);        paintNormal.setColor(Color.RED);        paintText=new Paint();        paintText.setTextSize(paintextSize);        paintText.setAntiAlias(true);        paintText.setColor(Color.WHITE);        paintText.setStyle(Paint.Style.FILL);        paintText.setTextAlign(Paint.Align.CENTER);        matrix=new Matrix();        measure=new PathMeasure();    }    /**     * 初始化路径     */    private void initPath() {        if(type==TYPE.NORMAL){            activeXY[0]=0;            activeXY[1]=0;        }        pathSmallC=new Path();        pathSmallC.addCircle(0,0,radioSmall, Path.Direction.CW);        pathLine=new Path();        pathLine.lineTo(activeXY[0],activeXY[1]);        if(type==TYPE.COMEBACK){            measure.setPath(pathLine,false);            measure.getPosTan(measure.getLength()*varySet,activeXY,null);        }        pathBigC=new Path();        pathText=new Path();        if(type==TYPE.BEYOUND){            pathBigC.addCircle(activeXY[0],activeXY[1],radioBig*varySet, Path.Direction.CW);            pathText.moveTo(activeXY[0]-radioBig,activeXY[1]+4*varySet);            pathText.lineTo(activeXY[0]+radioBig,activeXY[1]+4*varySet);            paintText.setTextSize(paintextSize*varySet);        }else {            pathBigC.addCircle(activeXY[0],activeXY[1],radioBig, Path.Direction.CW);            pathText.moveTo(activeXY[0]-radioBig,activeXY[1]+4);            pathText.lineTo(activeXY[0]+radioBig,activeXY[1]+4);            paintText.setTextSize(paintextSize);        }        regionBubble=new Region();        regionBubble.setPath(pathBigC,regionClip);        pathStick=new Path();        controlXY[0]=activeXY[0]/2;        controlXY[1]=activeXY[1]/2;        sin=(0-activeXY[1])/len;        cos=(0-activeXY[0])/len;        startSmall[0]=0-radioSmall*sin;        startSmall[1]=0+radioSmall*cos;        endBig[0]=activeXY[0]-radioBig*sin;        endBig[1]=activeXY[1]+radioBig*cos;        startBig[0]=activeXY[0]+radioBig*sin;        startBig[1]=activeXY[1]-radioBig*cos;        endSmall[0]=0+radioSmall*sin;        endSmall[1]=0-radioSmall*cos;        pathStick.moveTo(startSmall[0],startSmall[1]);        pathStick.quadTo(controlXY[0],controlXY[1],endBig[0],endBig[1]);        pathStick.lineTo(startBig[0],startBig[1]);        pathStick.quadTo(controlXY[0],controlXY[1],endSmall[0],endSmall[1]);        pathStick.close();        pathStick.setFillType(Path.FillType.WINDING);    }    /**     * 初始化动画     */    private void initValuAnimator() {        uplistner = new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator valueAnimator) {                varySet= (float) valueAnimator.getAnimatedValue();                invalidate();            }        };        Animator.AnimatorListener listener=new Animator.AnimatorListener() {            @Override            public void onAnimationStart(Animator animator) {            }            @Override            public void onAnimationEnd(Animator animator) {                type=TYPE.NORMAL;                varySet=1;            }            @Override            public void onAnimationCancel(Animator animator) {            }            @Override            public void onAnimationRepeat(Animator animator) {            }        };        animatorBack=ValueAnimator.ofFloat(1.0000f,0.0000f);        animatorBack.setInterpolator(new LinearInterpolator());        animatorBack.setRepeatCount(0);        animatorBack.setDuration(1000);        animatorBack.addUpdateListener(uplistner);        animatorBack.addListener(listener);        animatorGone=ValueAnimator.ofFloat(1.0000f,0.0000f);        animatorGone.setInterpolator(new LinearInterpolator());        animatorGone.setRepeatCount(0);        animatorGone.setDuration(1000);        animatorGone.addUpdateListener(uplistner);        animatorGone.addListener(listener);    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        getParent().requestDisallowInterceptTouchEvent(true);        return super.dispatchTouchEvent(event);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()){            case MotionEvent.ACTION_DOWN:                if(type==TYPE.NORMAL) {                    activeXY[0] = event.getRawX();                    activeXY[1] = event.getRawY();                    matrix.mapPoints(activeXY);                    if (regionBubble.contains((int) activeXY[0], (int) activeXY[1])) {                        type = TYPE.DRAG;                        lastXY[0]=activeXY[0];                        lastXY[1]=activeXY[1];                    }                }                break;            case MotionEvent.ACTION_MOVE:                activeXY[0]= event.getRawX();                activeXY[1]=event.getRawY();                matrix.mapPoints(activeXY);                if(type==TYPE.DRAG){                    len= (float) Math.hypot(activeXY[0],activeXY[1]);                    if(len<=maxLen){                        lastXY[0]=activeXY[0];                        lastXY[1]=activeXY[1];                    }else if(len>maxLen){                        type=TYPE.BEYOUND;                    }                }                invalidate();                break;            case MotionEvent.ACTION_UP:                if(type==TYPE.DRAG){                    activeXY[0]=lastXY[0];                    activeXY[1]=lastXY[1];                    //开始动画                    type=TYPE.COMEBACK;                    animatorBack.start();                }else if(type==TYPE.BEYOUND){                    //开始动画                    animatorGone.start();                }                break;            default:                break;        }        return true;    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        mWidth=w;        mHeight=h;        regionClip=new Region(-mWidth,-mHeight,mWidth,mHeight);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //平移画布        canvas.translate(mWidth/2,mHeight/2);        matrix.reset();        if(matrix.isIdentity()){            canvas.getMatrix().invert(matrix);        }        initPath();        switch (type){            case NORMAL:                drawBigC(canvas);                break;            case DRAG:                drawStick(canvas);                drawSmallC(canvas);                drawBigC(canvas);                break;            case BEYOUND:                drawBigC(canvas);                break;            case COMEBACK:                drawStick(canvas);                drawSmallC(canvas);                drawBigC(canvas);                break;            default:break;        }        drawText(canvas);    }    /**     * 画小圆     * @param canvas     */    private void drawSmallC(Canvas canvas) {        canvas.drawPath(pathSmallC,paintNormal);    }    /**     * 画拖拽     * @param canvas     */    private void drawStick(Canvas canvas) {        canvas.drawPath(pathStick,paintNormal);    }    /**     * 画大圆     * @param canvas     */    private void drawBigC(Canvas canvas) {        canvas.drawPath(pathBigC,paintNormal);    }    /**     * 画文字     * @param canvas     */    private void drawText(Canvas canvas){canvas.drawTextOnPath("1",pathText,0,0,paintText);}}

效果展示

这里写图片描述

还没优化,有什么建议或者看法,可以留言,抠脚来的,不喜勿喷

阅读全文
1 0