android贝塞尔曲线拖拽,贝塞尔曲线实践--拖拽气泡

贝塞尔曲线扫盲

贝塞尔曲线主要由于三个部分控制:起点,终点,中间的辅助控制点。在android自带的Path类中自带了方法,可以帮助我们实现贝塞尔曲线:

二阶贝塞尔

/**

* Add a quadratic bezier from the last point, approaching control point

* (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for

* this contour, the first point is automatically set to (0,0).

*

* @param x1 The x-coordinate of the control point on a quadratic curve

* @param y1 The y-coordinate of the control point on a quadratic curve

* @param x2 The x-coordinate of the end point on a quadratic curve

* @param y2 The y-coordinate of the end point on a quadratic curve

*/

public void quadTo(float x1, float y1, float x2, float y2) {

isSimplePath = false;

native_quadTo(mNativePath, x1, y1, x2, y2);

}

quadTo()方法从上一个点为起点开始绘制贝塞尔曲线,其中(x1,y1)为辅助控制点,(x2,y2)为终点。

Path mPath = new Path();

mPath.moveTo(x0,y0);

mPath.quadTo(x1,y1,x2,y2);

如调用以上代码,即绘制起点(x0,y0),终点(x2,y2),辅助控制点(x1,y1)的贝塞尔曲线。因此,通过不断改变这三个点的位置,我们可以绘制出各种各样的曲线

三阶贝塞尔

/**

* Add a cubic bezier from the last point, approaching control points

* (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been

* made for this contour, the first point is automatically set to (0,0).

*

* @param x1 The x-coordinate of the 1st control point on a cubic curve

* @param y1 The y-coordinate of the 1st control point on a cubic curve

* @param x2 The x-coordinate of the 2nd control point on a cubic curve

* @param y2 The y-coordinate of the 2nd control point on a cubic curve

* @param x3 The x-coordinate of the end point on a cubic curve

* @param y3 The y-coordinate of the end point on a cubic curve

*/

public void cubicTo(float x1, float y1, float x2, float y2,

float x3, float y3) {

isSimplePath = false;

native_cubicTo(mNativePath, x1, y1, x2, y2, x3, y3);

}

cubicTo()方法从上一个点为起点开始绘制三阶贝塞尔曲线,其中(x1,y1),( x2, y2 )为辅助控制点,(x3,y3)为终点。

拖拽气泡实践

思路

拖拽气泡,其实 就是这样的几个状态

1.气泡静止状态 --气泡小球和数字

2.气泡相连 -- 两个相连的气泡小球(带黏连效果),和数字

3.气泡分离 -- 单个气泡小球的拖动

4.气泡消失 -- 爆炸

我们需要在这几个状态分别,做出处理.

e5e19c09d812

这里写图片描述

1.两个圆和一个角度

一个静止的圆O1

一个动的圆O2

互补角和内错角

根据圆的关系,和Anchor点,计算出角度

2.两条贝塞尔二阶曲线

上半边: A---Ar---B, 以Ar为锚点,A和B重点的二阶曲线

下半边: C---Ar---D,以Ar为锚点,C和D重点的二阶曲线

具体坐标的求法,在上图中可见.

根据贝塞尔曲线的要求:

起点,终点,中间的辅助控制点。用在android自带的Path类中自带了方法.

举个例子,就可以这么做

上边线:

//画上半弧 B-->A---------------

//A点静坐标计算

float mBubStillAX = mBubStillCenter.x -mBubStillRadius*sinTheta;

float mBubStillAY = mBubStillCenter.y +mBubStillRadius*cosTheta;

//B点动坐标计算

float mBubMoveBX = mBubMoveableCenter.x -mBubMoveableRadius*sinTheta;

float mBubMoveBY = mBubMoveableCenter.y +mBubMoveableRadius*cosTheta;

mBezierPath.moveTo(mBubStillAX,mBubStillAY);

mBezierPath.quadTo(iAnchorX,iAnchorY,mBubMoveBX,mBubMoveBY);

3.状态判断OnTouch

在onTouchEvent()中,对touch事件进行自己处理,即return true进行以下几个状态切换.

按下, 如果 当前状态 没有消失, 则计算 当前气泡距离,和 最大允许偏移量 作比较 ,进行状态切换

移动 ,改变动点的位置, 计算两点距离, 改变 静止点的半径,切换状态,并重绘制,调用ondraw

抬起手, 根据 链接 状态, 开始进行 动画

4.绘制不同 OnDraw

根据上述不同的状态,绘制不同的view.

1当 当前状态 != 消失, 则, 画 移动的气泡 和文字

2.相连的气泡状态,绘制贝塞尔曲线

3.boom,爆炸状态, 爆炸效果动画

最终效果

e5e19c09d812

这里写图片描述

实现源码

/**

* 仿QQ拖拽气泡

* Created by chenchangjun on 17/5/25.

*

*/

public class DragBuubbleView extends View {

public Context mContext;

/**

* state 根据 ontouch事件进行判断,

*/

private final int STATE_DEFAULT = 0;// 1.气泡静止状态 --气泡小球和数字

private final int STATE_CONECTED = 1;//2.气泡相连 -- 两个相连的气泡小球(带黏连效果),和数字

private final int STATE_APART = 2;//3.气泡分离 -- 单个气泡小球的拖动

private final int STATE_DISMISS = 3;//4.气泡消失 -- 爆炸

//default values;

private float default_radius;

private int default_bubble_color;

private String default_bubble_text;

private int default_bubble_textColor;

private float default_bubble_textSize;

private float default_bubble_radius;

private float bubble_radius;//小球半径

private int bubble_color;//小球颜色

private String bubble_text;//小球显示字

private int bubble_textColor; //字体颜色

private float bubble_textSize;//字体大小

/**

* 静止气泡和动态气泡相关

*/

private float mBubStillRadius;//不动气泡的半径

private float mBubMoveableRadius;//可动气泡的半径

private PointF mBubStillCenter;//不动气泡的圆心

private PointF mBubMoveableCenter;//可动气泡的圆心

private Paint mBubblePaint;//气泡的画笔

/**

* 贝塞尔曲线path相关

*/

private Path mBezierPath;

private Paint mTextPaint;

private Rect mTextRect;

private Paint mBurstPaint;

private Rect mBurstRect;

private int current_state = STATE_DEFAULT;//当前状态

private float bubbleDistance; //气泡间距

private float maxDistance; //气泡最大间距

private float MOVE_OFFSET = 0;//允许你的 手指触摸偏移量

private Bitmap[] burstBitmapsArray;//气泡爆炸bitmap数组

private boolean isBurstAnimStart = false; //是否执行 气泡爆炸

private int curDrawableIndex; //当前气泡爆炸图片index

//气泡爆炸的图片id数组

private int[] burstDrawablesArray = {R.drawable.burst_1, R.drawable.burst_2

, R.drawable.burst_3, R.drawable.burst_4, R.drawable.burst_5};

private void init(Context context, AttributeSet attrs) {

this.mContext = context;

if (attrs != null) {

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DragBubbleView);

bubble_radius = typedArray.getDimension(R.styleable.DragBubbleView_bubble_radius, default_bubble_radius);

bubble_color = typedArray.getColor(R.styleable.DragBubbleView_bubble_color, default_bubble_color);

bubble_textSize = typedArray.getDimension(R.styleable.DragBubbleView_bubble_textSize, default_bubble_textSize);

bubble_textColor = typedArray.getColor(R.styleable.DragBubbleView_bubble_textColor, default_bubble_textColor);

bubble_text = typedArray.getString(R.styleable.DragBubbleView_bubble_text);

typedArray.recycle();

}

mBubStillRadius = bubble_radius;

mBubMoveableRadius = mBubStillRadius;

maxDistance = 8 * bubble_radius;

MOVE_OFFSET = maxDistance / 4;

//气泡画笔

mBubblePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

mBubblePaint.setColor(bubble_color);

mBubblePaint.setStyle(Paint.Style.FILL);

mBezierPath = new Path();

//文字画笔

mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

mTextPaint.setColor(bubble_textColor);

mTextPaint.setTextSize(bubble_textSize);

mTextRect = new Rect();

//爆炸画笔

mBurstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

mBurstPaint.setFilterBitmap(true);

mBurstRect = new Rect();

burstBitmapsArray = new Bitmap[burstDrawablesArray.length];

for (int i = 0; i < burstDrawablesArray.length; i++) {

//将气泡爆炸的drawable转为bitmap

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), burstDrawablesArray[i]);

burstBitmapsArray[i] = bitmap;

}

}

private void initView(int w, int h) {

//设置两气泡圆心初始坐标

if (mBubStillCenter == null) {

mBubStillCenter = new PointF(w / 2, h / 2);

} else {

mBubStillCenter.set(w / 2, h / 2);

}

//设置动点坐标

if (mBubMoveableCenter == null) {

mBubMoveableCenter = new PointF(w / 2, h / 2);

} else {

mBubMoveableCenter.set(w / 2, h / 2);

}

current_state = STATE_DEFAULT;

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

//当 当前状态 != 消失, 则, 画 移动的气泡 和文字

if (current_state != STATE_DISMISS) {

canvas.drawCircle(mBubMoveableCenter.x,mBubMoveableCenter.y,

mBubMoveableRadius,mBubblePaint);

mTextPaint.getTextBounds(bubble_text, 0, bubble_text.length(), mTextRect);

canvas.drawText(bubble_text, mBubMoveableCenter.x - mTextRect.width() / 2, mBubMoveableCenter.y + mTextRect.height() / 2, mTextPaint);

}

//2.相连的气泡状态,绘制贝塞尔曲线

if (current_state == STATE_CONECTED) {

//1.静止气泡

canvas.drawCircle(mBubStillCenter.x, mBubStillCenter.y, mBubStillRadius, mBubblePaint);

//重置path状态

mBezierPath.reset();

//2.相连曲线---贝塞尔曲线

//计算控制点坐标---两个球圆心的中点.

int iAnchorX = (int) ((mBubStillCenter.x + mBubMoveableCenter.x) / 2);

int iAnchorY = (int) ((mBubStillCenter.y + mBubMoveableCenter.y) / 2);

//计算 三角函数

float cosTheta = (mBubStillCenter.x - mBubMoveableCenter.x)/bubbleDistance;

float sinTheta = (mBubStillCenter.y - mBubMoveableCenter.y)/bubbleDistance;

//画上半弧 B-->A---------------

//A点静坐标计算

float mBubStillAX = mBubStillCenter.x -mBubStillRadius*sinTheta;

float mBubStillAY = mBubStillCenter.y +mBubStillRadius*cosTheta;

//B点动坐标计算

float mBubMoveBX = mBubMoveableCenter.x -mBubMoveableRadius*sinTheta;

float mBubMoveBY = mBubMoveableCenter.y +mBubMoveableRadius*cosTheta;

mBezierPath.moveTo(mBubStillAX,mBubStillAY);

mBezierPath.quadTo(iAnchorX,iAnchorY,mBubMoveBX,mBubMoveBY);

//画下半弧 C-->D-------------

//C点动坐标计算

float mBubMoveCX = mBubMoveableCenter.x +mBubMoveableRadius*sinTheta;

float mBubMoveCY = mBubMoveableCenter.y -mBubMoveableRadius*cosTheta;

//D点静坐标计算

float mBubStillDX = mBubStillCenter.x +mBubStillRadius*sinTheta;

float mBubStillDY = mBubStillCenter.y -mBubStillRadius*cosTheta;

mBezierPath.lineTo(mBubMoveCX,mBubMoveCY);

mBezierPath.quadTo(iAnchorX,iAnchorY,mBubStillDX,mBubStillDY);

//释放资源,并画----------

mBezierPath.close();

canvas.drawPath(mBezierPath,mBubblePaint);

}

//boom,爆炸状态, 爆炸效果动画

if(isBurstAnimStart){

mBurstRect.set((int)(mBubMoveableCenter.x - mBubMoveableRadius),

(int)(mBubMoveableCenter.y - mBubMoveableRadius),

(int)(mBubMoveableCenter.x + mBubMoveableRadius),

(int)(mBubMoveableCenter.y + mBubMoveableRadius));

canvas.drawBitmap(burstBitmapsArray[curDrawableIndex],null,

mBurstRect,mBubblePaint); }

}

/**

* 重置状态

*/

public void reset(){

initView(getWidth(),getHeight());

invalidate();

}

/**

* 开始气泡 重置状态 的动画

*/

private void startBubbleRestAnim() {

ValueAnimator anim = ValueAnimator.ofObject(new PointFEvaluator(),

new PointF(mBubMoveableCenter.x,mBubMoveableCenter.y),

new PointF(mBubStillCenter.x,mBubStillCenter.y));

anim.setDuration(200);

anim.setInterpolator(new OvershootInterpolator(5f));

anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator animation) {

mBubMoveableCenter = (PointF) animation.getAnimatedValue();

invalidate();

}

});

anim.addListener(new AnimatorListenerAdapter() {

@Override

public void onAnimationEnd(Animator animation) {

current_state= STATE_DEFAULT;

}

});

anim.start();

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

initView(w, h);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()){

//按下, 如果 当前状态 没有消失, 则计算 当前气泡距离,和 最大允许偏移量 作比较 ,进行状态切换

case MotionEvent.ACTION_DOWN:

if(current_state!=STATE_DISMISS){

bubbleDistance=(float) Math.hypot(event.getX()-mBubStillCenter.x,event.getY()-mBubStillCenter.y);

if (bubbleDistance

current_state=STATE_CONECTED;

}else {

current_state=STATE_DEFAULT;

}

}

break;

//移动 ,改变动点的位置, 计算两点距离, 改变 静止点的半径,切换状态,并重绘制,调用ondraw

case MotionEvent.ACTION_MOVE:

{

if(current_state != STATE_DEFAULT){

mBubMoveableCenter.x = event.getX();

mBubMoveableCenter.y = event.getY();

bubbleDistance = (float) Math.hypot(event.getX() - mBubStillCenter.x,

event.getY() - mBubStillCenter.y);

if(current_state == STATE_CONECTED){

// 减去MOVE_OFFSET是为了让不动气泡半径到一个较小值时就直接消失

// 或者说是进入分离状态

if(bubbleDistance < maxDistance - MOVE_OFFSET){

mBubStillRadius = bubble_radius - bubbleDistance / 8;

}else{

current_state = STATE_APART;

}

}

invalidate();

}

}

break;

//抬起手, 根据 链接 状态, 开始进行 动画

case MotionEvent.ACTION_UP:

{

if(current_state == STATE_CONECTED){

startBubbleRestAnim();

}else if(current_state == STATE_APART){

if(bubbleDistance < 2 * bubble_radius){

startBubbleRestAnim();

}else{

startBubbleBurstAnim();

}

}

}

break;

}

return true;

}

/**

* 气泡消失动画

*/

private void startBubbleBurstAnim() {

//气泡改为消失状态

current_state = STATE_DISMISS;

isBurstAnimStart = true;

//做一个int型属性动画,从0~mBurstDrawablesArray.length结束

ValueAnimator anim = ValueAnimator.ofInt(0, burstDrawablesArray.length);

anim.setInterpolator(new LinearInterpolator());

anim.setDuration(500);

anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator animation) {

//设置当前绘制的爆炸图片index

curDrawableIndex = (int) animation.getAnimatedValue();

invalidate();

}

});

anim.addListener(new AnimatorListenerAdapter() {

@Override

public void onAnimationEnd(Animator animation) {

//修改动画执行标志

isBurstAnimStart = false;

}

});

anim.start();

}

public DragBuubbleView(Context context) {

super(context);

init(context, null);

}

public DragBuubbleView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

init(context, attrs);

}

public DragBuubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init(context, attrs);

}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)

public DragBuubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {

super(context, attrs, defStyleAttr, defStyleRes);

init(context, attrs);

}

}

代码下载:

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值