网上已经有很多完善的粘性球控件了,但还是想自己写一个练习一下,只是实现了功能,还并不是很完善,学习的可以参考一下
废话不多说,上代码:
1、首先,显示出来的粘性球比较简单,用贝塞尔曲线画出来就可以了
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(mBallColor);
mPath.reset();
mPath.moveTo(mLTPoint.x, mLTPoint.y);
mPath.lineTo(mRBPoint.x, mLTPoint.y);
mRectF.set(mRBPoint.x - mContentHeight / 2, mLTPoint.y, mRBPoint.x + mContentHeight / 2, mRBPoint.y);
mPath.arcTo(mRectF, -90, 180);
mPath.lineTo(mLTPoint.x, mRBPoint.y);
mRectF.set(mLTPoint.x - mContentHeight / 2, mLTPoint.y, mLTPoint.x + mContentHeight / 2, mRBPoint.y);
mPath.arcTo(mRectF, 90, 180);
canvas.drawPath(mPath, mPaint);
mPaint.setColor(mTextColor);
canvas.drawText(mText, mBaseLinePoint.x, mBaseLinePoint.y, mPaint);
mPaint.setColor(mBallColor);
}
2、计算
这部分稍微麻烦一点,主要是计算连接部分贝塞尔曲线的各个点位置
/**
* 动圆移动的时候计算各个位置的值
*
* @param touchX
* @param touchY
*/
private void calculateSelf(float touchX, float touchY) {
mLTPoint.x = touchX - (mContentWidth - mContentHeight) / 2;
mLTPoint.y = touchY - mContentHeight / 2;
mRBPoint.x = touchX + (mContentWidth - mContentHeight) / 2;
mRBPoint.y = touchY + mContentHeight / 2;
mBaseLinePoint.set(getPaddingLeft() + (mContentWidth - mBounds.width()) / 2 + mLTPoint.x - mContentHeight / 2, getPaddingTop() + (mContentHeight + mBounds.height()) / 2 + mLTPoint.y);
mMCenter.set((mLTPoint.x + mRBPoint.x) / 2, (mLTPoint.y + mRBPoint.y) / 2);
double radian = Math.atan2(mMCenter.y - mQCenter.y, mMCenter.x - mQCenter.x);
// 计算静圆两点坐标
mQPoint1.x = (float) (mQCenter.x + mQRadius * Math.cos(radian - mQRadian / 2.0));
mQPoint1.y = (float) (mQCenter.y + mQRadius * Math.sin(radian - mQRadian / 2.0));
mQPoint2.x = (float) (mQCenter.x + mQRadius * Math.sin(Math.PI / 2.0 - radian - mQRadian / 2.0));
mQPoint2.y = (float) (mQCenter.y + mQRadius * Math.cos(Math.PI / 2.0 - radian - mQRadian / 2.0));
calculateMPoint();
// 控制点坐标
mControl1.x = (mQCenter.x + mMCenter.x) / 2;
mControl1.y = (mQCenter.y + mMCenter.y) / 2;
mControl2.x = mControl1.x;
mControl2.y = mControl1.y;
mCurrDistance = Math.sqrt(Math.pow(mQCenter.x - mMCenter.x, 2) + Math.pow(mQCenter.y - mMCenter.y, 2));
mQRadius = (int) ((mQOrigRadius - mQOrigRadius / 3.0) * (1 - mCurrDistance / mMaxDistance) + mQOrigRadius / 3.0);
if (mCurrDistance > mMaxDistance) {
mHasLeave = true;
}
}
/**
* 计算动圆上的两个贝塞尔曲线点
*/
private void calculateMPoint() {
double radian = Math.atan2(mMCenter.y - mQCenter.y, mQCenter.x - mMCenter.x);
double lineRadian1 = radian - mMRadian / 2;
if (lineRadian1 + Math.PI < 0) {
lineRadian1 += 2 * Math.PI;
}
double lineRadian2 = radian + mMRadian / 2;
if (lineRadian2 + Math.PI < 0) {
lineRadian2 += 2 * Math.PI;
}
if (lineRadian2 > Math.PI) {
lineRadian2 = -(2 * Math.PI - lineRadian2);
}
mMPoint1 = getIntersectionOfSelf(lineRadian1);
mMPoint2 = getIntersectionOfSelf(lineRadian2);
}
/**
* 根据直线角度获取该直线逻辑上需要的与拖拽物的交点
*
* @param lineRadian 直线角度
* @return 交点坐标
*/
private PointF getIntersectionOfSelf(double lineRadian) {
if (lineRadian >= mRadianRight && lineRadian <= mRadianLeft) {
// 相交于上方直线
return MathUtil.intersectionLine(-Math.tan(lineRadian), mMCenter, 0, mLTPoint);
} else if (lineRadian > mRadianLeft || lineRadian < -mRadianLeft) {
// 相交于左方半圆
PointF[] pointFS = MathUtil.intersectionCircle(-Math.tan(lineRadian), mMCenter, new PointF(mLTPoint.x, (mLTPoint.y + mRBPoint.y) / 2), mContentHeight / 2);
if (pointFS.length > 1) {
if (pointFS[0].x > pointFS[1].x) {
pointFS[0] = pointFS[1];
}
}
return pointFS[0];
} else if (lineRadian >= -mRadianLeft && lineRadian <= -mRadianRight) {
// 相交于下方直线
return MathUtil.intersectionLine(-Math.tan(lineRadian), mMCenter, 0, mRBPoint);
} else if (lineRadian < mRadianRight || lineRadian > -mRadianRight) {
// 相交于右方半圆
PointF[] pointFS = MathUtil.intersectionCircle(-Math.tan(lineRadian), mMCenter, new PointF(mRBPoint.x, (mLTPoint.y + mRBPoint.y) / 2), mContentHeight / 2);
if (pointFS.length > 1) {
mMPoint2 = pointFS[1];
if (pointFS[0].x < pointFS[1].x) {
pointFS[0] = pointFS[1];
}
}
return pointFS[0];
} else {
return null;
}
}
3、效果图
时间原因先不详细解释了,有问题的话欢迎询问,有什么bug也欢迎反馈会尽快解决,谢谢各位!
GitHub地址:https://github.com/qswjoker/StickBall.git