public class StickinessView1 extends View { Paint paint; Path path; private PathMeasure mPathMeasure; ValueAnimator valueAnimator; boolean isAnimation; PointF ponitF; float offset1=30; float offset2=30; public StickinessView1(Context context) { this(context, null); } public StickinessView1(Context context, AttributeSet attrs) { this(context, attrs, 0); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public StickinessView1(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initData(); } private void initData() { mPathMeasure = new PathMeasure(); path = new Path(); paint = new Paint(); paint.setColor(Color.RED); paint.setStyle(Paint.Style.FILL); valueAnimator=new ValueAnimator(); //设置回弹插值器 valueAnimator.setInterpolator(new BounceInterpolator()); valueAnimator.setDuration(1000); valueAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { //恢复原状 isAnimation=false; lastX1=200; lastY1=200; } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { isAnimation=true; ponitF= (PointF) animation.getAnimatedValue(); Log.e("tt", "onAnimationUpdate: ponitF"+ponitF.x+" x "+ponitF.y ); postInvalidate(); } }); } @Override public void draw(Canvas canvas) { super.draw(canvas); if(isAnimation) { drawAdhesionBody(canvas, 200, 200, 15f, offset1, ponitF.x, ponitF.y, 20f, offset2) ; }else{ drawAdhesionBody(canvas, 200, 200, 15f, offset1, lastX1, lastY1, 20f, offset2) ; } } /** * 画粘连体 * * @param cx1 圆心x1 * @param cy1 圆心y1 * @param r1 圆半径r1 * @param offset1 贝塞尔曲线偏移角度offset1 * @param cx2 圆心x2 * @param cy2 圆心y2 * @param r2 圆半径r2 * @param offset2 贝塞尔曲线偏移角度offset2 * @return */ public void drawAdhesionBody(Canvas canvas,float cx1, float cy1, float r1, float offset1, float cx2, float cy2, float r2, float offset2) { path.reset(); float dX = cx1 - cx2; float dY = cy1 - cy2; path.addCircle(cx1, cy1, r1, Path.Direction.CW); path.addCircle(cx2, cy2, r2, Path.Direction.CW); mPathMeasure.setPath(path, false); //canvas.drawPath(path, paint); 这样使用 发现判断不准确画的也不太对 canvas.drawCircle(cx1, cy1, r1,paint); canvas.drawCircle(cx2, cy2, r2,paint); /* 求三角函数 */ float degrees = (float) Math.toDegrees(Math.atan(Math.abs(cy2 - cy1) / Math.abs(cx2 - cx1))); /* 两条贝塞尔曲线的四个端点 */ float x1 = 0, y1 = 0, x2 = 0, y2 = 0, x3 = 0, y3 = 0, x4 = 0, y4 = 0; float position1[] = new float[2]; float position2[] = new float[2]; float position3[] = new float[2]; float position4[] = new float[2]; if(dX==0&&dY==0)return ; //圆2在圆1的右下角 包括正右方,包括正下方 if (dX <= 0 && dY <= 0) { Log.e("tt", "drawAdhesionBody: \t//圆2在圆1的右下角" ); mPathMeasure.getPosTan(mPathMeasure.getLength() * ((offset1 + degrees) / 360), position3, null); if (degrees <= offset1) { mPathMeasure.getPosTan(mPathMeasure.getLength() * ((360 - (offset1 - degrees)) / 360), position1, null); } else { mPathMeasure.getPosTan(mPathMeasure.getLength() * ((degrees - offset1) / 360), position1, null); } Log.e("tt", "drawAdhesionBody: mPathMeasure.getLength() "+mPathMeasure.getLength() ); mPathMeasure.nextContour(); Log.e("tt", "drawAdhesionBody: mPathMeasure.getLength() "+mPathMeasure.getLength() ); mPathMeasure.getPosTan(mPathMeasure.getLength() * ((180 + offset2 + degrees) / 360), position2, null); mPathMeasure.getPosTan(mPathMeasure.getLength() * ((180 + degrees-offset2 ) / 360), position4, null); } //圆2在圆1的左下角 包括正左方 else if (dX > 0 && dY <= 0) { Log.e("tt", "drawAdhesionBody: \t//圆2在圆1的左下角" ); mPathMeasure.getPosTan(mPathMeasure.getLength() * ((90 + offset1 +90- degrees) / 360), position3, null); mPathMeasure.getPosTan(mPathMeasure.getLength() * ((90 - offset1 + 90-degrees) / 360), position1, null); mPathMeasure.nextContour(); mPathMeasure.getPosTan(mPathMeasure.getLength() * ((270 - offset2 + 90-degrees) / 360), position4, null); if(degrees<=offset2) mPathMeasure.getPosTan(mPathMeasure.getLength() * (( offset2-degrees) / 360), position2, null); else mPathMeasure.getPosTan(mPathMeasure.getLength() * ((360- (degrees-offset2)) / 360), position2, null); } //圆2在圆1的左上角 包括正上方 else if (dX >= 0 && dY > 0) { Log.e("tt", "drawAdhesionBody: \t//圆2在圆1的左上角" ); mPathMeasure.getPosTan(mPathMeasure.getLength() * ((180 + offset1 + degrees) / 360), position3, null); mPathMeasure.getPosTan(mPathMeasure.getLength() * ((180 - offset1 + degrees) / 360), position1, null); mPathMeasure.nextContour(); if (degrees <= offset2) { mPathMeasure.getPosTan(mPathMeasure.getLength() * ((360 - offset2 + degrees) / 360), position4, null); } else { mPathMeasure.getPosTan(mPathMeasure.getLength() * ((degrees - offset2) / 360), position4, null); } mPathMeasure.getPosTan(mPathMeasure.getLength() * ((degrees + offset2) / 360), position2, null); } //圆2在圆1的右上角 else if (dX < 0 && dY > 0) { Log.e("tt", "drawAdhesionBody: \t//圆2在圆1的右上角" ); if (degrees <= offset1) mPathMeasure.getPosTan(mPathMeasure.getLength() * (( offset1-degrees) / 360), position3, null); else mPathMeasure.getPosTan(mPathMeasure.getLength() * (( 360-(degrees-offset1)) / 360), position3, null); mPathMeasure.getPosTan(mPathMeasure.getLength() * ((360-offset1 - degrees) / 360), position1, null); mPathMeasure.nextContour(); mPathMeasure.getPosTan(mPathMeasure.getLength() * ((180 - offset2 - degrees) / 360), position4, null); mPathMeasure.getPosTan(mPathMeasure.getLength() * ((90-degrees + offset2 + 90) / 360), position2, null); } /* 贝塞尔曲线的控制点 */ x1 = position1[0]; y1 = position1[1]; x2 = position2[0]; y2 = position2[1]; x3 = position3[0]; y3 = position3[1]; x4 = position4[0]; y4 = position4[1]; Log.e("tt", "drawAdhesionBody: x1, y1 , x2, y2 , , y3 , x4, y4;" + x1 +","+y1 +","+x2 +","+y2 +","+ x3 +","+ y3 +","+x4 +","+ y4 ); float anchorX1, anchorY1, anchorX2, anchorY2; /* 圆1大于圆2 */ if (r1 >r2) { anchorX1 = (x2 + x3) / 2; anchorY1 = (y2 + y3) / 2; anchorX2 = (x1 + x4) / 2; anchorY2 = (y1 + y4) / 2; } /* 圆1小于或等于圆2 */ else { anchorX1 = (x1 + x4) / 2; anchorY1 = (y1 + y4) / 2; anchorX2 = (x2 + x3) / 2; anchorY2 = (y2 + y3) / 2; } path.moveTo(x1, y1); path.quadTo(anchorX1, anchorY1, x2, y2); //path.lineTo(x2,y2); path.lineTo(x4, y4); //path.lineTo(x3,y3); path.quadTo(anchorX2, anchorY2, x3, y3); path.lineTo(x1, y1); canvas.drawPath(path,paint); //return path; } float cy2=200 ,cx2=200; float lastX=200,lastY=200; float lastX1=200,lastY1=200; @Override public boolean onTouchEvent(MotionEvent event) { cx2=event.getX(); cy2=event.getY(); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: lastX=cx2; lastY=cy2; break; case MotionEvent.ACTION_MOVE: float dx= cx2-lastX; float dy= cy2-lastY; if(Math.abs(dx)>0.2||Math.abs(dx)>0.2){ lastX1+=dx; lastY1+=dy; postInvalidate(); } break; case MotionEvent.ACTION_UP: valueAnimator.setObjectValues(new PointF(lastX1,lastY1),new PointF(200,200)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { valueAnimator.setEvaluator(new PointFEvaluator(new PointF())); } valueAnimator.start(); break; } lastX=cx2; lastY=cy2; return true; } }
这是完整的代码.主要的点是 运动时坐标的计算.
就拿
圆2在圆1的右下角 包括正右方,包括正下方来说吧 图:
开始的位置是从红点而且是顺时针 通过PathMeasure getPosTan的方法可以得到某一个长度的坐标和正切.
长度如何求呢.... 既然我们已经知道 旋转角度 那就可以 通过PathMeasure getLength方法获取圆1的总长度乘 (degree+offset)/360 就获取到了长度.
如: x3 y3 的坐标就是这样求的
mPathMeasure.getPosTan(mPathMeasure.getLength() * ((offset1 + degrees) / 360), position3, null);但x1 y1 的坐标是分情况的 如果 degree小于offset1 那么这角度是不是在360-offset1~0的范围.大于角度是不是degrees-offset1所以:
if (degrees <= offset1) { mPathMeasure.getPosTan(mPathMeasure.getLength() * ((360 - (offset1 - degrees)) / 360), position1, null); } else { mPathMeasure.getPosTan(mPathMeasure.getLength() * ((degrees - offset1) / 360), position1, null); }那么圆2计算也是一样的 在计算之前要调用 nextContour()跳到圆2的图形区域.
mPathMeasure.getPosTan(mPathMeasure.getLength() * ((180 + offset2 + degrees) / 360), position2, null); mPathMeasure.getPosTan(mPathMeasure.getLength() * ((180 + degrees-offset2 ) / 360), position4, null);其他的位置状态计算,稍微的画一下,分析就简单了.效果如图
参考:http://blog.csdn.net/z82367825/article/details/51599245 核心代码改写的
ps:通过pathmeasure计算某一个点坐标很简单.