[Android]仿新版QQ的tab下面拖拽标记为已读的效果

时间:2021-03-15 12:37:25

以下内容为原创,欢迎转载,转载请注明

来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/4182929.html

可拖拽的红点,(仿新版QQ,tab下面拖拽标记为已读的效果),拖拽一定的距离可以消失回调。

[Android]仿新版QQ的tab下面拖拽标记为已读的效果

[Android]仿新版QQ的tab下面拖拽标记为已读的效果 [Android]仿新版QQ的tab下面拖拽标记为已读的效果

GitHub:DraggableFlagViewhttps://github.com/wangjiegulu/DraggableFlagView

实现原理:

当根据touch事件的移动,不断调用onDraw()方法进行刷新绘制。

*注意:这里原来的小红点称为红点A;根据手指移动绘制的小红点称为红点B。

touch事件移动的时候需要处理的逻辑:

1. 红点A的半径根据滑动的距离会不断地变小。

2. 红点B会紧随手指的位置移动。

3. 在红点A和红点B之间需要用贝塞尔曲线绘制连接区域。

4. 如果红点A和红点B之间的间距达到了设置的最大的距离,则表示,这次的拖拽会有效,一旦放手红点就会消失。

5. 如果达到了第4中情况,则红点A和中间连接的贝塞尔曲线不会被绘制。

6. 如果红点A和红点B之间的距离没有达到设置的最大的距离,则放手后,红点B消失,红点A从原来变小的半径使用反弹动画变换到原来最初的状态

一些工具类需要依赖 AndroidBucket(https://github.com/wangjiegulu/AndroidBucket),nineoldandroid

使用方式:

<com.wangjie.draggableflagview.DraggableFlagView
       xmlns:dfv="http://schemas.android.com/apk/res/com.wangjie.draggableflagview"
android:id="@+id/main_dfv"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentBottom="true"
android:layout_margin="15dp"
dfv:color="#FF3B30"
/>
 public class MainActivity extends Activity implements DraggableFlagView.OnDraggableFlagViewListener, View.OnClickListener {

     @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViewById(R.id.main_btn).setOnClickListener(this); DraggableFlagView draggableFlagView = (DraggableFlagView) findViewById(R.id.main_dfv);
draggableFlagView.setOnDraggableFlagViewListener(this);
draggableFlagView.setText("7");
} @Override
public void onFlagDismiss(DraggableFlagView view) {
Toast.makeText(this, "onFlagDismiss", Toast.LENGTH_SHORT).show();
} @Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.main_btn:
Toast.makeText(this, "hello world", Toast.LENGTH_SHORT).show();
break;
}
}
}

DraggableFlagView代码:

 /**
* Author: wangjie
* Email: tiantian.china.2@gmail.com
* Date: 12/23/14.
*/
public class DraggableFlagView extends View {
private static final String TAG = DraggableFlagView.class.getSimpleName(); public static interface OnDraggableFlagViewListener {
/**
* 拖拽销毁圆点后的回调
*
* @param view
*/
void onFlagDismiss(DraggableFlagView view);
} private OnDraggableFlagViewListener onDraggableFlagViewListener; public void setOnDraggableFlagViewListener(OnDraggableFlagViewListener onDraggableFlagViewListener) {
this.onDraggableFlagViewListener = onDraggableFlagViewListener;
} public DraggableFlagView(Context context) {
super(context);
init(context);
} private int patientColor = Color.RED; public DraggableFlagView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DraggableFlagView);
int indexCount = a.getIndexCount();
for (int i = 0; i < indexCount; i++) {
int attrIndex = a.getIndex(i);
if (attrIndex == R.styleable.DraggableFlagView_color) {
patientColor = a.getColor(attrIndex, Color.RED);
}
}
a.recycle();
init(context);
} public DraggableFlagView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
} private Context context;
private int originRadius; // 初始的圆的半径
private int originWidth;
private int originHeight; private int maxMoveLength; // 最大的移动拉长距离
private boolean isArrivedMaxMoved; // 达到了最大的拉长距离(松手可以触发事件) private int curRadius; // 当前点的半径
private int touchedPointRadius; // touch的圆的半径
private Point startPoint = new Point();
private Point endPoint = new Point(); private Paint paint; // 绘制圆形图形
private Paint textPaint; // 绘制圆形图形
private Paint.FontMetrics textFontMetrics; private int[] location; private boolean isTouched; // 是否是触摸状态 private Triangle triangle = new Triangle(); private String text = ""; // 正常状态下显示的文字 private void init(Context context) {
this.context = context; setBackgroundColor(Color.TRANSPARENT); // 设置绘制flag的paint
paint = new Paint();
paint.setColor(patientColor);
paint.setAntiAlias(true); // 设置绘制文字的paint
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(ABTextUtil.sp2px(context, 12));
textPaint.setTextAlign(Paint.Align.CENTER);
textFontMetrics = paint.getFontMetrics(); } RelativeLayout.LayoutParams originLp; // 实际的layoutparams
RelativeLayout.LayoutParams newLp; // 触摸时候的LayoutParams private boolean isFirst = true; @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Logger.d(TAG, String.format("onSizeChanged, w: %s, h: %s, oldw: %s, oldh: %s", w, h, oldw, oldh));
if (isFirst && w > 0 && h > 0) {
isFirst = false; originWidth = w;
originHeight = h; originRadius = Math.min(originWidth, originHeight) / 2;
curRadius = originRadius;
touchedPointRadius = originRadius; maxMoveLength = ABAppUtil.getDeviceHeight(context) / 6; refreshStartPoint(); ViewGroup.LayoutParams lp = this.getLayoutParams();
if (RelativeLayout.LayoutParams.class.isAssignableFrom(lp.getClass())) {
originLp = (RelativeLayout.LayoutParams) lp;
}
newLp = new RelativeLayout.LayoutParams(lp.width, lp.height);
} } @Override
public void setLayoutParams(ViewGroup.LayoutParams params) {
super.setLayoutParams(params);
refreshStartPoint();
} /**
* 修改layoutParams后,需要重新设置startPoint
*/
private void refreshStartPoint() {
location = new int[2];
this.getLocationInWindow(location);
// Logger.d(TAG, "location on screen: " + Arrays.toString(location));
// startPoint.set(location[0], location[1] + h);
try {
location[1] = location[1] - ABAppUtil.getTopBarHeight((Activity) context);
} catch (Exception ex) {
} startPoint.set(location[0], location[1] + getMeasuredHeight());
// Logger.d(TAG, "startPoint: " + startPoint);
} Path path = new Path(); @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.TRANSPARENT); int startCircleX = 0, startCircleY = 0;
if (isTouched) { // 触摸状态 startCircleX = startPoint.x + curRadius;
startCircleY = startPoint.y - curRadius;
// 绘制原来的圆形(触摸移动的时候半径会不断变化)
canvas.drawCircle(startCircleX, startCircleY, curRadius, paint);
// 绘制手指跟踪的圆形
int endCircleX = endPoint.x;
int endCircleY = endPoint.y;
canvas.drawCircle(endCircleX, endCircleY, originRadius, paint); if (!isArrivedMaxMoved) { // 没有达到拉伸最大值
path.reset();
double sin = triangle.deltaY / triangle.hypotenuse;
double cos = triangle.deltaX / triangle.hypotenuse; // A点
path.moveTo(
(float) (startCircleX - curRadius * sin),
(float) (startCircleY - curRadius * cos)
);
// B点
path.lineTo(
(float) (startCircleX + curRadius * sin),
(float) (startCircleY + curRadius * cos)
);
// C点
path.quadTo(
(startCircleX + endCircleX) / 2, (startCircleY + endCircleY) / 2,
(float) (endCircleX + originRadius * sin), (float) (endCircleY + originRadius * cos)
);
// D点
path.lineTo(
(float) (endCircleX - originRadius * sin),
(float) (endCircleY - originRadius * cos)
);
// A点
path.quadTo(
(startCircleX + endCircleX) / 2, (startCircleY + endCircleY) / 2,
(float) (startCircleX - curRadius * sin), (float) (startCircleY - curRadius * cos)
);
canvas.drawPath(path, paint);
} } else { // 非触摸状态
if (curRadius > 0) {
startCircleX = curRadius;
startCircleY = originHeight - curRadius;
canvas.drawCircle(startCircleX, startCircleY, curRadius, paint);
if (curRadius == originRadius) { // 只有在恢复正常的情况下才显示文字
// 绘制文字
float textH = textFontMetrics.bottom - textFontMetrics.top;
canvas.drawText(text, startCircleX, startCircleY + textH / 2, textPaint);
// canvas.drawText(text, startCircleX, startCircleY, textPaint);
}
} } // Logger.d(TAG, "circleX: " + startCircleX + ", circleY: " + startCircleY + ", curRadius: " + curRadius); } float downX = Float.MAX_VALUE;
float downY = Float.MAX_VALUE; @Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
// Logger.d(TAG, "onTouchEvent: " + event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isTouched = true;
this.setLayoutParams(newLp);
endPoint.x = (int) downX;
endPoint.y = (int) downY; changeViewHeight(this, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
postInvalidate(); downX = event.getX() + location[0];
downY = event.getY() + location[1];
// Logger.d(TAG, String.format("downX: %f, downY: %f", downX, downY)); break;
case MotionEvent.ACTION_MOVE:
// 计算直角边和斜边(用于计算绘制两圆之间的填充去)
triangle.deltaX = event.getX() - downX;
triangle.deltaY = -1 * (event.getY() - downY); // y轴方向相反,所有需要取反
double distance = Math.sqrt(triangle.deltaX * triangle.deltaX + triangle.deltaY * triangle.deltaY);
triangle.hypotenuse = distance;
// Logger.d(TAG, "triangle: " + triangle);
refreshCurRadiusByMoveDistance((int) distance); endPoint.x = (int) event.getX();
endPoint.y = (int) event.getY(); postInvalidate(); break;
case MotionEvent.ACTION_UP:
isTouched = false;
this.setLayoutParams(originLp); if (isArrivedMaxMoved) { // 触发事件
changeViewHeight(this, originWidth, originHeight);
postInvalidate();
if (null != onDraggableFlagViewListener) {
onDraggableFlagViewListener.onFlagDismiss(this);
}
Logger.d(TAG, "触发事件...");
resetAfterDismiss();
} else { // 还原
changeViewHeight(this, originWidth, originHeight);
startRollBackAnimation(500/*ms*/);
} downX = Float.MAX_VALUE;
downY = Float.MAX_VALUE;
break;
} return true;
} /**
* 触发事件之后重置
*/
private void resetAfterDismiss() {
this.setVisibility(GONE);
text = "";
isArrivedMaxMoved = false;
curRadius = originRadius;
postInvalidate();
} /**
* 根据移动的距离来刷新原来的圆半径大小
*
* @param distance
*/
private void refreshCurRadiusByMoveDistance(int distance) {
if (distance > maxMoveLength) {
isArrivedMaxMoved = true;
curRadius = 0;
} else {
isArrivedMaxMoved = false;
float calcRadius = (1 - 1f * distance / maxMoveLength) * originRadius;
float maxRadius = ABTextUtil.dip2px(context, 2);
curRadius = (int) Math.max(calcRadius, maxRadius);
// Logger.d(TAG, "[refreshCurRadiusByMoveDistance]curRadius: " + curRadius + ", calcRadius: " + calcRadius + ", maxRadius: " + maxRadius);
} } /**
* 改变某控件的高度
*
* @param view
* @param height
*/
private void changeViewHeight(View view, int width, int height) {
ViewGroup.LayoutParams lp = view.getLayoutParams();
if (null == lp) {
lp = originLp;
}
lp.width = width;
lp.height = height;
view.setLayoutParams(lp);
} /**
* 回滚状态动画
*/
private ValueAnimator rollBackAnim; private void startRollBackAnimation(long duration) {
if (null == rollBackAnim) {
rollBackAnim = ValueAnimator.ofFloat(curRadius, originRadius);
rollBackAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
curRadius = (int) value;
postInvalidate();
}
});
rollBackAnim.setInterpolator(new BounceInterpolator()); // 反弹效果
rollBackAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
DraggableFlagView.this.clearAnimation();
}
});
}
rollBackAnim.setDuration(duration);
rollBackAnim.start();
} /**
* 计算四个坐标的三角边关系
*/
class Triangle {
double deltaX;
double deltaY;
double hypotenuse; @Override
public String toString() {
return "Triangle{" +
"deltaX=" + deltaX +
", deltaY=" + deltaY +
", hypotenuse=" + hypotenuse +
'}';
}
} public String getText() {
return text;
} public void setText(String text) {
this.text = text;
postInvalidate();
}
}