仿微信朋友圈图片拖拽到底部删除效果实现

2018-02-27 11:16:36来源:https://www.jianshu.com/p/f657bcd1f39d作者:FrankChoo人点击

分享


效果展示


拖拽删除.gif
效果分析
一组图片长按触发拖拽
原位置图片消失, 跟手拖动
拖动到底部时图片消失, 否则回弹原位置
实现思路
要想做到全屏拖动, 这里想到了之前实现的消息气泡拖拽
新建一个Window层级, 在其上面处理
当手指长按时, 触发添加一个新 Window 的操作
Duplicate 原位置的 View, 获取Bitmap
监听触摸事件, 在新的Window上动态的绘制这个 Bitmap
当手指松开的瞬间
手指松开的位置在底部时触发回调
未滑动到底部, 使用属性动画回弹回去


使用方式
// 与点击事件无冲突
mIvLauncher.setOnClickListener {
Toast.makeText(this, "perform OnClick", Toast.LENGTH_SHORT).show();
}
DragDeleteView.attach(mIvLauncher) {
Toast.makeText(this@MainActivity, "被拖拽删除了", Toast.LENGTH_SHORT).show()
}
mTvText.setOnClickListener {
Toast.makeText(this, "perform OnClick", Toast.LENGTH_SHORT).show();
}
DragDeleteView.attach(mTvText) {
Toast.makeText(this@MainActivity, "被拖拽删除了", Toast.LENGTH_SHORT).show()
}

最终的代码
/**
* Created by Frank on 2018/1/15.
* Email: frankchoochina@gmail.com
* Version: 1.1
* Description: 拖拽删除的 View
*/
public class DragDeleteView extends View {
/**
* 外界调用这个方法进行绑定实现拖拽删除的功能
*
* @param view
*/
public static void attach(@NotNull View view, Callback callback) {
view.setOnTouchListener(new DragDeleteTouchPerformerInternal(view, callback));
}
/**
* 提供给外界回调的接口
*/
public interface Callback {
void onDelete();
}
private Bitmap mAnchorBitmap;
private PointF mStartPoint;
private PointF mCurPoint;
private float mThresholdHeight;
private Paint mPaint;
private CallbackInternal mCallbackInternal;
public DragDeleteView(Context context) {
this(context, null);
}
public DragDeleteView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DragDeleteView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mStartPoint = new PointF();
mCurPoint = new PointF();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG
| Paint.DITHER_FLAG);
mPaint.setTextSize(sp2px(15));
}

private void bindAnchorView(View anchorView) {
mAnchorBitmap = getBitmapFromView(anchorView);
int[] startArray = new int[2];
anchorView.getLocationInWindow(startArray);
mStartPoint.x = startArray[0];
mStartPoint.y = startArray[1];
mCurPoint.x = mStartPoint.x;
mCurPoint.y = mStartPoint.y;
}
private Bitmap getBitmapFromView(View anchorView) {
anchorView.buildDrawingCache();
Bitmap bitmap = anchorView.getDrawingCache();
return bitmap;
}

private void updateFingerPoint(float rawX, float rawY) {
if (mAnchorBitmap == null) return;
mCurPoint.x = rawX - mAnchorBitmap.getWidth() / 2;
mCurPoint.y = rawY - mAnchorBitmap.getHeight() / 2;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 1. 绘制 Bitmap
drawAnchorBitmap(canvas);
// 2. 绘制底部的删除区域
drawBottomRectAndText(canvas);
}
private void drawAnchorBitmap(Canvas canvas) {
canvas.drawBitmap(mAnchorBitmap, mCurPoint.x,
mCurPoint.y, mPaint);
}
private void drawBottomRectAndText(Canvas canvas) {
if (mThresholdHeight == 0) {
// 这里的阈值直接写死了
mThresholdHeight = getMeasuredHeight() * 9f / 10f;
}
// 1. 绘制矩形
RectF rectF = new RectF(0, mThresholdHeight, getWidth(), getHeight());
mPaint.setColor(isOverThresholdHeight() ? Color.RED : Color.BLUE);
canvas.drawRect(rectF, mPaint);
// 2. 绘制底部文本
String str = isOverThresholdHeight() ? "松开删除" : "拖动到此处删除";
Paint.FontMetrics fm = mPaint.getFontMetrics();
float baseLineOffsetY = (fm.bottom - fm.top) / 2 - fm.bottom;
float baseLine = (rectF.bottom + rectF.top) / 2 + baseLineOffsetY;
float textStartX = getWidth() / 2 - mPaint.measureText(str) / 2;
mPaint.setColor(Color.WHITE);
canvas.drawText(str, textStartX, baseLine, mPaint);
}
/**
* 判断是否拖拽超过了的阈值
*
* @return
*/
public boolean isOverThresholdHeight() {
return mCurPoint.y + mAnchorBitmap.getHeight() * 0.8f > mThresholdHeight;
}
/**
* 供 DragDeleteTouchPerformerInternal 内部调用
* <p>
* 恢复原先位置
*/
private void recover() {
final float deltaX = mCurPoint.x - mStartPoint.x;
final float deltaY = mCurPoint.y - mStartPoint.y;
ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f).setDuration(500);
anim.setInterpolator(new OvershootInterpolator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurPoint.x = mStartPoint.x + deltaX * (float) animation.getAnimatedValue();
mCurPoint.y = mStartPoint.y + deltaY * (float) animation.getAnimatedValue();
invalidate();
}
});
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mCallbackInternal.onRecovered();
}
});
anim.start();
}
private float sp2px(int sp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
}
/**
* 供 DragDeleteTouchPerformerInternal 内部回调
*/
private interface CallbackInternal {
void onRecovered();
}

private void setOnRecoverListener(CallbackInternal callbackInternal) {
mCallbackInternal = callbackInternal;
}
/**
* 被绑定的View滑动事件的处理类
*/
private static class DragDeleteTouchPerformerInternal implements OnTouchListener, OnLongClickListener, CallbackInternal {
final Context context;
final View anchorView;
final WindowManager wm;
final WindowManager.LayoutParams params;
final DragDeleteView dragDeleteView;
final Callback callback;
boolean isLongClicked = false;
public DragDeleteTouchPerformerInternal(View anchorView, Callback callback) {
this.callback = callback;
this.anchorView = anchorView;
context = anchorView.getContext();
wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
params = new WindowManager.LayoutParams();
params.flags = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;// 让当前Window占用状态栏空间
params.format = PixelFormat.TRANSPARENT;
params.width = context.getResources().getDisplayMetrics().widthPixels;
params.height = context.getResources().getDisplayMetrics().heightPixels;
dragDeleteView = new DragDeleteView(context);
dragDeleteView.setOnRecoverListener(this);
anchorView.setOnLongClickListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (!isLongClicked) return false;
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE: {
// 1. 添加到 Window 中
if (!dragDeleteView.isAttachedToWindow()) {
dragDeleteView.bindAnchorView(anchorView);
wm.addView(dragDeleteView, params);
v.getParent().requestDisallowInterceptTouchEvent(true);
v.setVisibility(View.INVISIBLE);
}
// 2. 更新坐标位置
dragDeleteView.updateFingerPoint(event.getRawX(), event.getRawY());
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
if (dragDeleteView.isOverThresholdHeight()) {
wm.removeView(dragDeleteView);
callback.onDelete();
} else {
dragDeleteView.recover();
}
v.getParent().requestDisallowInterceptTouchEvent(false);
isLongClicked = false;
break;
}
}
return false;
}
@Override
public void onRecovered() {
if (dragDeleteView.isAttachedToWindow()) {
wm.removeView(dragDeleteView);
anchorView.setVisibility(View.VISIBLE);
}
}
@Override
public boolean onLongClick(View v) {
isLongClicked = true;
return true;
}
}
}







最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台