public class CleerScrollHorizontalView extends CleerDividerLinearLayout { private static final String TAG = "Cleer.ScrollView"; private static final boolean DEBUG = true; private static final int MAX_DRAG_LENGTH = 316; private static final int START_SCROLL_LENGTH = MAX_DRAG_LENGTH / 2; private static int DRAG_NOT_CLICK_SIGN_LENGTH; private static final int SCROLL_ANIM_DURATION = 850; private static final int COLOR_RED_NORMAL = 0xffe70909; private static final int COLOR_RED_PRESSED = 0x5fe70909; private Scroller mScroller; private Paint deleteBgPaint; private Paint deleteIconPaint; private Bitmap deleteBitmap; private int layoutWidth; private int layoutHeight; private int deleteWidth; private int deleteHeight; private boolean deleteClickSign = false; private boolean itemExceptDeleteClickSign = false; private boolean itemClickSign = false; private boolean deleteClickAnimation = false; private boolean needShowDeleteView = false; private int currX; private int prevX; private int origX; private boolean intercept; //For log private String logTag = TAG; private OnDeleteClickListener listener; private OnClickListener onClickListener; public void setLogTag(String tag) { logTag = tag; } public interface OnDeleteClickListener { void onClick(); } public void setOnDeleteClickListener(OnDeleteClickListener listener) { this.listener = listener; } public void setOnClickListener(OnClickListener listener) { onClickListener = listener; } public CleerScrollHorizontalView(Context context, AttributeSet attrs) { super(context, attrs); mScroller = new Scroller(context, new DecelerateInterpolator()); deleteBgPaint = new Paint(); deleteIconPaint = new Paint(); deleteBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_item_delete); DRAG_NOT_CLICK_SIGN_LENGTH = ViewConfiguration.get(context).getScaledTouchSlop(); //Force layout to invoke onDraw setWillNotDraw(false); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); layoutWidth = this.getMeasuredWidth(); layoutHeight = this.getMeasuredHeight(); deleteWidth = deleteBitmap.getWidth(); deleteHeight = deleteBitmap.getHeight(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); deleteBgPaint.setColor(deleteClickAnimation ? COLOR_RED_PRESSED : COLOR_RED_NORMAL); //Draw deleteBg canvas.drawRect(layoutWidth, 0, layoutWidth + MAX_DRAG_LENGTH, layoutHeight, deleteBgPaint); //Draw deleteIcon int deleteLeft = layoutWidth + (MAX_DRAG_LENGTH - deleteWidth) / 2; int deleteTop = (layoutHeight - deleteHeight) / 2; canvas.drawBitmap(deleteBitmap, deleteLeft, deleteTop, deleteIconPaint); } private boolean isTouchPointInDeleteView(int x, int y) { return x > layoutWidth && x <= layoutWidth + MAX_DRAG_LENGTH && y >= 0 && y <= layoutHeight; } private boolean isTouchPointInItemNotDeleteView(int x, int y) { return x >= 0 && x <= layoutWidth && y >= 0 && y <= layoutHeight; } /** Hide the deleteView */ private void hideDeleteView() { startScroll(getScrollX(), 0, -getScrollX(), 0, SCROLL_ANIM_DURATION); } /** Hide all the layouts except itself */ private void triggerInitAllOthersLayouts() { if (getParent() instanceof ViewGroup) { ViewGroup parent = (ViewGroup) getParent(); for (int i = 0; i < parent.getChildCount(); ++i) { View child = parent.getChildAt(i); if (child instanceof CleerScrollHorizontalView && !child.equals(this)) { CleerScrollHorizontalView c = (CleerScrollHorizontalView) child; c.hideDeleteView(); } } } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { if (DEBUG) Log.v("dispatchTouchEvent", "logTag->" + logTag + ", ACTION_DOWN->" + ev.getX()); triggerInitAllOthersLayouts(); mScroller.forceFinished(true); origX = currX = prevX = (int) ev.getX(); needShowDeleteView = false; deleteClickSign = isTouchPointInDeleteView((int) ev.getX() + getScrollX(), (int) ev.getY()); itemExceptDeleteClickSign = !deleteClickSign; itemClickSign = true; //For deleteView rendering, type:ACTION_DOWN deleteClickAnimation = deleteClickSign; invalidate(); intercept = false; break; } case MotionEvent.ACTION_MOVE: { if (DEBUG) Log.v("dispatchTouchEvent", "logTag->" + logTag + ", ACTION_MOVE"); //Scroll animation currX = (int) ev.getX(); if (intercept) { int dOffset = -(currX - prevX); if (getScrollX() + dOffset < 0) { if (DEBUG) Log.v("dispatchTouchEvent", "logTag->" + logTag + ", ACTION_MOVE" + ", move type one"); scrollTo(0, 0); } else if (getScrollX() + dOffset > MAX_DRAG_LENGTH) { if (DEBUG) Log.v("dispatchTouchEvent", "logTag->" + logTag + ", ACTION_MOVE" + ", move type two"); scrollTo(MAX_DRAG_LENGTH, 0); } else { if (DEBUG) Log.v("dispatchTouchEvent", "logTag->" + logTag + ", ACTION_MOVE" + ", move type three->" + dOffset); scrollBy(dOffset, 0); } } //For deleteView rendering, type:ACTION_MOVE deleteClickAnimation = isTouchPointInDeleteView((int) ev.getX() + getScrollX(), (int) ev.getY()); invalidate(); if (Math.abs(currX - origX) >= DRAG_NOT_CLICK_SIGN_LENGTH) { //Disallow parent to intercept MotionEvent getParent().requestDisallowInterceptTouchEvent(true); intercept = true; } prevX = currX; break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: { if (DEBUG) Log.v("dispatchTouchEvent", "logTag->" + logTag + ", ACTION_UP"); currX = (int) ev.getX(); if (Math.abs(currX - origX) >= DRAG_NOT_CLICK_SIGN_LENGTH) { itemClickSign = false; itemExceptDeleteClickSign = false; } if (deleteClickSign && isTouchPointInDeleteView((int) ev.getX() + getScrollX(), (int) ev.getY())) { if (DEBUG) Log.v(TAG, "logTag->" + logTag + " deleteView clicked"); hideDeleteView(); if (listener != null) { listener.onClick(); } itemClickSign = true; } //For deleteView rendering, type:ACTION_UP deleteClickAnimation = false; invalidate(); //Scroll animation if (getScrollX() > 0 && getScrollX() < START_SCROLL_LENGTH) { needShowDeleteView = false; } if (getScrollX() > START_SCROLL_LENGTH) { needShowDeleteView = true; } if (currX > origX) { needShowDeleteView = false; } if (needShowDeleteView) { if (DEBUG) Log.v("dispatchTouchEvent", "logTag->" + logTag + ", ACTION_UP" + ", up to changed"); mScroller.forceFinished(true); startScroll(getScrollX(), 0, MAX_DRAG_LENGTH - getScrollX(), 0, SCROLL_ANIM_DURATION); } else { if (DEBUG) Log.v("dispatchTouchEvent", "logTag->" + logTag + ", ACTION_UP" + ", up to origin2"); mScroller.forceFinished(true); startScroll(getScrollX(), 0, -getScrollX(), 0, SCROLL_ANIM_DURATION); } if (needShowDeleteView && itemClickSign) { if (DEBUG) Log.v("dispatchTouchEvent", "logTag->" + logTag + ", ACTION_UP" + ", up to origin3"); mScroller.forceFinished(true); startScroll(getScrollX(), 0, -getScrollX(), 0, SCROLL_ANIM_DURATION); } break; } } return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (!itemClickSign) { return true; } return super.onInterceptTouchEvent(ev) || intercept; } @Override public boolean performClick() { boolean r = super.performClick(); if (onClickListener != null) { onClickListener.onClick(this); return true; } return r; } @Override public boolean onTouchEvent(MotionEvent ev) { if (!intercept) super.onTouchEvent(ev); if (ev.getAction() == MotionEvent.ACTION_UP) { if (itemExceptDeleteClickSign && isTouchPointInItemNotDeleteView((int) ev.getX() + getScrollX(), (int) ev.getY()) && !isTouchPointInDeleteView((int) ev.getX() + getScrollX(), (int) ev.getY())) { if (DEBUG) Log.v(TAG, "logTag->" + logTag + " itemExceptDeleteView clicked"); performClick(); itemExceptDeleteClickSign = false; } } return true; } private void startScroll(int startX, int startY, int dx, int dy, int duration) { int readDuration = Math.abs((int) (1.0f * duration / MAX_DRAG_LENGTH * dx)); mScroller.startScroll(startX, startY, dx, dy, readDuration); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), 0); postInvalidate(); } }}复制代码