QQドラッグの小さい赤い点の原理とその実現を模倣します
qq小红点可以ドラッグ&ドロップみんなはすでに游んだことを信じて、ユーザーの体験もとても良いです.今日はdemoを作って、この博文を見終わったらみんながきっと自分で実現できると信じています.効果はとても良い効果図です!考え方:小さな赤い点は元のコントロールの外にドラッグすることができます.クリックした後、その最外層にカバー層を追加して、小さな赤い点の位置で小さな赤い点のドラッグ効果を描きます.便宜上、TextViewを継承してこのコントロールを実現することを選択し、もちろんViewを直接継承してもよいが、フォントサイズや色などの属性を定義するには、TextViewを直接継承することを選択し、それらのステップを省く. は、小さな赤い点が消えた後のイベントをドラッグした後のリスニング を1つのインタフェースでコールバックする.もちろんドラッグアンドドロップはOnTouchイベントをリスニングする必要があります.また、onTouchEventメソッド を書き換えます.
はい、構造の差は多くありません.これでコードを開始します.
私たちはdownイベントで小さな赤い点の位置を記録して、それから最上部の親レイアウトを手に入れて、親レイアウトの位置を取得して、両者は小さな赤い点の位置を計算して、pointViewを生成して親レイアウトに参加して、更に元のビューを隠してmoveイベントの中で2つの点を計算して、中間に鼻水のようなものを描いて2つの点をupとcancelに接続する時判断して、ここで直接QQの論理をまねます
もちろん、親レイアウトでは、親コントロールにlistviewなどがあるかどうかを判断します.そうしないと、引く過程でcancelに落ちてしまう可能性があります.scrollParentを使用します.requestDisallowInterceptTouchEvent(true);このコードは、親レイアウトのブロックイベントをブロックします.
次のコードは、親レイアウトに追加するビューです.一般的に、最上位の親レイアウトはフレームレイアウトで、直接addViewでいいです.
属性アニメーションで手放した后のアニメーション表示をして、リバウンド効果はOvershootInterpolatorで実现して、爆発効果は私はフレームアニメーションで実现すべきだと思って、しかし美工がなくて、自分で书いて、感じはまあまあでしょう.
私たちが引っ張るときは丸い点で引っ張るので、きれいにするために、ここで幅を強引に同じ理屈にします.
背景はStateListDrawableで丸くする
基本はこれだけですが、好きなソースをダウンロードしてみましょう~~~ソースアドレス
はい、構造の差は多くありません.これでコードを開始します.
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イベントの中で2つの点を計算して、中間に鼻水のようなものを描いて2つの点を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で丸くする
基本はこれだけですが、好きなソースをダウンロードしてみましょう~~~ソースアドレス