自定义控件仿QQ小红点,粘性控件
1.实现自由拖拽
2.使用路径贝塞尔随手指拖动自由绘制
3.接口回调,实现监听事件
先放出效果图
有没有感觉像被拖拽的鼻屎
1.继承与View — so我们需要重写方法onMeasure()和onDraw().
感觉我不用多做解释考虑了。。。已经写了很详细了。卧槽
public GooView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GooView(Context context) {
this(context, null);
}
public GooView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 测量
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 绘图,作画
*
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.canvas = canvas;
//设定抗锯齿画笔
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
//画笔颜色
paint.setColor(Color.BLUE);
//固定圆的点
mStickCenter = new PointF(100f, 200f);
//固定的圆环半径
assetsAadius = 100f;
//固定圆的点半径
if (distanceBetween2Points > assetsAadius) {
mStickRodius = 4f;
} else {
mStickRodius = 10f - 6f * move2stickDistance / assetsAadius;
}
//画固定圆框
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(mStickCenter.x, mStickCenter.y, assetsAadius, paint);
paint.setStyle(Paint.Style.FILL);
//移动圆的圆心
mDragCenter = getmDragCenter();
//移动圆半径
mDragRodius = 15f;
//是否不再作画
if (!cleanAllDraw) {
//画出移动圆
canvas.drawCircle(mDragCenter.x, mDragCenter.y, mDragRodius, paint);
if (!beyondDistance) {//如果超出方位不再画贝塞尔曲线和原点
//贝塞尔曲线绘制
upDraw(canvas, paint);
//得到当前移动圆圆心与
//画固定圆
canvas.drawCircle(mStickCenter.x, mStickCenter.y, mStickRodius, paint);
}
//设定文本画笔
Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(Color.WHITE);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTextSize(10f);
//画固定圆上边写上文本...
canvas.drawText("A", mStickCenter.x, mStickCenter.y + mStickRodius * 0.3f, textPaint);
//画移动圆,并在上边写上文本...
canvas.drawText("B", mDragCenter.x, mDragCenter.y + mDragRodius * 0.3f, textPaint);
}
//重新设置为false;
beyondDistance = false;
cleanAllDraw = false;
}
/**
* 根据传入的移动圆的圆心开始作贝塞尔曲线,不管怎样画,最终的画会覆盖之前的画
*
* @param canvas 画布
* @param paint 画笔
*/
private void upDraw(Canvas canvas, Paint paint) {
//测到测量的两点圆中心点
PointF mMiddlePoint = GeometryUtil.getMiddlePoint(mStickCenter, mDragCenter);
//获得两圆圆心连线的斜率;
float yOffset = mStickCenter.y - mDragCenter.y;
float xOffset = mStickCenter.x - mDragCenter.x;
Double lineK = null;
if (xOffset != 0) {
lineK = (double) (yOffset / xOffset);
}
//****
paint.setColor(Color.RED);
//得到固定圆的两个附着点
PointF[] mStickPoints = GeometryUtil.getIntersectionPoints(mStickCenter, mStickRodius, lineK);
//得到移动圆的两个附着点
PointF[] mDragPoints = GeometryUtil.getIntersectionPoints(mDragCenter, mDragRodius, lineK);
//画出求出的四个点(测试用)
canvas.drawCircle(mStickPoints[0].x, mStickPoints[0].y, 2f, paint);
canvas.drawCircle(mStickPoints[1].x, mStickPoints[1].y, 2f, paint);
canvas.drawCircle(mDragPoints[0].x, mDragPoints[0].y, 2f, paint);
canvas.drawCircle(mDragPoints[1].x, mDragPoints[1].y, 2f, paint);
paint.setColor(Color.BLUE);
//绘制路径
Path path = new Path();
//移动到这个点
path.moveTo(mStickPoints[0].x, mStickPoints[0].y);
//画一半贝塞尔曲线
path.quadTo(mMiddlePoint.x, mMiddlePoint.y, mDragPoints[0].x, mDragPoints[0].y);
//画出直线
path.lineTo(mDragPoints[1].x, mDragPoints[1].y);
//画另一半贝塞尔曲线
path.quadTo(mMiddlePoint.x, mMiddlePoint.y, mStickPoints[1].x, mStickPoints[1].y);
//封闭路径
path.close();
//根据设定路径作画
canvas.drawPath(path, paint);
}
这里需要采用三角函数的技术,但是万能的开源,有一个数学工具类,
提出来给你们看看
package com.oblivion.utils;
import android.graphics.PointF;
/**
* 几何图形工具
*/
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;
}
}
很简单吧。我们已经能画出一帧图片来了,所以。我们现在子需要对触摸点进行判断,然后对事件经行处理,就能得出入图所示
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://记录按下点,作为移动圆圆心起始点
downX_dot = event.getX();
downY_dot = event.getY();
PointF down_PointF = new PointF(downX_dot, downY_dot);
//如果按下点的距离大于外圆环
distanceBetween2Points = GeometryUtil.getDistanceBetween2Points(down_PointF, mStickCenter);
setmDragCenter(new PointF(downX_dot, downY_dot));
invalidate();
state = State.touch;//状态修改为触摸状态
setStateCallBack();
break;
case MotionEvent.ACTION_MOVE:
moveX_dot = event.getX();
moveY_dot = event.getY();
//移动点所在坐标
PointF move_pointF = new PointF(moveX_dot, moveY_dot);
//抬起点与原点的距离
move2stickDistance = GeometryUtil.getDistanceBetween2Points(move_pointF, mStickCenter);
//判断移动点是否超出范围
beyondDistance = move2stickDistance > assetsAadius ? true : false;
setmDragCenter(new PointF(moveX_dot, moveY_dot));
invalidate();
setStateCallBack();
break;
case MotionEvent.ACTION_UP:
float upX_dot = event.getX();
float upY_dot = event.getY();
//抬起点所在坐标
up_pointF = new PointF(upX_dot, upY_dot);
//抬起点与原点的距离
up2stickDistance = GeometryUtil.getDistanceBetween2Points(up_pointF, mStickCenter);
//判断是否移动了
if (move2stickDistance > assetsAadius) {
cleanAllDraw = true;
invalidate();
state = State.remove;//清除状态
} else {
//开始动画
setAnimator();
//设定回到默认值
cleanAllDraw = false;
state = State.retain;//保留状态
}
move2stickDistance = 0;
setStateCallBack();
break;
}
return true;
}
事件完成里,大体框架能弄出来了,现在我们需要优化一下下,比如,我们需要什么时候将图片消失,社么时候存在,,这就。。。。
需要值动画进行处理
/**
* 设定值动画
*/
private void setAnimator() {
ValueAnimator va = ValueAnimator.ofFloat(1.0f);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float animatedFraction = animation.getAnimatedFraction();
//获取手指离开点的距离
PointF pointByPercent = GeometryUtil.getPointByPercent(up_pointF, mStickCenter, animatedFraction);
//动态设定距离
setmDragCenter(pointByPercent);
invalidate();
System.out.println(animatedFraction);
}
});
va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
state = State.retain;//保留状态
}
});
//重力回弹
va.setInterpolator(new OvershootInterpolator(4));
//时长
va.setDuration(500);
va.start();
}
最后写一下监听回调
/**
* 创建粘性控件的消息监听
*/
public void setOnMessageStateListener(onMessageStateListener listener) {
monMessageStateListener = listener;
}
/**
* 创建消息监听的回调
*/
public interface onMessageStateListener {
/**
* 正在操作消息
*/
void onTouchMessage();
/**
* 保留消息
*/
void onRetainMessage();
/**
* 清除消息
*/
void onRemoveMessage();
}
/**
* 响应接口回调
*/
private void setStateCallBack() {
if (monMessageStateListener != null) {
if (state == State.retain) {
monMessageStateListener.onRetainMessage();
} else if (state == State.remove) {
monMessageStateListener.onRemoveMessage();
} else {
monMessageStateListener.onTouchMessage();
}
}
}
贴出MainActivity,看一下,怎么调用监听
package com.oblivion.gooview;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import com.oblivion.ui.GooView;
import com.oblivion.utils.ToastUtils;
public class MainActivity extends AppCompatActivity {
private GooView view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = new GooView(this);
setContentView(view);
view.setOnMessageStateListener(new GooView.onMessageStateListener() {
@Override
public void onTouchMessage() {
ToastUtils.setToast(getApplicationContext(),"触摸");
}
@Override
public void onRetainMessage() {
ToastUtils.setToast(getApplicationContext(),"保留");
}
@Override
public void onRemoveMessage() {
ToastUtils.setToast(getApplicationContext(),"清除");
}
});
}
}