粘性控件 (对View的自定义)
* 应用场景: 未读提醒的清除
* 功能实现:
> 1. 画静态图 OK
> 2. 把静态的数值变成变量(计算得到真实的变量) OK
> 3. 不断地修改变量, 重绘界面, 动起来了.
> 4. 功能分析:
a. 拖拽超出范围,断开, 松手, 消失
b. 拖拽超出范围,断开,放回去了,恢复
c. 拖拽没超出范围, 松手,弹回去
没有布局:
MainActivity
public class MainActivity extends Activity {
@Override
protected 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;
@Override
protected 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();
// 跳到点1
path.moveTo(mStickPoints[0].x, mStickPoints[0].y);
// 画曲线1 -> 2
path.quadTo(mControlPoint.x, mControlPoint.y, mDragPoints[0].x, mDragPoints[0].y);
// 画直线2 -> 3
path.lineTo(mDragPoints[1].x, mDragPoints[1].y);
// 画曲线3 -> 4
path.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.0f
float 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);
}
@Override
public 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() {
@Override
public void onAnimationUpdate(ValueAnimator mAnim) {
// 0.0 -> 1.0f
float 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();
}
@Override
protected 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;
}
}
附件列表
时间: 2024-10-16 03:29:52