Java tutorial
/* * Copyright (C) 2015 Haruki Hasegawa * Modifications Copyright(C) 2016 Fred Grott(GrottWorkShop) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.shareme.gwsmaterialuikit.library.advancerv.draggable; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.NinePatchDrawable; import android.support.v4.view.ViewCompat; import android.support.v7.widget.RecyclerView; import android.view.MotionEvent; import android.view.View; import com.github.shareme.gwsmaterialuikit.library.advancerv.utils.CustomRecyclerViewUtils; class DraggingItemDecorator extends BaseDraggableItemDecorator { @SuppressWarnings("unused") private static final String TAG = "DraggingItemDecorator"; private int mTranslationX; private int mTranslationY; private Bitmap mDraggingItemImage; private int mTranslationLeftLimit; private int mTranslationRightLimit; private int mTranslationTopLimit; private int mTranslationBottomLimit; private int mTouchPositionX; private int mTouchPositionY; private NinePatchDrawable mShadowDrawable; private final Rect mShadowPadding = new Rect(); private boolean mStarted; private boolean mIsScrolling; private ItemDraggableRange mRange; private int mLayoutOrientation; private DraggingItemInfo mDraggingItemInfo; public DraggingItemDecorator(RecyclerView recyclerView, RecyclerView.ViewHolder draggingItem, ItemDraggableRange range) { super(recyclerView, draggingItem); mRange = range; } private static int clip(int value, int min, int max) { return Math.min(Math.max(value, min), max); } private static View findRangeFirstItem(RecyclerView rv, ItemDraggableRange range, int firstVisiblePosition, int lastVisiblePosition) { if (firstVisiblePosition == RecyclerView.NO_POSITION || lastVisiblePosition == RecyclerView.NO_POSITION) { return null; } View v = null; final int childCount = rv.getChildCount(); for (int i = 0; i < childCount; i++) { final View v2 = rv.getChildAt(i); final RecyclerView.ViewHolder vh = rv.getChildViewHolder(v2); if (vh != null) { final int position = vh.getLayoutPosition(); if ((position >= firstVisiblePosition) && (position <= lastVisiblePosition) && range.checkInRange(position)) { v = v2; break; } } } return v; } private static View findRangeLastItem(RecyclerView rv, ItemDraggableRange range, int firstVisiblePosition, int lastVisiblePosition) { if (firstVisiblePosition == RecyclerView.NO_POSITION || lastVisiblePosition == RecyclerView.NO_POSITION) { return null; } View v = null; final int childCount = rv.getChildCount(); for (int i = childCount - 1; i >= 0; i--) { final View v2 = rv.getChildAt(i); final RecyclerView.ViewHolder vh = rv.getChildViewHolder(v2); if (vh != null) { final int position = vh.getLayoutPosition(); if ((position >= firstVisiblePosition) && (position <= lastVisiblePosition) && range.checkInRange(position)) { v = v2; break; } } } return v; } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { // NOTE: // On lollipop or later, View has Z-axis property and no needed to draw the dragging view manually. // However, if the RecyclerView has any other decorations or RecyclerView is in scrolling state, // need to draw it to avoid visual corruptions. if (mDraggingItemImage != null) { final float left = mTranslationX - mShadowPadding.left; final float top = mTranslationY - mShadowPadding.top; c.drawBitmap(mDraggingItemImage, left, top, null); } } public void start(MotionEvent e, DraggingItemInfo draggingItemInfo) { if (mStarted) { return; } final View itemView = mDraggingItemViewHolder.itemView; mDraggingItemInfo = draggingItemInfo; mDraggingItemImage = createDraggingItemImage(itemView, mShadowDrawable); mTranslationLeftLimit = mRecyclerView.getPaddingLeft(); mTranslationTopLimit = mRecyclerView.getPaddingTop(); mLayoutOrientation = CustomRecyclerViewUtils.getOrientation(mRecyclerView); // hide itemView.setVisibility(View.INVISIBLE); update(e, true); mRecyclerView.addItemDecoration(this); mStarted = true; } public void finish(boolean animate) { if (mStarted) { mRecyclerView.removeItemDecoration(this); } final RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator(); if (itemAnimator != null) { itemAnimator.endAnimations(); } mRecyclerView.stopScroll(); // return to default position updateDraggingItemPosition(mTranslationX, mTranslationY); if (mDraggingItemViewHolder != null) { moveToDefaultPosition(mDraggingItemViewHolder.itemView, animate); } // show if (mDraggingItemViewHolder != null) { mDraggingItemViewHolder.itemView.setVisibility(View.VISIBLE); } mDraggingItemViewHolder = null; if (mDraggingItemImage != null) { mDraggingItemImage.recycle(); mDraggingItemImage = null; } mRange = null; mTranslationX = 0; mTranslationY = 0; mTranslationLeftLimit = 0; mTranslationRightLimit = 0; mTranslationTopLimit = 0; mTranslationBottomLimit = 0; mTouchPositionX = 0; mTouchPositionY = 0; mStarted = false; } public boolean update(MotionEvent e, boolean force) { mTouchPositionX = (int) (e.getX() + 0.5f); mTouchPositionY = (int) (e.getY() + 0.5f); return refresh(force); } public boolean refresh(boolean force) { final int prevTranslationX = mTranslationX; final int prevTranslationY = mTranslationY; updateTranslationOffset(); final boolean updated = (prevTranslationX != mTranslationX) || (prevTranslationY != mTranslationY); if (updated || force) { updateDraggingItemPosition(mTranslationX, mTranslationY); ViewCompat.postInvalidateOnAnimation(mRecyclerView); } return updated; } public void setShadowDrawable(NinePatchDrawable shadowDrawable) { mShadowDrawable = shadowDrawable; if (mShadowDrawable != null) { mShadowDrawable.getPadding(mShadowPadding); } } public int getDraggingItemTranslationY() { return mTranslationY; } public int getDraggingItemTranslationX() { return mTranslationX; } public int getDraggingItemMoveOffsetY() { return mTranslationY - mDraggingItemInfo.initialItemTop; } public int getDraggingItemMoveOffsetX() { return mTranslationX - mDraggingItemInfo.initialItemLeft; } private void updateTranslationOffset() { final RecyclerView rv = mRecyclerView; final int childCount = rv.getChildCount(); if (childCount > 0) { mTranslationLeftLimit = 0; mTranslationRightLimit = rv.getWidth() - mDraggingItemInfo.width; mTranslationTopLimit = 0; mTranslationBottomLimit = rv.getHeight() - mDraggingItemInfo.height; switch (mLayoutOrientation) { case CustomRecyclerViewUtils.ORIENTATION_VERTICAL: { mTranslationLeftLimit += rv.getPaddingLeft(); mTranslationRightLimit -= rv.getPaddingRight(); break; } case CustomRecyclerViewUtils.ORIENTATION_HORIZONTAL: { mTranslationTopLimit += rv.getPaddingTop(); mTranslationBottomLimit -= rv.getPaddingBottom(); break; } } mTranslationRightLimit = Math.max(mTranslationLeftLimit, mTranslationRightLimit); mTranslationBottomLimit = Math.max(mTranslationTopLimit, mTranslationBottomLimit); if (!mIsScrolling) { final int firstVisiblePosition = CustomRecyclerViewUtils.findFirstVisibleItemPosition(rv, true); final int lastVisiblePosition = CustomRecyclerViewUtils.findLastVisibleItemPosition(rv, true); final View firstChild = findRangeFirstItem(rv, mRange, firstVisiblePosition, lastVisiblePosition); final View lastChild = findRangeLastItem(rv, mRange, firstVisiblePosition, lastVisiblePosition); switch (mLayoutOrientation) { case CustomRecyclerViewUtils.ORIENTATION_VERTICAL: { if (firstChild != null) { mTranslationTopLimit = Math.min(mTranslationBottomLimit, firstChild.getTop()); } if (lastChild != null) { final int limit = Math.max(0, lastChild.getBottom() - mDraggingItemInfo.height); mTranslationBottomLimit = Math.min(mTranslationBottomLimit, limit); } break; } case CustomRecyclerViewUtils.ORIENTATION_HORIZONTAL: { if (firstChild != null) { mTranslationLeftLimit = Math.min(mTranslationLeftLimit, firstChild.getLeft()); } if (lastChild != null) { final int limit = Math.max(0, lastChild.getRight() - mDraggingItemInfo.width); mTranslationRightLimit = Math.min(mTranslationRightLimit, limit); } break; } } } } else { mTranslationRightLimit = mTranslationLeftLimit = rv.getPaddingLeft(); mTranslationBottomLimit = mTranslationTopLimit = rv.getPaddingTop(); } mTranslationX = mTouchPositionX - mDraggingItemInfo.grabbedPositionX; mTranslationY = mTouchPositionY - mDraggingItemInfo.grabbedPositionY; mTranslationX = clip(mTranslationX, mTranslationLeftLimit, mTranslationRightLimit); mTranslationY = clip(mTranslationY, mTranslationTopLimit, mTranslationBottomLimit); } private static int toSpanAlignedPosition(int position, int spanCount) { if (position == RecyclerView.NO_POSITION) { return RecyclerView.NO_POSITION; } return (position / spanCount) * spanCount; } public boolean isReachedToTopLimit() { return (mTranslationY == mTranslationTopLimit); } public boolean isReachedToBottomLimit() { return (mTranslationY == mTranslationBottomLimit); } public boolean isReachedToLeftLimit() { return (mTranslationX == mTranslationLeftLimit); } public boolean isReachedToRightLimit() { return (mTranslationX == mTranslationRightLimit); } private Bitmap createDraggingItemImage(View v, NinePatchDrawable shadow) { int width = v.getWidth() + mShadowPadding.left + mShadowPadding.right; int height = v.getHeight() + mShadowPadding.top + mShadowPadding.bottom; final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(bitmap); if (shadow != null) { shadow.setBounds(0, 0, width, height); shadow.draw(canvas); } final int savedCount = canvas.save(Canvas.CLIP_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG); // NOTE: Explicitly set clipping rect. This is required on Gingerbread. canvas.clipRect(mShadowPadding.left, mShadowPadding.top, width - mShadowPadding.right, height - mShadowPadding.bottom); canvas.translate(mShadowPadding.left, mShadowPadding.top); v.draw(canvas); canvas.restoreToCount(savedCount); return bitmap; } private void updateDraggingItemPosition(float translationX, int translationY) { // NOTE: Need to update the view position to make other decorations work properly while dragging if (mDraggingItemViewHolder != null) { setItemTranslation(mRecyclerView, mDraggingItemViewHolder, translationX - mDraggingItemViewHolder.itemView.getLeft(), translationY - mDraggingItemViewHolder.itemView.getTop()); } } public void setIsScrolling(boolean isScrolling) { if (mIsScrolling == isScrolling) { return; } mIsScrolling = isScrolling; } public int getTranslatedItemPositionTop() { return mTranslationY; } public int getTranslatedItemPositionBottom() { return mTranslationY + mDraggingItemInfo.height; } public int getTranslatedItemPositionLeft() { return mTranslationX; } public int getTranslatedItemPositionRight() { return mTranslationX + mDraggingItemInfo.width; } public void invalidateDraggingItem() { if (mDraggingItemViewHolder != null) { ViewCompat.setTranslationX(mDraggingItemViewHolder.itemView, 0); ViewCompat.setTranslationY(mDraggingItemViewHolder.itemView, 0); mDraggingItemViewHolder.itemView.setVisibility(View.VISIBLE); } mDraggingItemViewHolder = null; } public void setDraggingItemViewHolder(RecyclerView.ViewHolder holder) { if (mDraggingItemViewHolder != null) { throw new IllegalStateException( "A new view holder is attempt to be assigned before invalidating the older one"); } mDraggingItemViewHolder = holder; holder.itemView.setVisibility(View.INVISIBLE); } }