BezierDemo源码解析-实现qq消息气泡拖拽消失的效果(二)

2015-07-20 17:10:59 · 作者: · 浏览: 12
m); } private void calculate(){ float distance = (float) Math.sqrt(Math.pow(y-startY, 2) + Math.pow(x-startX, 2)); radius = -distance/15+DEFAULT_RADIUS; if(radius < 9){ isAnimStart = true; exploredImageView.setVisibility(View.VISIBLE); exploredImageView.setImageResource(R.drawable.tip_anim); ((AnimationDrawable) exploredImageView.getDrawable()).stop(); ((AnimationDrawable) exploredImageView.getDrawable()).start(); tipImageView.setVisibility(View.GONE); } // 根据角度算出四边形的四个点 float offsetX = (float) (radius*Math.sin(Math.atan((y - startY) / (x - startX)))); float offsetY = (float) (radius*Math.cos(Math.atan((y - startY) / (x - startX)))); float x1 = startX - offsetX; float y1 = startY + offsetY; float x2 = x - offsetX; float y2 = y + offsetY; float x3 = x + offsetX; float y3 = y - offsetY; float x4 = startX + offsetX; float y4 = startY - offsetY; path.reset(); path.moveTo(x1, y1); path.quadTo(anchorX, anchorY, x2, y2); path.lineTo(x3, y3); path.quadTo(anchorX, anchorY, x4, y4); path.lineTo(x1, y1); // 更改图标的位置 tipImageView.setX(x - tipImageView.getWidth()/2); tipImageView.setY(y - tipImageView.getHeight()/2); } @Override protected void onDraw(Canvas canvas){ if(isAnimStart || !isTouch){ canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.OVERLAY); }else{ calculate(); canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.OVERLAY); canvas.drawPath(path, paint); canvas.drawCircle(startX, startY, radius, paint); canvas.drawCircle(x, y, radius, paint); } super.onDraw(canvas); } @Override public boolean onTouchEvent(MotionEvent event) { if(event.getAction() == MotionEvent.ACTION_DOWN){ // 判断触摸点是否在tipImageView中 Rect rect = new Rect(); int[] location = new int[2]; tipImageView.getDrawingRect(rect); tipImageView.getLocationOnScreen(location); rect.left = location[0]; rect.top = location[1]; rect.right = rect.right + location[0]; rect.bottom = rect.bottom + location[1]; if (rect.contains((int)event.getRawX(), (int)event.getRawY())){ isTouch = true; } }else if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL){ isTouch = false; tipImageView.setX(startX - tipImageView.getWidth()/2); tipImageView.setY(startY - tipImageView.getHeight()/2); } invalidate(); if(isAnimStart){ return super.onTouchEvent(event); } anchorX = (event.getX() + startX)/2; anchorY = (event.getY() + startY)/2; x = event.getX(); y = event.getY(); return true; } }

该控件是一个自定义的FrameLayout,之所以不用自定义view,是为了能直接添加显示消息数目的图片。

关于成员变量的那部分注释已经比较清楚了,我直接看看

init()方法

在init方法中首先初始化了画笔paint,这个paint就是绘制粘连拉伸效果的。然后paint初始化代码下面为FrameLayout添加了两个图片:exploredImageView和tipImageView,exploredImageView是在拉断之后显示的气泡,而tipImageView是数字提示,这两个ImageView都只是为了辅助模仿qq,但不是我们要讨论的核心。

onLayout()方法

非重点,略。

calculate()方法

这是根据手指拖动位置计算各坐标的的方法,同时还在这里根据坐标点将path路径也定义了:

        path.reset();
        path.moveTo(x1, y1);
        path.quadTo(anchorX, anchorY, x2, y2);
        path.lineTo(x3, y3);
        path.quadTo(anchorX, anchorY, x4, y4);
        path.lineTo(x1, y1);

这端代码是粘连拉伸效果的核心。一会而我们做的各种实验都是在这里修修改改。

onDraw()方法

    @Override
    protected void onDraw(Canvas canvas){
        if(isAnimStart || !isTouch){
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.OVERLAY);
        }else{
            calculate();
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.OVERLAY);
            canvas.drawPath(path, paint);
           // canvas.drawCircle(startX, startY, radius, paint);
           // canvas.drawCircle(x, y, radius, paint);
        }
        super.onDraw(canvas);
    }

这个方法调用了上面的calculate方法,然后根据计算出的值绘制path和圆圈。

onTouchEvent()方法

这个方法将根据触摸点的位置变化记录必要的位置信息,供calculate()方法计算,同时在必要的地方发送绘制请求。

?

一步一步分解

如果讲到这里就结束,你肯定不满意-“我还是没明白贝塞尔曲线是如何应用到里面的呢”。为了彻底明白我们将做几个分解代码的实验。

?

首先我们找到onDraw方法,

    @Override
    protected void onDraw(Canvas canvas){
        if(isAnimStart || !isTouch){
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.OVERLAY);
        }else{
            calculate();
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.OVERLAY);
            canvas.drawPath(path, paint);
            canvas.drawCircle(startX, startY, radius, paint);
            canvas.drawCircle(x, y, radius, paint);
        }
        super.onDraw(canvas);
    }

if(isAnimStart || !isTouch){

中的代码是拉断之后的效果,不去管他。

主要看else中的代码

首先调用了calculate()方法,然后调用了

 canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.OVERLAY);

这个去掉也无所谓。

接着绘制了一条带有贝塞尔曲线的封闭路径:

 canvas.drawPath(path, paint);

然后分别绘制了两端的圆圈。

?

为了更直观的看出效果,我们将原本

// 默认定点圆半径
public static final float DEFAULT_RADIUS = 20;

改成

// 默认定点圆半径
public static final float DEFAULT_RADIUS = 150;

这样大点会更清楚的看到拉伸过程,而且拉很长也不会断,拉断的临界点是下面代码决定的:

calculate方法中

float distance = (float) Math.sqrt(Math.pow(y-startY, 2) + Math.pow(x-startX, 2));
radius = -distance/15+DEFAULT_RADIUS;

if(radius < 9){
isAnimStart = true;

更改之后得到的效果如下:

QQ图片20150311010955.png

你看我都拉了半边屏幕。

但是这样仍然难以看到曲线是如何绘制的,这是因为画笔paint的绘制类型是填充模式的,我们改成线条模式:

将init()方法改成

private void init(){
path = new Path();

paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
paint.setColor(Color.RED);

......

这样我们就能看到线条是如何组合的了:

?

QQ图片20150311011548.png

可以看出的确是两个圆圈和一条封闭的路径组成的。那个数字图片有点碍眼,我们想办法去掉

?

在calculate()方法的适当位置加上

 tipImageView.setVisibility(View.GONE);

我是加在第三行左右,总之能保证会被执行就行。我不敢说加在这里最合适,我只是单纯的想去掉它而已。

下面是去掉之后来回拉伸的变换图:

654444.gif

有点猥琐。。。。

现在我们将两个圆圈也去掉吧,这两个圆圈仅仅是根据两点之间距离的大小改变了下半径而已(第二个点也改变了圆点坐标)。贝塞尔曲线在中间那部分,让我们看看包含了贝塞尔曲线的path路径的真面目。

去掉圆圈只需将ondraw方法的相关代码注释掉:

QQ图片20150311013309.png

下面是注释之后的效果:

8487498.gif

这就是我们的path了。

回到构建这个path的代码,在calculate方法中:

        path.reset();
        path.moveTo(x1, y1);
       
        path.quadTo(anchorX, anchorY, x2, y2);
        path.lineTo(x3, y3);
        path.quadTo(anchorX, anchorY, x4, y4);
     
        path.lineTo(x1, y1);

其中lineTo方法是绘制直线,quadTo方法就是绘制贝塞尔曲线,准确的说,是绘制二阶贝塞尔曲线。为了