先看效果图(本篇实现气泡拖拽消失,没有实现回弹和消失时爆炸效果)
气泡拖拽消失
观察效果图,可以发现其中有两个圆(固定圆和拖拽圆)加上中间拖拽的贝塞尔效果,其中固定圆的半径随着拉伸不断变小,小到一定值时消失,手指松开,如果固定圆还存在则回弹,不存在则拖拽圆也消失,本篇没实现的是回弹时的贝塞尔效果,已经拖拽圆消失时爆炸的效果,如果有机会实现了会再进行讲解。
先来初始化代码,我们定义为BubbleView类
public class BubbleView extends View {
private int mFixCircleRadius = 20;//固定圆的半径,其半径会随着拉伸变小,我们设定最小是6,小于6的时候固定圆消失
private int mDragCircleRadius = 20;//拖拽时显示的园
private PointF mFixCirclePoint;//固定圆圆心,本文只是演示,具体使用时要自己计算view的大小,再设置合适的圆心和半径
private PointF mDragCirclePoint;//拖拽圆的圆心,随着拖拽不断变化
private Paint paint;//画笔
private float distance;//两个圆心的距离
private boolean isDrag = false;//判断手指按下时是否落在固定圆的区域
private boolean isFixCircleShow = true;//固定圆是否显示
private boolean isUp = false;//手指是否弹起,弹起时如果固定圆显示这时候要回弹回来,如果固定圆消失了,此时应该执行拖拽圆爆炸消失效果,本文演示只做消失效果
public BubbleView(Context context) {
this(context, null);
}
public BubbleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
//初始化画笔,圆心
private void init() {
mFixCirclePoint = new PointF(300, 300);
mDragCirclePoint = new PointF(300, 300);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setColor(getResources().getColor(R.color.red));
}
}
接着我们监听onTouchEvent事件,在ACTION_DOWN的时候判断手指是否落在了固定圆的区域,如果没有不响应本次拖拽事件,接着在ACTION_MOVE中计算拉伸的距离,并不断的改变固定圆的大小(拉伸距离越大半径越小)以及拉伸圆的圆心(手指移动到的点),在ACTION_UP中我们恢复初始值,代码如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
float downX = event.getX();
float downY = event.getY();
isUp = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isDrag(downX, downY);//判断是否在固定圆区域,不在不显示拖拽效果
break;
case MotionEvent.ACTION_MOVE:
if (!isDrag) return true;
//判断是否出现拉伸效果
if (Math.abs(event.getX() - mFixCirclePoint.x )> mFixCircleRadius || Math.abs(event.getY() - mFixCirclePoint.y) > mFixCircleRadius) {
mDragCirclePoint.x = event.getX();//不断改变拖拽圆的圆心
mDragCirclePoint.y = event.getY();
float dx = mFixCirclePoint.x - mDragCirclePoint.x;
float dy = mFixCirclePoint.y - mDragCirclePoint.y;
distance = (float) Math.hypot(Math.abs(dx), Math.abs(dy));//计算拖拽的距离
postInvalidate();
}
break;
case MotionEvent.ACTION_UP:
isDrag = false;
isUp = true;
mFixCircleRadius = 20;
mDragCirclePoint.x = event.getX();
mDragCirclePoint.y = event.getY();
postInvalidate();
break;
}
return true;
}
//判断手指是否落在了固定的小圆内
private void isDrag(float x, float y) {
if (x >= mFixCirclePoint.x - mFixCircleRadius-20 && x <= mFixCirclePoint.x + mFixCircleRadius+20 && y >= mFixCirclePoint.y - mFixCircleRadius-20 && y <= mFixCirclePoint.y + mFixCircleRadius+20)
isDrag = true;
else isDrag = false;
}
接下来就是绘制,绘制过程中除了两个圆,最主要的就是中间的拉伸效果,拉伸效果实际上是一个贝塞尔曲线,如下图
本图来自 http://blog.csdn.net/z240336124/article/details/76409397
最主要的就是计算p0,p1,p2,p3的点,其中我们知道c0,c1的位置,然后根据三角函数就可以计算出这4个点,然后p0到p1是一个二阶贝塞尔曲线,p2到p3是一个贝塞尔曲线,控制点我们选为c0,c1中间的位置,代码如下:
//绘制拉伸的效果
private void drawBezer(Canvas canvas) {
Path path = new Path();
//计算4个控制点
float dx = mFixCirclePoint.x - mDragCirclePoint.x;
float dy = mFixCirclePoint.y - mDragCirclePoint.y;
if (dx == 0) {
dx = 0.001f;
}
float tan = dy / dx;
// 获取角a度值
float arcTanA = (float) Math.atan(tan);
// 依次计算 p0 , p1 , p2 , p3 点的位置
float P0X = (float) (mFixCirclePoint.x + mFixCircleRadius * Math.sin(arcTanA));
float P0Y = (float) (mFixCirclePoint.y - mFixCircleRadius * Math.cos(arcTanA));
float P1X = (float) (mDragCirclePoint.x + mDragCircleRadius * Math.sin(arcTanA));
float P1Y = (float) (mDragCirclePoint.y - mDragCircleRadius * Math.cos(arcTanA));
float P2X = (float) (mDragCirclePoint.x - mDragCircleRadius * Math.sin(arcTanA));
float P2Y = (float) (mDragCirclePoint.y + mDragCircleRadius * Math.cos(arcTanA));
float P3X = (float) (mFixCirclePoint.x - mFixCircleRadius * Math.sin(arcTanA));
float P3Y = (float) (mFixCirclePoint.y + mFixCircleRadius * Math.cos(arcTanA));
PointF controlPoint = new PointF(mFixCirclePoint.x + (mDragCirclePoint.x - mFixCirclePoint.x) / 2, (mFixCirclePoint.y + (mDragCirclePoint.y - mFixCirclePoint.y) / 2));
// 整合贝塞尔曲线路径
path.moveTo(P0X, P0Y);
path.quadTo(controlPoint.x, controlPoint.y, P1X, P1Y);
path.lineTo(P2X, P2Y);
path.quadTo(controlPoint.x, controlPoint.y, P3X, P3Y);
path.close();
canvas.drawPath(path, paint);
}
最后是整个的绘制过程代码:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isUp) {
//手指没有弹起时
mFixCircleRadius = (int) (20 - distance / 14);
//初始化时只画固定圆
if (!isDrag)
canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
else {
//拖拽时不断减小固定圆半径,如果小于6时,固定圆消失
if (mFixCircleRadius <= 6) {
isFixCircleShow = false;
}
if (mFixCircleRadius > 6 && isFixCircleShow) {
canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
drawBezer(canvas);//绘制拖拽的效果
}
canvas.drawCircle(mDragCirclePoint.x, mDragCirclePoint.y, mDragCircleRadius, paint);
}
} else {
//手指弹起时,如果固定圆没有消失,显示回弹效果
if (isFixCircleShow)
canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
}
}
最后贴一下本篇的完整代码
/**
* Created by liuyong
* Data: 2017/8/9
* Github:https://github.com/MrAllRight
* qq未读消息拖拽消失
*/
public class BubbleView extends View {
private int mFixCircleRadius = 20;//固定圆的半径,其半径会随着拉伸变小,我们设定最小是6,小于6的时候固定圆消失
private int mDragCircleRadius = 20;//拖拽时显示的园
private PointF mFixCirclePoint;//固定圆圆心,本文只是演示,具体使用时要自己计算view的大小,再设置合适的圆心和半径
private PointF mDragCirclePoint;//拖拽圆的圆心,随着拖拽不断变化
private Paint paint;//画笔
private float distance;//两个圆心的距离
private boolean isDrag = false;//判断手指按下时是否落在固定圆的区域
private boolean isFixCircleShow = true;//固定圆是否显示
private boolean isUp = false;//手指是否弹起,弹起时如果固定圆显示这时候要回弹回来,如果固定圆消失了,此时应该执行拖拽圆爆炸消失效果,本文演示只做消失效果
public BubbleView(Context context) {
this(context, null);
}
public BubbleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
//初始化画笔,圆心
private void init() {
mFixCirclePoint = new PointF(300, 300);
mDragCirclePoint = new PointF(300, 300);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setColor(getResources().getColor(R.color.red));
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float downX = event.getX();
float downY = event.getY();
isUp = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isDrag(downX, downY);//判断是否在固定圆区域,不在不显示拖拽效果
break;
case MotionEvent.ACTION_MOVE:
if (!isDrag) return true;
//判断是否出现拉伸效果
if (Math.abs(event.getX() - mFixCirclePoint.x )> mFixCircleRadius || Math.abs(event.getY() - mFixCirclePoint.y) > mFixCircleRadius) {
mDragCirclePoint.x = event.getX();//不断改变拖拽圆的圆心
mDragCirclePoint.y = event.getY();
float dx = mFixCirclePoint.x - mDragCirclePoint.x;
float dy = mFixCirclePoint.y - mDragCirclePoint.y;
distance = (float) Math.hypot(Math.abs(dx), Math.abs(dy));//计算拖拽的距离
postInvalidate();
}
break;
case MotionEvent.ACTION_UP:
isDrag = false;
isUp = true;
mFixCircleRadius = 20;
mDragCirclePoint.x = event.getX();
mDragCirclePoint.y = event.getY();
postInvalidate();
break;
}
return true;
}
//判断手指是否落在了固定的小圆内
private void isDrag(float x, float y) {
if (x >= mFixCirclePoint.x - mFixCircleRadius-20 && x <= mFixCirclePoint.x + mFixCircleRadius+20 && y >= mFixCirclePoint.y - mFixCircleRadius-20 && y <= mFixCirclePoint.y + mFixCircleRadius+20)
isDrag = true;
else isDrag = false;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isUp) {
//手指没有弹起时
mFixCircleRadius = (int) (20 - distance / 14);
//初始化时只画固定圆
if (!isDrag)
canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
else {
//拖拽时不断减小固定圆半径,如果小于6时,固定圆消失
if (mFixCircleRadius <= 6) {
isFixCircleShow = false;
}
if (mFixCircleRadius > 6 && isFixCircleShow) {
canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
drawBezer(canvas);//绘制拖拽的效果
}
canvas.drawCircle(mDragCirclePoint.x, mDragCirclePoint.y, mDragCircleRadius, paint);
}
} else {
//手指弹起时,如果固定圆没有消失,显示回弹效果
if (isFixCircleShow)
canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
}
}
//绘制拉伸的效果
private void drawBezer(Canvas canvas) {
Path path = new Path();
//计算4个控制点
float dx = mFixCirclePoint.x - mDragCirclePoint.x;
float dy = mFixCirclePoint.y - mDragCirclePoint.y;
if (dx == 0) {
dx = 0.001f;
}
float tan = dy / dx;
// 获取角a度值
float arcTanA = (float) Math.atan(tan);
// 依次计算 p0 , p1 , p2 , p3 点的位置
float P0X = (float) (mFixCirclePoint.x + mFixCircleRadius * Math.sin(arcTanA));
float P0Y = (float) (mFixCirclePoint.y - mFixCircleRadius * Math.cos(arcTanA));
float P1X = (float) (mDragCirclePoint.x + mDragCircleRadius * Math.sin(arcTanA));
float P1Y = (float) (mDragCirclePoint.y - mDragCircleRadius * Math.cos(arcTanA));
float P2X = (float) (mDragCirclePoint.x - mDragCircleRadius * Math.sin(arcTanA));
float P2Y = (float) (mDragCirclePoint.y + mDragCircleRadius * Math.cos(arcTanA));
float P3X = (float) (mFixCirclePoint.x - mFixCircleRadius * Math.sin(arcTanA));
float P3Y = (float) (mFixCirclePoint.y + mFixCircleRadius * Math.cos(arcTanA));
PointF controlPoint = new PointF(mFixCirclePoint.x + (mDragCirclePoint.x - mFixCirclePoint.x) / 2, (mFixCirclePoint.y + (mDragCirclePoint.y - mFixCirclePoint.y) / 2));
// 整合贝塞尔曲线路径
path.moveTo(P0X, P0Y);
path.quadTo(controlPoint.x, controlPoint.y, P1X, P1Y);
path.lineTo(P2X, P2Y);
path.quadTo(controlPoint.x, controlPoint.y, P3X, P3Y);
path.close();
canvas.drawPath(path, paint);
}
}
也可到github上下载整个贝塞尔曲线的代码,其中有水波纹,直播点赞,还有本篇qq气泡拖拽效果,地址:https://github.com/MrAllRight/BezierView
写完发现这个还是参考红橙Darren的文章,大家可以去他的简书看一下,里面有自定义view的系列,讲解的都不错,而且还有视频讲解。
网友评论