* 应用场景: 未读提醒的清除
* 功能实现:
> 1. 画静态图 OK
> 2. 把静态的数值变成变量(计算得到真实的变量) OK
> 3. 不断地修改变量, 重绘界面, 动起来了.
> 4. 功能分析:
a. 拖拽超出范围,断开, 松手, 消失
b. 拖拽超出范围,断开,放回去了,恢复
c. 拖拽没超出范围, 松手,弹回去


没有布局:
MainActivity
public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(new GooView(MainActivity.this));}}
Utils
public class Utils {public static Toast mToast;public static void showToast(Context mContext, String msg) {if (mToast == null) {mToast = Toast.makeText(mContext, "", Toast.LENGTH_SHORT);}mToast.setText(msg);mToast.show();}/*** dip 转换成 px* @param dip* @param context* @return*/public static float dip2Dimension(float dip, Context context) {DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics);}/*** @param dip* @param context* @param complexUnit {@link TypedValue#COMPLEX_UNIT_DIP} {@link TypedValue#COMPLEX_UNIT_SP}}* @return*/public static float toDimension(float dip, Context context, int complexUnit) {DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();return TypedValue.applyDimension(complexUnit, dip, displayMetrics);}/** 获取状态栏高度* @param v* @return*/public static int getStatusBarHeight(View v) {if (v == null) {return 0;}Rect frame = new Rect();v.getWindowVisibleDisplayFrame(frame);return frame.top;}public static String getActionName(MotionEvent event) {String action = "unknow";switch (MotionEventCompat.getActionMasked(event)) {case MotionEvent.ACTION_DOWN:action = "ACTION_DOWN";break;case MotionEvent.ACTION_MOVE:action = "ACTION_MOVE";break;case MotionEvent.ACTION_UP:action = "ACTION_UP";break;case MotionEvent.ACTION_CANCEL:action = "ACTION_CANCEL";break;case MotionEvent.ACTION_SCROLL:action = "ACTION_SCROLL";break;case MotionEvent.ACTION_OUTSIDE:action = "ACTION_SCROLL";break;default:break;}return action;}}
GooView
/*** 粘性控件* @author poplar**/public class GooView extends View {private static final String TAG = "TAG";private Paint mPaint;public GooView(Context context) {this(context, null);}public GooView(Context context, AttributeSet attrs) {this(context, attrs , 0);}public GooView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// 做初始化操作mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mPaint.setColor(Color.RED);}PointF[] mStickPoints = new PointF[]{new PointF(250f, 250f),new PointF(250f, 350f)};PointF[] mDragPoints = new PointF[]{new PointF(50f, 250f),new PointF(50f, 350f)};PointF mControlPoint = new PointF(150f, 300f);PointF mDragCenter = new PointF(80f, 80f);float mDragRadius = 14f;PointF mStickCenter = new PointF(150f, 150f);float mStickRadius = 12f;private int statusBarHeight;float farestDistance = 80f;private boolean isOutofRange;private boolean isDisappear;@Overrideprotected void onDraw(Canvas canvas) {// 计算连接点值, 控制点, 固定圆半径// 1. 获取固定圆半径(根据两圆圆心距离)float tempStickRadius = getTempStickRadius();// 2. 获取直线与圆的交点float yOffset = mStickCenter.y - mDragCenter.y;float xOffset = mStickCenter.x - mDragCenter.x;Double lineK = null;if(xOffset != 0){lineK = (double) (yOffset / xOffset);}// 通过几何图形工具获取交点坐标mDragPoints = GeometryUtil.getIntersectionPoints(mDragCenter, mDragRadius, lineK);mStickPoints = GeometryUtil.getIntersectionPoints(mStickCenter, tempStickRadius, lineK);// 3. 获取控制点坐标mControlPoint = GeometryUtil.getMiddlePoint(mDragCenter, mStickCenter);// 保存画布状态canvas.save();canvas.translate(0, -statusBarHeight);// 画出最大范围(参考用)mPaint.setStyle(Style.STROKE);canvas.drawCircle(mStickCenter.x, mStickCenter.y, farestDistance, mPaint);mPaint.setStyle(Style.FILL);if(!isDisappear){if(!isOutofRange){// 3. 画连接部分Path path = new Path();// 跳到点1path.moveTo(mStickPoints[0].x, mStickPoints[0].y);// 画曲线1 -> 2path.quadTo(mControlPoint.x, mControlPoint.y, mDragPoints[0].x, mDragPoints[0].y);// 画直线2 -> 3path.lineTo(mDragPoints[1].x, mDragPoints[1].y);// 画曲线3 -> 4path.quadTo(mControlPoint.x, mControlPoint.y, mStickPoints[1].x, mStickPoints[1].y);path.close();canvas.drawPath(path, mPaint);// 画附着点(参考用)mPaint.setColor(Color.BLUE);canvas.drawCircle(mDragPoints[0].x, mDragPoints[0].y, 3f, mPaint);canvas.drawCircle(mDragPoints[1].x, mDragPoints[1].y, 3f, mPaint);canvas.drawCircle(mStickPoints[0].x, mStickPoints[0].y, 3f, mPaint);canvas.drawCircle(mStickPoints[1].x, mStickPoints[1].y, 3f, mPaint);mPaint.setColor(Color.RED);// 2. 画固定圆canvas.drawCircle(mStickCenter.x, mStickCenter.y, tempStickRadius, mPaint);}// 1. 画拖拽圆canvas.drawCircle(mDragCenter.x, mDragCenter.y, mDragRadius, mPaint);}// 恢复上次的保存状态canvas.restore();}// 获取固定圆半径(根据两圆圆心距离)private float getTempStickRadius() {float distance = GeometryUtil.getDistanceBetween2Points(mDragCenter, mStickCenter);// if(distance> farestDistance){// distance = farestDistance;// }distance = Math.min(distance, farestDistance);// 0.0f -> 1.0ffloat percent = distance / farestDistance;Log.d(TAG, "percent: " + percent);// percent , 100% -> 20%return evaluate(percent, mStickRadius, mStickRadius * 0.2f);}public Float evaluate(float fraction, Number startValue, Number endValue) {float startFloat = startValue.floatValue();return startFloat + fraction * (endValue.floatValue() - startFloat);}@Overridepublic boolean onTouchEvent(MotionEvent event) {float x;float y;switch (event.getAction()) {case MotionEvent.ACTION_DOWN:isOutofRange = false;isDisappear = false;x = event.getRawX();y = event.getRawY();updateDragCenter(x, y);break;case MotionEvent.ACTION_MOVE:x = event.getRawX();y = event.getRawY();updateDragCenter(x, y);// 处理断开事件float distance = GeometryUtil.getDistanceBetween2Points(mDragCenter, mStickCenter);if(distance > farestDistance){isOutofRange = true;invalidate();}break;case MotionEvent.ACTION_UP:if(isOutofRange){float d = GeometryUtil.getDistanceBetween2Points(mDragCenter, mStickCenter);if(d > farestDistance){// a. 拖拽超出范围,断开, 松手, 消失isDisappear = true;invalidate();}else {//b. 拖拽超出范围,断开,放回去了,恢复updateDragCenter(mStickCenter.x, mStickCenter.y);}}else {// c. 拖拽没超出范围, 松手,弹回去final PointF tempDragCenter = new PointF(mDragCenter.x, mDragCenter.y);ValueAnimator mAnim = ValueAnimator.ofFloat(1.0f);mAnim.addUpdateListener(new AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator mAnim) {// 0.0 -> 1.0ffloat percent = mAnim.getAnimatedFraction();PointF p = GeometryUtil.getPointByPercent(tempDragCenter, mStickCenter, percent);updateDragCenter(p.x, p.y);}});mAnim.setInterpolator(new OvershootInterpolator(4));mAnim.setDuration(500);mAnim.start();}break;default:break;}return true;}/*** 更新拖拽圆圆心坐标,并重绘界面* @param x* @param y*/private void updateDragCenter(float x, float y) {mDragCenter.set(x, y);invalidate();}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);statusBarHeight = Utils.getStatusBarHeight(this);}}
GeometryUtil
/*** 几何图形工具*/public class GeometryUtil {/*** As meaning of method name.* 获得两点之间的距离* @param p0* @param p1* @return*/public static float getDistanceBetween2Points(PointF p0, PointF p1) {float distance = (float) Math.sqrt(Math.pow(p0.y - p1.y, 2) + Math.pow(p0.x - p1.x, 2));return distance;}/*** Get middle point between p1 and p2.* 获得两点连线的中点* @param p1* @param p2* @return*/public static PointF getMiddlePoint(PointF p1, PointF p2) {return new PointF((p1.x + p2.x) / 2.0f, (p1.y + p2.y) / 2.0f);}/*** Get point between p1 and p2 by percent.* 根据百分比获取两点之间的某个点坐标* @param p1* @param p2* @param percent* @return*/public static PointF getPointByPercent(PointF p1, PointF p2, float percent) {return new PointF(evaluateValue(percent, p1.x , p2.x), evaluateValue(percent, p1.y , p2.y));}/*** 根据分度值,计算从start到end中,fraction位置的值。fraction范围为0 -> 1* @param fraction* @param start* @param end* @return*/public static float evaluateValue(float fraction, Number start, Number end){return start.floatValue() + (end.floatValue() - start.floatValue()) * fraction;}/*** Get the point of intersection between circle and line.* 获取 通过指定圆心,斜率为lineK的直线与圆的交点。** @param pMiddle The circle center point.* @param radius The circle radius.* @param lineK The slope of line which cross the pMiddle.* @return*/public static PointF[] getIntersectionPoints(PointF pMiddle, float radius, Double lineK) {PointF[] points = new PointF[2];float radian, xOffset = 0, yOffset = 0;if(lineK != null){radian= (float) Math.atan(lineK);xOffset = (float) (Math.sin(radian) * radius);yOffset = (float) (Math.cos(radian) * radius);}else {xOffset = radius;yOffset = 0;}points[0] = new PointF(pMiddle.x + xOffset, pMiddle.y - yOffset);points[1] = new PointF(pMiddle.x - xOffset, pMiddle.y + yOffset);return points;}}
