该控件是一个自定义的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;
更改之后得到的效果如下:

你看我都拉了半边屏幕。
但是这样仍然难以看到曲线是如何绘制的,这是因为画笔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);
......
这样我们就能看到线条是如何组合的了:
?

可以看出的确是两个圆圈和一条封闭的路径组成的。那个数字图片有点碍眼,我们想办法去掉
?
在calculate()方法的适当位置加上
tipImageView.setVisibility(View.GONE);
我是加在第三行左右,总之能保证会被执行就行。我不敢说加在这里最合适,我只是单纯的想去掉它而已。
下面是去掉之后来回拉伸的变换图:

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

下面是注释之后的效果:

这就是我们的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方法就是绘制贝塞尔曲线,准确的说,是绘制二阶贝塞尔曲线。为了