qq小红点可以拖动相信大家已经玩过了,用户体验也非常的好.今天就来搞个demo出来,相信看完这个博文大家一定能自己实现下. 效果很不错
效果图!
思路:
小红点是可以拖拽到原来的控件外面的, 那么就在点击下去之后在它的最外层添加一个覆盖层来在小红点所在位置绘制小红点的拖拽效果
为了方便,我们选择继承TextView来实现这个控件, 当然直接继承View也是可以的,不过要定义下字体大小颜色等属性, 这里我们选择直接继承TextView省去那些步骤.
用一个接口回调当拖拽完小红点消失后的事件的监听
当然拖拽需要对OnTouch事件进行监听, 我们还要重写它的 onTouchEvent方法
好了,结构差不多就这些了 下来开始代码:
public boolean onTouchEvent(MotionEvent event) {
View root = getRootView();
if (root == null || !(root instanceof ViewGroup)) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
root.getLocationOnScreen(p);
scrollParent = getScrollParent();
if (scrollParent != null) {
scrollParent.requestDisallowInterceptTouchEvent(true);
}
int location[] = new int[2];
getLocationOnScreen(location);
x = location[0] + (getWidth() / 2) - p[0];
y = location[1] + (getHeight() / 2) - p[1];
r = (getWidth() + getHeight()) / 4;
pointView = new PointView(getContext());
pointView.setLayoutParams(new ViewGroup.LayoutParams(root.getWidth(), root.getHeight()));
setDrawingCacheEnabled(true);
pointView.catchBitmap = getDrawingCache();
pointView.setLocation(x, y, r, event.getRawX() - p[0], event.getRawY() - p[1]);
((ViewGroup) root).addView(pointView);
setVisibility(View.INVISIBLE);
break;
case MotionEvent.ACTION_MOVE:
pointView.refrashXY(event.getRawX() - p[0], event.getRawY() - p[1]);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (scrollParent != null) {
scrollParent.requestDisallowInterceptTouchEvent(false);
}
if (!pointView.broken) { // 没有拉断
pointView.cancel();
} else if (pointView.nearby) {// 拉断了,但是又回去了
pointView.cancel();
} else { // 彻底拉断了
pointView.broken();
}
break;
default:
break;
}
return true;
}
我们在down事件记录到小红点的位置, 然后拿到最顶部的父布局, 在获取到父布局的位置, 两者计算出小红点所在位置, 生成一个pointView加入到父布局中, 再将原来的视图隐藏
move事件里面计算出两个点, 中间画一下鼻涕一样的东西将两个点连接起来
up和cancel的时候判断是否拉断, 这里直接仿QQ的逻辑
这段代码表示记录下原来的控件长什么样子到时候把这个样子在添加的视图中绘制出来就可以了
setDrawingCacheEnabled(true);
pointView.catchBitmap = getDrawingCache();
当然在父布局中还要判断下父控件中有没有listview等,要不然在你拉动的过程很可能就被cancel掉了,要用
scrollParent.requestDisallowInterceptTouchEvent(true);这段代码阻止父布局拦截事件.
下面这个代码就是我们往父布局添加的视图, 一般情况下最顶层的父布局都是帧布局,直接addView就可以了
class PointView extends View {
private Bitmap catchBitmap;
private Circle c1;
private Circle c2;
private Paint paint;
private Path path = new Path();
private int maxDistance = 10; // 10倍半径距离视为拉断
private boolean broken; // 是否拉断过
private boolean out; // 放手的时候是否拉断
private boolean nearby;
private int brokenProgress;
public PointView(Context context) {
super(context);
init();
}
public void init() {
paint = new Paint();
paint.setColor(backgroundColor);
paint.setAntiAlias(true);
}
public void setLocation(float c1X, float c1Y, float r, float endX, float endY) {
broken = false;
c1 = new Circle(c1X, c1Y, r);
c2 = new Circle(endX, endY, r);
}
public void refrashXY(float x, float y) {
c2.x = x;
c2.y = y;
// 以前的半径应该根据距离缩小点了
// 计算出距离
double distance = c1.getDistance(c2);
int rate = 10;
c1.r = (float) ((c2.r * c2.r * rate) / (distance + (c2.r * rate)));
Log.i("info", "c1: " + c1.r);
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.TRANSPARENT);
if (out) {
float dr = c2.r / 2 + c2.r * 4 * (brokenProgress / 100f);
Log.i("info", "dr" + dr);
canvas.drawCircle(c2.x, c2.y, c2.r / (brokenProgress + 1), paint);
canvas.drawCircle(c2.x - dr, c2.y - dr, c2.r / (brokenProgress + 2), paint);
canvas.drawCircle(c2.x + dr, c2.y - dr, c2.r / (brokenProgress + 2), paint);
canvas.drawCircle(c2.x - dr, c2.y + dr, c2.r / (brokenProgress + 2), paint);
canvas.drawCircle(c2.x + dr, c2.y + dr, c2.r / (brokenProgress + 2), paint);
} else {
// 绘制手指跟踪的圆形
canvas.drawBitmap(catchBitmap, c2.x - c2.r, c2.y - c2.r, paint);
path.reset();
float deltaX = c2.x - c1.x;
float deltaY = -(c2.y - c1.y);
double distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
double sin = deltaY / distance;
double cos = deltaX / distance;
nearby = distance < c2.r * maxDistance;
if (nearby && !broken) {
canvas.drawCircle(c1.x, c1.y, c1.r, paint);
path.moveTo((float) (c1.x - c1.r * sin), (float) (c1.y - c1.r * cos));
path.lineTo((float) (c1.x + c1.r * sin), (float) (c1.y + c1.r * cos));
path.quadTo((c1.x + c2.x) / 2, (c1.y + c2.y) / 2, (float) (c2.x + c2.r * sin), (float) (c2.y + c2.r
* cos));
path.lineTo((float) (c2.x - c2.r * sin), (float) (c2.y - c2.r * cos));
path.quadTo((c1.x + c2.x) / 2, (c1.y + c2.y) / 2, (float) (c1.x - c1.r * sin), (float) (c1.y - c1.r
* cos));
canvas.drawPath(path, paint);
} else {
broken = true; // 已经拉断了
}
}
}
public void cancel() {
int duration = 150;
AnimatorSet set = new AnimatorSet();
ValueAnimator animx = ValueAnimator.ofFloat(c2.x, c1.x);
animx.setDuration(duration);
animx.setInterpolator(new OvershootInterpolator(2));
animx.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
c2.x = (float) animation.getAnimatedValue();
invalidate();
}
});
ValueAnimator animy = ValueAnimator.ofFloat(c2.y, c1.y);
animy.setDuration(duration);
animy.setInterpolator(new OvershootInterpolator(2));
animy.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
c2.y = (float) animation.getAnimatedValue();
invalidate();
}
});
set.playTogether(animx, animy);
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
ViewGroup vg = (ViewGroup) PointView.this.getParent();
vg.removeView(PointView.this);
DragPointView.this.setVisibility(View.VISIBLE);
}
});
set.start();
}
public void broken() {
out = true;
int duration = 500;
ValueAnimator a = ValueAnimator.ofInt(0, 100);
a.setDuration(duration);
a.setInterpolator(new LinearInterpolator());
a.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
brokenProgress = (int) animation.getAnimatedValue();
invalidate();
}
});
a.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
ViewGroup vg = (ViewGroup) PointView.this.getParent();
vg.removeView(PointView.this);
}
});
a.start();
if (dragListencer != null) {
dragListencer.onDragOut();
}
}
class Circle {
float x;
float y;
float r;
public Circle(float x, float y, float r) {
this.x = x;
this.y = y;
this.r = r;
}
public double getDistance(Circle c) {
float deltaX = x - c.x;
float deltaY = y - c.y;
double distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
return distance;
}
}
}
用属性动画来做放手后的动画显示, 回弹效果用 OvershootInterpolator 实现, 爆炸效果我觉得应该用帧动画来实现,但是没有美工,自己写了个, 感觉还凑合吧.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int w = getMeasuredWidth();
int h = getMeasuredHeight();
if(w != h){ // 简单的将宽高搞成一样的,如果有更好的方法欢迎在我博客下方留言!
int x = Math.max(w, h);
setMeasuredDimension(x, x);
}
}
因为我们拉扯的时候是圆点进行拉扯的, 所以为了好看, 在这里把宽高强行搞成一样的
同理:
DragPointView.this.setBackgroundDrawable(createStateListDrawable((getHeight() > getWidth() ? getHeight() : getWidth()) / 2, backgroundColor));
背景用一个StateListDrawable来搞成圆的
基本就这些了, 喜欢的下载源码看吧~~~
源码地址