前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >11.粘性控件

11.粘性控件

作者头像
六月的雨
发布2018-05-14 12:17:54
7270
发布2018-05-14 12:17:54
举报
文章被收录于专栏:Android开发指南Android开发指南

粘性控件 (对View的自定义)

* 应用场景: 未读提醒的清除

* 功能实现:

> 1. 画静态图 OK

> 2. 把静态的数值变成变量(计算得到真实的变量) OK 

> 3. 不断地修改变量, 重绘界面, 动起来了.

> 4. 功能分析:

    a. 拖拽超出范围,断开, 松手, 消失

    b. 拖拽超出范围,断开,放回去了,恢复

    c. 拖拽没超出范围, 松手,弹回去

240px-Bezier_2_big.gif
240px-Bezier_2_big.gif
240px-Bezier_3_big.gif
240px-Bezier_3_big.gif

没有布局:

MainActivity

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
/**
 * 粘性控件
 * @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

代码语言:javascript
复制
/**
 * 几何图形工具
 */
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;
 }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2015-12-12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档