11.粘性控件

粘性控件 (对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;
 }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

android galley实现画廊效果

今天在做一个软件界面时用到了ImageSwitcher和Gallery控件,在看API时,感觉上面的例子讲的不是很具体,效率并不高。在这里我就以一个图片浏览功能...

2049
来自专栏程序员叨叨叨

【Android】手把手教你上滑解锁的效果

最近,公司开发的APP中要实现类似上滑解锁效果的推荐页,捣腾了两天,基本实现了效果,附效果图如上。接下来和大家聊聊如何实现这样的效果。

5592
来自专栏潇涧技术专栏

Android Heroes Reading Notes 2

《Android群英传》读书笔记 (2) 第三章 控件架构与自定义控件详解 + 第四章 ListView使用技巧 + 第五章 Scroll分析

991
来自专栏Felix的技术分享

Android ListView头部视差控件

1463
来自专栏青蛙要fly的专栏

项目需求讨论:截图—涂鸦—分享

这个也是具体项目中遇到的项目需求:需要在一个特定的界面中(都是图表和各种数据,可能需求分享给别人,告诉别人这个数据怎么怎么,这个图表怎么怎么) 所以给我们开发的...

1544
来自专栏向治洪

android移动view

import android.app.Activity;   import android.content.Context;   import android....

1787
来自专栏Winter漫聊技术

Android水波动画帮助类,一行代码实现View显示/隐藏/startActivity特效(0.3.1)

So,你可以如下compile该library了,也可以把这个类CircularAnim拷贝到项目里去。

1062
来自专栏向治洪

android 自定义gallerey并实现预览功能

自从Gallery被谷歌废弃以后,Google推荐使用ViewPager和HorizontalScrollView来实现Gallery的效果。的确Horizon...

2875
来自专栏开发之途

Android 仿360悬浮球与加速球

5388
来自专栏项勇

笔记 77 | Launcher表盘时间部件工具类(时,分,秒)

但是,发现这两种方法里都没有秒针的动画,方法一无法实现秒针,方法二由于是封装好的类,查看和修改起来不方便。

851

扫码关注云+社区

领取腾讯云代金券