mirror of https://github.com/kurisufriend/Clover
Replace my homemade swipe-to-dismiss and drag implementation SwipeListener with the RecyclerView native ItemTouchHelper. Also add an undo snackbar when removing boards.multisite
parent
48d8f94e94
commit
3ac7a1a348
@ -1,673 +0,0 @@ |
|||||||
/* |
|
||||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
|
||||||
* Copyright (C) 2014 Floens |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version. |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
package org.floens.chan.ui.helper; |
|
||||||
|
|
||||||
import android.support.v4.view.ViewCompat; |
|
||||||
import android.support.v4.view.ViewPropertyAnimatorCompat; |
|
||||||
import android.support.v4.view.ViewPropertyAnimatorListener; |
|
||||||
import android.support.v7.widget.RecyclerView; |
|
||||||
import android.view.View; |
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator; |
|
||||||
import android.view.animation.LinearInterpolator; |
|
||||||
|
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.HashMap; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Map; |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* This is an adaption of the {@link android.support.v7.widget.DefaultItemAnimator} but to animate |
|
||||||
* swipes to the left or right. |
|
||||||
*/ |
|
||||||
public class SwipeItemAnimator extends RecyclerView.ItemAnimator { |
|
||||||
private static final boolean DEBUG = true; |
|
||||||
|
|
||||||
private ArrayList<RecyclerView.ViewHolder> mPendingRemovals = new ArrayList<RecyclerView.ViewHolder>(); |
|
||||||
private ArrayList<RecyclerView.ViewHolder> mPendingAdditions = new ArrayList<RecyclerView.ViewHolder>(); |
|
||||||
private ArrayList<MoveInfo> mPendingMoves = new ArrayList<MoveInfo>(); |
|
||||||
private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<ChangeInfo>(); |
|
||||||
|
|
||||||
private ArrayList<ArrayList<RecyclerView.ViewHolder>> mAdditionsList = |
|
||||||
new ArrayList<ArrayList<RecyclerView.ViewHolder>>(); |
|
||||||
private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<ArrayList<MoveInfo>>(); |
|
||||||
private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<ArrayList<ChangeInfo>>(); |
|
||||||
|
|
||||||
private ArrayList<RecyclerView.ViewHolder> mAddAnimations = new ArrayList<RecyclerView.ViewHolder>(); |
|
||||||
private ArrayList<RecyclerView.ViewHolder> mMoveAnimations = new ArrayList<RecyclerView.ViewHolder>(); |
|
||||||
private ArrayList<RecyclerView.ViewHolder> mRemoveAnimations = new ArrayList<RecyclerView.ViewHolder>(); |
|
||||||
private ArrayList<RecyclerView.ViewHolder> mChangeAnimations = new ArrayList<RecyclerView.ViewHolder>(); |
|
||||||
|
|
||||||
private static class MoveInfo { |
|
||||||
public RecyclerView.ViewHolder holder; |
|
||||||
public int fromX, fromY, toX, toY; |
|
||||||
|
|
||||||
private MoveInfo(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { |
|
||||||
this.holder = holder; |
|
||||||
this.fromX = fromX; |
|
||||||
this.fromY = fromY; |
|
||||||
this.toX = toX; |
|
||||||
this.toY = toY; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private static class ChangeInfo { |
|
||||||
public RecyclerView.ViewHolder oldHolder, newHolder; |
|
||||||
public int fromX, fromY, toX, toY; |
|
||||||
|
|
||||||
private ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder) { |
|
||||||
this.oldHolder = oldHolder; |
|
||||||
this.newHolder = newHolder; |
|
||||||
} |
|
||||||
|
|
||||||
private ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, |
|
||||||
int fromX, int fromY, int toX, int toY) { |
|
||||||
this(oldHolder, newHolder); |
|
||||||
this.fromX = fromX; |
|
||||||
this.fromY = fromY; |
|
||||||
this.toX = toX; |
|
||||||
this.toY = toY; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String toString() { |
|
||||||
return "ChangeInfo{" + |
|
||||||
"oldHolder=" + oldHolder + |
|
||||||
", newHolder=" + newHolder + |
|
||||||
", fromX=" + fromX + |
|
||||||
", fromY=" + fromY + |
|
||||||
", toX=" + toX + |
|
||||||
", toY=" + toY + |
|
||||||
'}'; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void runPendingAnimations() { |
|
||||||
boolean removalsPending = !mPendingRemovals.isEmpty(); |
|
||||||
boolean movesPending = !mPendingMoves.isEmpty(); |
|
||||||
boolean changesPending = !mPendingChanges.isEmpty(); |
|
||||||
boolean additionsPending = !mPendingAdditions.isEmpty(); |
|
||||||
if (!removalsPending && !movesPending && !additionsPending && !changesPending) { |
|
||||||
// nothing to animate
|
|
||||||
return; |
|
||||||
} |
|
||||||
// First, remove stuff
|
|
||||||
for (RecyclerView.ViewHolder holder : mPendingRemovals) { |
|
||||||
animateRemoveImpl(holder); |
|
||||||
} |
|
||||||
mPendingRemovals.clear(); |
|
||||||
// Next, move stuff
|
|
||||||
if (movesPending) { |
|
||||||
final ArrayList<MoveInfo> moves = new ArrayList<MoveInfo>(); |
|
||||||
moves.addAll(mPendingMoves); |
|
||||||
mMovesList.add(moves); |
|
||||||
mPendingMoves.clear(); |
|
||||||
Runnable mover = new Runnable() { |
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
for (MoveInfo moveInfo : moves) { |
|
||||||
animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, |
|
||||||
moveInfo.toX, moveInfo.toY); |
|
||||||
} |
|
||||||
moves.clear(); |
|
||||||
mMovesList.remove(moves); |
|
||||||
} |
|
||||||
}; |
|
||||||
if (removalsPending) { |
|
||||||
View view = moves.get(0).holder.itemView; |
|
||||||
ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration()); |
|
||||||
} else { |
|
||||||
mover.run(); |
|
||||||
} |
|
||||||
} |
|
||||||
// Next, change stuff, to run in parallel with move animations
|
|
||||||
if (changesPending) { |
|
||||||
final ArrayList<ChangeInfo> changes = new ArrayList<ChangeInfo>(); |
|
||||||
changes.addAll(mPendingChanges); |
|
||||||
mChangesList.add(changes); |
|
||||||
mPendingChanges.clear(); |
|
||||||
Runnable changer = new Runnable() { |
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
for (ChangeInfo change : changes) { |
|
||||||
animateChangeImpl(change); |
|
||||||
} |
|
||||||
changes.clear(); |
|
||||||
mChangesList.remove(changes); |
|
||||||
} |
|
||||||
}; |
|
||||||
if (removalsPending) { |
|
||||||
RecyclerView.ViewHolder holder = changes.get(0).oldHolder; |
|
||||||
ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration()); |
|
||||||
} else { |
|
||||||
changer.run(); |
|
||||||
} |
|
||||||
} |
|
||||||
// Next, add stuff
|
|
||||||
if (additionsPending) { |
|
||||||
final ArrayList<RecyclerView.ViewHolder> additions = new ArrayList<RecyclerView.ViewHolder>(); |
|
||||||
additions.addAll(mPendingAdditions); |
|
||||||
mAdditionsList.add(additions); |
|
||||||
mPendingAdditions.clear(); |
|
||||||
Runnable adder = new Runnable() { |
|
||||||
public void run() { |
|
||||||
for (RecyclerView.ViewHolder holder : additions) { |
|
||||||
animateAddImpl(holder); |
|
||||||
} |
|
||||||
additions.clear(); |
|
||||||
mAdditionsList.remove(additions); |
|
||||||
} |
|
||||||
}; |
|
||||||
if (removalsPending || movesPending || changesPending) { |
|
||||||
long removeDuration = removalsPending ? getRemoveDuration() : 0; |
|
||||||
long moveDuration = movesPending ? getMoveDuration() : 0; |
|
||||||
long changeDuration = changesPending ? getChangeDuration() : 0; |
|
||||||
long totalDelay = removeDuration + Math.max(moveDuration, changeDuration); |
|
||||||
View view = additions.get(0).itemView; |
|
||||||
ViewCompat.postOnAnimationDelayed(view, adder, totalDelay); |
|
||||||
} else { |
|
||||||
adder.run(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean animateRemove(final RecyclerView.ViewHolder holder) { |
|
||||||
endAnimation(holder); |
|
||||||
mPendingRemovals.add(holder); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
public static class SwipeAnimationData { |
|
||||||
public long time; |
|
||||||
public boolean right; |
|
||||||
} |
|
||||||
|
|
||||||
private Map<View, SwipeAnimationData> removeData = new HashMap<>(); |
|
||||||
|
|
||||||
public void addRemoveData(View view, SwipeAnimationData data) { |
|
||||||
removeData.put(view, data); |
|
||||||
} |
|
||||||
|
|
||||||
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) { |
|
||||||
final View view = holder.itemView; |
|
||||||
|
|
||||||
SwipeAnimationData data = removeData.remove(view); |
|
||||||
if (data == null) { |
|
||||||
data = new SwipeAnimationData(); |
|
||||||
data.time = getRemoveDuration(); |
|
||||||
data.right = true; |
|
||||||
} |
|
||||||
|
|
||||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); |
|
||||||
animation.setDuration(data.time) |
|
||||||
.translationX(data.right ? view.getWidth() : -view.getWidth()) |
|
||||||
.alpha(0) |
|
||||||
.setInterpolator(new LinearInterpolator()) |
|
||||||
.setListener(new VpaListenerAdapter() { |
|
||||||
@Override |
|
||||||
public void onAnimationStart(View view) { |
|
||||||
dispatchRemoveStarting(holder); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onAnimationEnd(View view) { |
|
||||||
animation.setListener(null); |
|
||||||
|
|
||||||
// Reset view properties
|
|
||||||
ViewCompat.setAlpha(view, 1); |
|
||||||
ViewCompat.setTranslationX(view, 0f); |
|
||||||
|
|
||||||
dispatchRemoveFinished(holder); |
|
||||||
mRemoveAnimations.remove(holder); |
|
||||||
dispatchFinishedWhenDone(); |
|
||||||
} |
|
||||||
}).start(); |
|
||||||
mRemoveAnimations.add(holder); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean animateAdd(final RecyclerView.ViewHolder holder) { |
|
||||||
endAnimation(holder); |
|
||||||
ViewCompat.setAlpha(holder.itemView, 0); |
|
||||||
mPendingAdditions.add(holder); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
private void animateAddImpl(final RecyclerView.ViewHolder holder) { |
|
||||||
final View view = holder.itemView; |
|
||||||
mAddAnimations.add(holder); |
|
||||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); |
|
||||||
animation.alpha(1).setInterpolator(new AccelerateDecelerateInterpolator()).setDuration(getAddDuration()). |
|
||||||
setListener(new VpaListenerAdapter() { |
|
||||||
@Override |
|
||||||
public void onAnimationStart(View view) { |
|
||||||
dispatchAddStarting(holder); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onAnimationCancel(View view) { |
|
||||||
ViewCompat.setAlpha(view, 1); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onAnimationEnd(View view) { |
|
||||||
animation.setListener(null); |
|
||||||
dispatchAddFinished(holder); |
|
||||||
mAddAnimations.remove(holder); |
|
||||||
dispatchFinishedWhenDone(); |
|
||||||
} |
|
||||||
}).start(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY, |
|
||||||
int toX, int toY) { |
|
||||||
final View view = holder.itemView; |
|
||||||
fromX += ViewCompat.getTranslationX(holder.itemView); |
|
||||||
fromY += ViewCompat.getTranslationY(holder.itemView); |
|
||||||
endAnimation(holder); |
|
||||||
int deltaX = toX - fromX; |
|
||||||
int deltaY = toY - fromY; |
|
||||||
if (deltaX == 0 && deltaY == 0) { |
|
||||||
dispatchMoveFinished(holder); |
|
||||||
return false; |
|
||||||
} |
|
||||||
if (deltaX != 0) { |
|
||||||
ViewCompat.setTranslationX(view, -deltaX); |
|
||||||
} |
|
||||||
if (deltaY != 0) { |
|
||||||
ViewCompat.setTranslationY(view, -deltaY); |
|
||||||
} |
|
||||||
mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY)); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
private void animateMoveImpl(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { |
|
||||||
final View view = holder.itemView; |
|
||||||
final int deltaX = toX - fromX; |
|
||||||
final int deltaY = toY - fromY; |
|
||||||
if (deltaX != 0) { |
|
||||||
ViewCompat.animate(view).translationX(0); |
|
||||||
} |
|
||||||
if (deltaY != 0) { |
|
||||||
ViewCompat.animate(view).translationY(0); |
|
||||||
} |
|
||||||
ViewCompat.animate(view).alpha(1f); |
|
||||||
// TDO: make EndActions end listeners instead, since end actions aren't called when
|
|
||||||
// vpas are canceled (and can't end them. why?)
|
|
||||||
// need listener functionality in VPACompat for this. Ick.
|
|
||||||
mMoveAnimations.add(holder); |
|
||||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); |
|
||||||
animation.setDuration(getMoveDuration()).setInterpolator(new AccelerateDecelerateInterpolator()).setListener(new VpaListenerAdapter() { |
|
||||||
@Override |
|
||||||
public void onAnimationStart(View view) { |
|
||||||
dispatchMoveStarting(holder); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onAnimationCancel(View view) { |
|
||||||
if (deltaX != 0) { |
|
||||||
ViewCompat.setTranslationX(view, 0); |
|
||||||
} |
|
||||||
if (deltaY != 0) { |
|
||||||
ViewCompat.setTranslationY(view, 0); |
|
||||||
} |
|
||||||
ViewCompat.setAlpha(view, 1f); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onAnimationEnd(View view) { |
|
||||||
animation.setListener(null); |
|
||||||
dispatchMoveFinished(holder); |
|
||||||
mMoveAnimations.remove(holder); |
|
||||||
dispatchFinishedWhenDone(); |
|
||||||
} |
|
||||||
}).start(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, |
|
||||||
int fromX, int fromY, int toX, int toY) { |
|
||||||
final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView); |
|
||||||
final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView); |
|
||||||
final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView); |
|
||||||
endAnimation(oldHolder); |
|
||||||
int deltaX = (int) (toX - fromX - prevTranslationX); |
|
||||||
int deltaY = (int) (toY - fromY - prevTranslationY); |
|
||||||
// recover prev translation state after ending animation
|
|
||||||
ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX); |
|
||||||
ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY); |
|
||||||
ViewCompat.setAlpha(oldHolder.itemView, prevAlpha); |
|
||||||
if (newHolder != null && newHolder.itemView != null) { |
|
||||||
// carry over translation values
|
|
||||||
endAnimation(newHolder); |
|
||||||
ViewCompat.setTranslationX(newHolder.itemView, -deltaX); |
|
||||||
ViewCompat.setTranslationY(newHolder.itemView, -deltaY); |
|
||||||
ViewCompat.setAlpha(newHolder.itemView, 0); |
|
||||||
} |
|
||||||
mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY)); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
private void animateChangeImpl(final ChangeInfo changeInfo) { |
|
||||||
final RecyclerView.ViewHolder holder = changeInfo.oldHolder; |
|
||||||
final View view = holder.itemView; |
|
||||||
final RecyclerView.ViewHolder newHolder = changeInfo.newHolder; |
|
||||||
final View newView = newHolder != null ? newHolder.itemView : null; |
|
||||||
mChangeAnimations.add(changeInfo.oldHolder); |
|
||||||
|
|
||||||
final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration( |
|
||||||
getChangeDuration()).setInterpolator(new AccelerateDecelerateInterpolator()); |
|
||||||
oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX); |
|
||||||
oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY); |
|
||||||
oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { |
|
||||||
@Override |
|
||||||
public void onAnimationStart(View view) { |
|
||||||
dispatchChangeStarting(changeInfo.oldHolder, true); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onAnimationEnd(View view) { |
|
||||||
oldViewAnim.setListener(null); |
|
||||||
ViewCompat.setAlpha(view, 1); |
|
||||||
ViewCompat.setTranslationX(view, 0); |
|
||||||
ViewCompat.setTranslationY(view, 0); |
|
||||||
dispatchChangeFinished(changeInfo.oldHolder, true); |
|
||||||
mChangeAnimations.remove(changeInfo.oldHolder); |
|
||||||
dispatchFinishedWhenDone(); |
|
||||||
} |
|
||||||
}).start(); |
|
||||||
if (newView != null) { |
|
||||||
mChangeAnimations.add(changeInfo.newHolder); |
|
||||||
final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView); |
|
||||||
newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()). |
|
||||||
alpha(1).setInterpolator(new AccelerateDecelerateInterpolator()).setListener(new VpaListenerAdapter() { |
|
||||||
@Override |
|
||||||
public void onAnimationStart(View view) { |
|
||||||
dispatchChangeStarting(changeInfo.newHolder, false); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onAnimationEnd(View view) { |
|
||||||
newViewAnimation.setListener(null); |
|
||||||
ViewCompat.setAlpha(newView, 1); |
|
||||||
ViewCompat.setTranslationX(newView, 0); |
|
||||||
ViewCompat.setTranslationY(newView, 0); |
|
||||||
dispatchChangeFinished(changeInfo.newHolder, false); |
|
||||||
mChangeAnimations.remove(changeInfo.newHolder); |
|
||||||
dispatchFinishedWhenDone(); |
|
||||||
} |
|
||||||
}).start(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void endChangeAnimation(List<ChangeInfo> infoList, RecyclerView.ViewHolder item) { |
|
||||||
for (int i = infoList.size() - 1; i >= 0; i--) { |
|
||||||
ChangeInfo changeInfo = infoList.get(i); |
|
||||||
if (endChangeAnimationIfNecessary(changeInfo, item)) { |
|
||||||
if (changeInfo.oldHolder == null && changeInfo.newHolder == null) { |
|
||||||
infoList.remove(changeInfo); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) { |
|
||||||
if (changeInfo.oldHolder != null) { |
|
||||||
endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder); |
|
||||||
} |
|
||||||
if (changeInfo.newHolder != null) { |
|
||||||
endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, RecyclerView.ViewHolder item) { |
|
||||||
boolean oldItem = false; |
|
||||||
if (changeInfo.newHolder == item) { |
|
||||||
changeInfo.newHolder = null; |
|
||||||
} else if (changeInfo.oldHolder == item) { |
|
||||||
changeInfo.oldHolder = null; |
|
||||||
oldItem = true; |
|
||||||
} else { |
|
||||||
return false; |
|
||||||
} |
|
||||||
ViewCompat.setAlpha(item.itemView, 1); |
|
||||||
ViewCompat.setTranslationX(item.itemView, 0); |
|
||||||
ViewCompat.setTranslationY(item.itemView, 0); |
|
||||||
dispatchChangeFinished(item, oldItem); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void endAnimation(RecyclerView.ViewHolder item) { |
|
||||||
final View view = item.itemView; |
|
||||||
// this will trigger end callback which should set properties to their target values.
|
|
||||||
ViewCompat.animate(view).cancel(); |
|
||||||
// TDO if some other animations are chained to end, how do we cancel them as well?
|
|
||||||
for (int i = mPendingMoves.size() - 1; i >= 0; i--) { |
|
||||||
MoveInfo moveInfo = mPendingMoves.get(i); |
|
||||||
if (moveInfo.holder == item) { |
|
||||||
ViewCompat.setTranslationY(view, 0); |
|
||||||
ViewCompat.setTranslationX(view, 0); |
|
||||||
dispatchMoveFinished(item); |
|
||||||
mPendingMoves.remove(item); |
|
||||||
} |
|
||||||
} |
|
||||||
endChangeAnimation(mPendingChanges, item); |
|
||||||
if (mPendingRemovals.remove(item)) { |
|
||||||
ViewCompat.setAlpha(view, 1); |
|
||||||
dispatchRemoveFinished(item); |
|
||||||
} |
|
||||||
if (mPendingAdditions.remove(item)) { |
|
||||||
ViewCompat.setAlpha(view, 1); |
|
||||||
dispatchAddFinished(item); |
|
||||||
} |
|
||||||
|
|
||||||
for (int i = mChangesList.size() - 1; i >= 0; i--) { |
|
||||||
ArrayList<ChangeInfo> changes = mChangesList.get(i); |
|
||||||
endChangeAnimation(changes, item); |
|
||||||
if (changes.isEmpty()) { |
|
||||||
mChangesList.remove(changes); |
|
||||||
} |
|
||||||
} |
|
||||||
for (int i = mMovesList.size() - 1; i >= 0; i--) { |
|
||||||
ArrayList<MoveInfo> moves = mMovesList.get(i); |
|
||||||
for (int j = moves.size() - 1; j >= 0; j--) { |
|
||||||
MoveInfo moveInfo = moves.get(j); |
|
||||||
if (moveInfo.holder == item) { |
|
||||||
ViewCompat.setTranslationY(view, 0); |
|
||||||
ViewCompat.setTranslationX(view, 0); |
|
||||||
dispatchMoveFinished(item); |
|
||||||
moves.remove(j); |
|
||||||
if (moves.isEmpty()) { |
|
||||||
mMovesList.remove(moves); |
|
||||||
} |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
for (int i = mAdditionsList.size() - 1; i >= 0; i--) { |
|
||||||
ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i); |
|
||||||
if (additions.remove(item)) { |
|
||||||
ViewCompat.setAlpha(view, 1); |
|
||||||
dispatchAddFinished(item); |
|
||||||
if (additions.isEmpty()) { |
|
||||||
mAdditionsList.remove(additions); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// animations should be ended by the cancel above.
|
|
||||||
if (mRemoveAnimations.remove(item) && DEBUG) { |
|
||||||
throw new IllegalStateException("after animation is cancelled, item should not be in " |
|
||||||
+ "mRemoveAnimations list"); |
|
||||||
} |
|
||||||
|
|
||||||
if (mAddAnimations.remove(item) && DEBUG) { |
|
||||||
throw new IllegalStateException("after animation is cancelled, item should not be in " |
|
||||||
+ "mAddAnimations list"); |
|
||||||
} |
|
||||||
|
|
||||||
if (mChangeAnimations.remove(item) && DEBUG) { |
|
||||||
throw new IllegalStateException("after animation is cancelled, item should not be in " |
|
||||||
+ "mChangeAnimations list"); |
|
||||||
} |
|
||||||
|
|
||||||
if (mMoveAnimations.remove(item) && DEBUG) { |
|
||||||
throw new IllegalStateException("after animation is cancelled, item should not be in " |
|
||||||
+ "mMoveAnimations list"); |
|
||||||
} |
|
||||||
dispatchFinishedWhenDone(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean isRunning() { |
|
||||||
return (!mPendingAdditions.isEmpty() || |
|
||||||
!mPendingChanges.isEmpty() || |
|
||||||
!mPendingMoves.isEmpty() || |
|
||||||
!mPendingRemovals.isEmpty() || |
|
||||||
!mMoveAnimations.isEmpty() || |
|
||||||
!mRemoveAnimations.isEmpty() || |
|
||||||
!mAddAnimations.isEmpty() || |
|
||||||
!mChangeAnimations.isEmpty() || |
|
||||||
!mMovesList.isEmpty() || |
|
||||||
!mAdditionsList.isEmpty() || |
|
||||||
!mChangesList.isEmpty()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Check the state of currently pending and running animations. If there are none |
|
||||||
* pending/running, call {@link #dispatchAnimationsFinished()} to notify any |
|
||||||
* listeners. |
|
||||||
*/ |
|
||||||
private void dispatchFinishedWhenDone() { |
|
||||||
if (!isRunning()) { |
|
||||||
dispatchAnimationsFinished(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void endAnimations() { |
|
||||||
int count = mPendingMoves.size(); |
|
||||||
for (int i = count - 1; i >= 0; i--) { |
|
||||||
MoveInfo item = mPendingMoves.get(i); |
|
||||||
View view = item.holder.itemView; |
|
||||||
ViewCompat.setTranslationY(view, 0); |
|
||||||
ViewCompat.setTranslationX(view, 0); |
|
||||||
dispatchMoveFinished(item.holder); |
|
||||||
mPendingMoves.remove(i); |
|
||||||
} |
|
||||||
count = mPendingRemovals.size(); |
|
||||||
for (int i = count - 1; i >= 0; i--) { |
|
||||||
RecyclerView.ViewHolder item = mPendingRemovals.get(i); |
|
||||||
dispatchRemoveFinished(item); |
|
||||||
mPendingRemovals.remove(i); |
|
||||||
} |
|
||||||
count = mPendingAdditions.size(); |
|
||||||
for (int i = count - 1; i >= 0; i--) { |
|
||||||
RecyclerView.ViewHolder item = mPendingAdditions.get(i); |
|
||||||
View view = item.itemView; |
|
||||||
ViewCompat.setAlpha(view, 1); |
|
||||||
dispatchAddFinished(item); |
|
||||||
mPendingAdditions.remove(i); |
|
||||||
} |
|
||||||
count = mPendingChanges.size(); |
|
||||||
for (int i = count - 1; i >= 0; i--) { |
|
||||||
endChangeAnimationIfNecessary(mPendingChanges.get(i)); |
|
||||||
} |
|
||||||
mPendingChanges.clear(); |
|
||||||
if (!isRunning()) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
int listCount = mMovesList.size(); |
|
||||||
for (int i = listCount - 1; i >= 0; i--) { |
|
||||||
ArrayList<MoveInfo> moves = mMovesList.get(i); |
|
||||||
count = moves.size(); |
|
||||||
for (int j = count - 1; j >= 0; j--) { |
|
||||||
MoveInfo moveInfo = moves.get(j); |
|
||||||
RecyclerView.ViewHolder item = moveInfo.holder; |
|
||||||
View view = item.itemView; |
|
||||||
ViewCompat.setTranslationY(view, 0); |
|
||||||
ViewCompat.setTranslationX(view, 0); |
|
||||||
dispatchMoveFinished(moveInfo.holder); |
|
||||||
moves.remove(j); |
|
||||||
if (moves.isEmpty()) { |
|
||||||
mMovesList.remove(moves); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
listCount = mAdditionsList.size(); |
|
||||||
for (int i = listCount - 1; i >= 0; i--) { |
|
||||||
ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i); |
|
||||||
count = additions.size(); |
|
||||||
for (int j = count - 1; j >= 0; j--) { |
|
||||||
RecyclerView.ViewHolder item = additions.get(j); |
|
||||||
View view = item.itemView; |
|
||||||
ViewCompat.setAlpha(view, 1); |
|
||||||
dispatchAddFinished(item); |
|
||||||
additions.remove(j); |
|
||||||
if (additions.isEmpty()) { |
|
||||||
mAdditionsList.remove(additions); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
listCount = mChangesList.size(); |
|
||||||
for (int i = listCount - 1; i >= 0; i--) { |
|
||||||
ArrayList<ChangeInfo> changes = mChangesList.get(i); |
|
||||||
count = changes.size(); |
|
||||||
for (int j = count - 1; j >= 0; j--) { |
|
||||||
endChangeAnimationIfNecessary(changes.get(j)); |
|
||||||
if (changes.isEmpty()) { |
|
||||||
mChangesList.remove(changes); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
cancelAll(mRemoveAnimations); |
|
||||||
cancelAll(mMoveAnimations); |
|
||||||
cancelAll(mAddAnimations); |
|
||||||
cancelAll(mChangeAnimations); |
|
||||||
|
|
||||||
dispatchAnimationsFinished(); |
|
||||||
} |
|
||||||
|
|
||||||
void cancelAll(List<RecyclerView.ViewHolder> viewHolders) { |
|
||||||
for (int i = viewHolders.size() - 1; i >= 0; i--) { |
|
||||||
ViewCompat.animate(viewHolders.get(i).itemView).cancel(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private static class VpaListenerAdapter implements ViewPropertyAnimatorListener { |
|
||||||
@Override |
|
||||||
public void onAnimationStart(View view) { |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onAnimationEnd(View view) { |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onAnimationCancel(View view) { |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
; |
|
||||||
} |
|
@ -1,408 +0,0 @@ |
|||||||
/* |
|
||||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
|
||||||
* Copyright (C) 2014 Floens |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version. |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
package org.floens.chan.ui.helper; |
|
||||||
|
|
||||||
import android.content.Context; |
|
||||||
import android.graphics.Canvas; |
|
||||||
import android.graphics.Rect; |
|
||||||
import android.support.v7.widget.LinearLayoutManager; |
|
||||||
import android.support.v7.widget.RecyclerView; |
|
||||||
import android.util.Log; |
|
||||||
import android.view.MotionEvent; |
|
||||||
import android.view.VelocityTracker; |
|
||||||
import android.view.View; |
|
||||||
import android.view.ViewConfiguration; |
|
||||||
|
|
||||||
import org.floens.chan.R; |
|
||||||
|
|
||||||
import static org.floens.chan.utils.AndroidUtils.dp; |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* An ItemDecorator and Touch listener that enabled the list to be reordered and items to be swiped away. |
|
||||||
* It isn't perfect, but works good for what is should do. |
|
||||||
*/ |
|
||||||
public class SwipeListener extends RecyclerView.ItemDecoration implements RecyclerView.OnItemTouchListener { |
|
||||||
public enum Swipeable { |
|
||||||
NO, |
|
||||||
LEFT, |
|
||||||
RIGHT, |
|
||||||
BOTH; |
|
||||||
} |
|
||||||
|
|
||||||
private static final String TAG = "SwipeListener"; |
|
||||||
private static final int MAX_SCROLL_SPEED = 14; // dp/s
|
|
||||||
|
|
||||||
private final int slopPixels; |
|
||||||
private final int flingPixels; |
|
||||||
private final int maxFlingPixels; |
|
||||||
|
|
||||||
private Callback callback; |
|
||||||
private final RecyclerView recyclerView; |
|
||||||
private final LinearLayoutManager layoutManager; |
|
||||||
private final SwipeItemAnimator swipeItemAnimator; |
|
||||||
|
|
||||||
private VelocityTracker tracker; |
|
||||||
private boolean swiping; |
|
||||||
private float touchDownX; |
|
||||||
private float touchDownY; |
|
||||||
private float totalScrolled; |
|
||||||
private float touchDownOffsetX; |
|
||||||
private float offsetX; |
|
||||||
private View downView; |
|
||||||
|
|
||||||
private boolean dragging; |
|
||||||
private boolean somePositionChanged = false; |
|
||||||
private int dragPosition = -1; |
|
||||||
private float offsetY; |
|
||||||
private float touchDownOffsetY; |
|
||||||
|
|
||||||
private final Runnable scrollRunnable = new Runnable() { |
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
if (dragging) { |
|
||||||
float scroll; |
|
||||||
boolean up = offsetY < recyclerView.getHeight() / 2f; |
|
||||||
if (up) { |
|
||||||
scroll = Math.max(-dp(MAX_SCROLL_SPEED), (offsetY - recyclerView.getHeight() / 6f) * 0.1f); |
|
||||||
} else { |
|
||||||
scroll = Math.min(dp(MAX_SCROLL_SPEED), (offsetY - recyclerView.getHeight() * 5f / 6f) * 0.1f); |
|
||||||
} |
|
||||||
|
|
||||||
if (up && scroll < 0f && layoutManager.findFirstCompletelyVisibleItemPosition() != 0) { |
|
||||||
recyclerView.scrollBy(0, (int) scroll); |
|
||||||
} else if (!up && scroll > 0f && layoutManager.findLastCompletelyVisibleItemPosition() != recyclerView.getAdapter().getItemCount() - 1) { |
|
||||||
recyclerView.scrollBy(0, (int) scroll); |
|
||||||
} |
|
||||||
|
|
||||||
if (scroll != 0) { |
|
||||||
processDrag(); |
|
||||||
recyclerView.post(scrollRunnable); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
public SwipeListener(Context context, RecyclerView rv, Callback callback) { |
|
||||||
recyclerView = rv; |
|
||||||
this.callback = callback; |
|
||||||
|
|
||||||
layoutManager = new LinearLayoutManager(context); |
|
||||||
rv.setLayoutManager(layoutManager); |
|
||||||
swipeItemAnimator = new SwipeItemAnimator(); |
|
||||||
swipeItemAnimator.setMoveDuration(250); |
|
||||||
rv.setItemAnimator(swipeItemAnimator); |
|
||||||
rv.addOnItemTouchListener(this); |
|
||||||
rv.addItemDecoration(this); |
|
||||||
|
|
||||||
ViewConfiguration viewConfiguration = ViewConfiguration.get(context); |
|
||||||
slopPixels = viewConfiguration.getScaledTouchSlop(); |
|
||||||
flingPixels = viewConfiguration.getScaledMinimumFlingVelocity(); |
|
||||||
maxFlingPixels = viewConfiguration.getScaledMaximumFlingVelocity(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onRequestDisallowInterceptTouchEvent(boolean b) { |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { |
|
||||||
switch (e.getActionMasked()) { |
|
||||||
case MotionEvent.ACTION_DOWN: |
|
||||||
totalScrolled = 0f; |
|
||||||
touchDownX = e.getRawX(); |
|
||||||
touchDownY = e.getRawY(); |
|
||||||
downView = rv.findChildViewUnder(e.getX(), e.getY()); |
|
||||||
if (downView == null) { |
|
||||||
// There can be gaps when a move animation is running
|
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
// Dragging gets initiated immediately if the touch went down on the thumb area
|
|
||||||
// Do not allow dragging when animations are running
|
|
||||||
View thumbView = downView.findViewById(R.id.thumb); |
|
||||||
if (thumbView != null && e.getX() < rv.getPaddingLeft() + thumbView.getRight() && !swipeItemAnimator.isRunning()) { |
|
||||||
int touchAdapterPos = rv.getChildAdapterPosition(downView); |
|
||||||
if (touchAdapterPos < 0 || !callback.isMoveable(touchAdapterPos)) { |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
dragging = true; |
|
||||||
dragPosition = touchAdapterPos; |
|
||||||
|
|
||||||
rv.post(scrollRunnable); |
|
||||||
|
|
||||||
offsetY = e.getY(); |
|
||||||
touchDownOffsetY = offsetY - downView.getTop(); |
|
||||||
|
|
||||||
downView.setVisibility(View.INVISIBLE); |
|
||||||
rv.invalidate(); |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
// Didn't went down on the thumb area, start up the tracker
|
|
||||||
if (tracker != null) { |
|
||||||
Log.w(TAG, "Tracker was not null, recycling extra"); |
|
||||||
tracker.recycle(); |
|
||||||
} |
|
||||||
tracker = VelocityTracker.obtain(); |
|
||||||
tracker.addMovement(e); |
|
||||||
break; |
|
||||||
case MotionEvent.ACTION_MOVE: |
|
||||||
if (dragging) { |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
float deltaX = e.getRawX() - touchDownX; |
|
||||||
float deltaY = e.getRawY() - touchDownY; |
|
||||||
totalScrolled += Math.abs(deltaY); |
|
||||||
int adapterPosition = rv.getChildAdapterPosition(downView); |
|
||||||
if (adapterPosition < 0) { |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
if (swiping) { |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
// Logic to find out if a swipe should be initiated
|
|
||||||
Swipeable swipeable = callback.getSwipeable(adapterPosition); |
|
||||||
if (swipeable != Swipeable.NO && Math.abs(deltaX) >= slopPixels && totalScrolled < slopPixels) { |
|
||||||
boolean wasSwiped = false; |
|
||||||
if (swipeable == Swipeable.BOTH) { |
|
||||||
wasSwiped = true; |
|
||||||
} else if (swipeable == Swipeable.LEFT && deltaX < -slopPixels) { |
|
||||||
wasSwiped = true; |
|
||||||
} else if (swipeable == Swipeable.RIGHT && deltaX > slopPixels) { |
|
||||||
wasSwiped = true; |
|
||||||
} |
|
||||||
|
|
||||||
if (wasSwiped) { |
|
||||||
swiping = true; |
|
||||||
touchDownOffsetX = deltaX; |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
break; |
|
||||||
case MotionEvent.ACTION_UP: |
|
||||||
case MotionEvent.ACTION_CANCEL: |
|
||||||
reset(); |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onTouchEvent(RecyclerView rv, MotionEvent e) { |
|
||||||
if (swiping) { |
|
||||||
float deltaX = e.getRawX() - touchDownX; |
|
||||||
switch (e.getActionMasked()) { |
|
||||||
case MotionEvent.ACTION_MOVE: { |
|
||||||
tracker.addMovement(e); |
|
||||||
offsetX = deltaX - touchDownOffsetX; |
|
||||||
downView.setTranslationX(offsetX); |
|
||||||
downView.setAlpha(Math.min(1f, Math.max(0f, 1f - (Math.abs(offsetX) / (float) downView.getWidth())))); |
|
||||||
break; |
|
||||||
} |
|
||||||
case MotionEvent.ACTION_UP: |
|
||||||
case MotionEvent.ACTION_CANCEL: { |
|
||||||
boolean reset = false; |
|
||||||
|
|
||||||
int adapterPosition = rv.getChildAdapterPosition(downView); |
|
||||||
if (adapterPosition < 0) { |
|
||||||
reset = true; |
|
||||||
} else if (e.getActionMasked() == MotionEvent.ACTION_UP) { |
|
||||||
tracker.addMovement(e); |
|
||||||
tracker.computeCurrentVelocity(1000); |
|
||||||
float xVelocity = tracker.getXVelocity(); |
|
||||||
|
|
||||||
if (Math.abs(xVelocity) > flingPixels && Math.abs(xVelocity) < maxFlingPixels && |
|
||||||
(xVelocity < 0) == (deltaX < 0) // Swiping in the same direction
|
|
||||||
) { |
|
||||||
SwipeItemAnimator.SwipeAnimationData data = new SwipeItemAnimator.SwipeAnimationData(); |
|
||||||
data.right = xVelocity > 0; |
|
||||||
|
|
||||||
// Remove animations are linear, calculate the time here to mimic the fling speed
|
|
||||||
float timeLeft = (rv.getWidth() - Math.abs(offsetX)) / Math.abs(xVelocity); |
|
||||||
timeLeft = Math.min(0.5f, timeLeft); |
|
||||||
data.time = (long) (timeLeft * 1000f); |
|
||||||
swipeItemAnimator.addRemoveData(downView, data); |
|
||||||
callback.removeItem(rv.getChildAdapterPosition(downView)); |
|
||||||
} else { |
|
||||||
reset = true; |
|
||||||
} |
|
||||||
} else { |
|
||||||
reset = true; |
|
||||||
} |
|
||||||
|
|
||||||
// The item should be reset to its original alpha and position.
|
|
||||||
// Otherwise our SwipeItemAnimator will handle the swipe remove animation
|
|
||||||
if (reset) { |
|
||||||
swipeItemAnimator.animateMove(rv.getChildViewHolder(downView), 0, 0, 0, 0); |
|
||||||
swipeItemAnimator.runPendingAnimations(); |
|
||||||
} |
|
||||||
|
|
||||||
reset(); |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} else if (dragging) { |
|
||||||
// Invalidate hover view
|
|
||||||
recyclerView.invalidate(); |
|
||||||
|
|
||||||
switch (e.getActionMasked()) { |
|
||||||
case MotionEvent.ACTION_MOVE: { |
|
||||||
offsetY = e.getY(); |
|
||||||
|
|
||||||
processDrag(); |
|
||||||
|
|
||||||
break; |
|
||||||
} |
|
||||||
case MotionEvent.ACTION_UP: |
|
||||||
case MotionEvent.ACTION_CANCEL: { |
|
||||||
if (somePositionChanged) { |
|
||||||
callback.movingDone(); |
|
||||||
} |
|
||||||
|
|
||||||
RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(downView); |
|
||||||
swipeItemAnimator.endAnimation(vh); |
|
||||||
float floatingViewPos = offsetY - touchDownOffsetY - downView.getTop(); |
|
||||||
swipeItemAnimator.animateMove(vh, 0, (int) floatingViewPos, 0, 0); |
|
||||||
swipeItemAnimator.runPendingAnimations(); |
|
||||||
|
|
||||||
reset(); |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void processDrag() { |
|
||||||
float floatingViewPos = offsetY - touchDownOffsetY + downView.getHeight() / 2f; |
|
||||||
|
|
||||||
View viewAtPosition = null; |
|
||||||
|
|
||||||
// like findChildUnder, but without looking at the x axis
|
|
||||||
for (int c = layoutManager.getChildCount(), i = c - 1; i >= 0; i--) { |
|
||||||
final View child = layoutManager.getChildAt(i); |
|
||||||
if (floatingViewPos >= child.getTop() && floatingViewPos <= child.getBottom()) { |
|
||||||
viewAtPosition = child; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (viewAtPosition == null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
int touchAdapterPos = recyclerView.getChildAdapterPosition(viewAtPosition); |
|
||||||
if (touchAdapterPos < 0) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
int firstCompletelyVisible = layoutManager.findFirstCompletelyVisibleItemPosition(); |
|
||||||
int lastCompletelyVisible = layoutManager.findLastCompletelyVisibleItemPosition(); |
|
||||||
|
|
||||||
if (touchAdapterPos < firstCompletelyVisible || touchAdapterPos > lastCompletelyVisible) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((touchAdapterPos > dragPosition && floatingViewPos > viewAtPosition.getTop() + viewAtPosition.getHeight() / 5f) || |
|
||||||
(touchAdapterPos < dragPosition && floatingViewPos < viewAtPosition.getTop() + viewAtPosition.getHeight() * 4f / 5f)) { |
|
||||||
if (callback.isMoveable(touchAdapterPos)) { |
|
||||||
callback.moveItem(dragPosition, touchAdapterPos); |
|
||||||
dragPosition = touchAdapterPos; |
|
||||||
somePositionChanged = true; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { |
|
||||||
if (swiping && this.downView == view) { |
|
||||||
outRect.set((int) offsetX, 0, (int) -offsetX, 0); |
|
||||||
} else { |
|
||||||
outRect.set(0, 0, 0, 0); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) { |
|
||||||
if (dragging) { |
|
||||||
for (int i = 0, c = layoutManager.getChildCount(); i < c; i++) { |
|
||||||
View child = layoutManager.getChildAt(i); |
|
||||||
if (child.getVisibility() != View.VISIBLE) { |
|
||||||
child.setVisibility(View.VISIBLE); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
RecyclerView.ViewHolder vh = parent.findViewHolderForAdapterPosition(dragPosition); |
|
||||||
if (vh != null) { |
|
||||||
vh.itemView.setVisibility(View.INVISIBLE); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) { |
|
||||||
if (dragging) { |
|
||||||
RecyclerView.ViewHolder vh = parent.findViewHolderForAdapterPosition(dragPosition); |
|
||||||
if (vh != null) { |
|
||||||
int left = parent.getPaddingLeft(); |
|
||||||
int top = (int) offsetY - (int) touchDownOffsetY; |
|
||||||
canvas.save(); |
|
||||||
canvas.translate(left, top); |
|
||||||
vh.itemView.draw(canvas); |
|
||||||
canvas.restore(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void reset() { |
|
||||||
if (tracker != null) { |
|
||||||
tracker.recycle(); |
|
||||||
tracker = null; |
|
||||||
} |
|
||||||
downView = null; |
|
||||||
for (int i = 0, c = layoutManager.getChildCount(); i < c; i++) { |
|
||||||
View child = layoutManager.getChildAt(i); |
|
||||||
if (child.getVisibility() != View.VISIBLE) { |
|
||||||
child.setVisibility(View.VISIBLE); |
|
||||||
} |
|
||||||
} |
|
||||||
swiping = false; |
|
||||||
offsetX = 0f; |
|
||||||
dragging = false; |
|
||||||
dragPosition = -1; |
|
||||||
somePositionChanged = false; |
|
||||||
} |
|
||||||
|
|
||||||
public interface Callback { |
|
||||||
Swipeable getSwipeable(int position); |
|
||||||
|
|
||||||
void removeItem(int position); |
|
||||||
|
|
||||||
boolean isMoveable(int position); |
|
||||||
|
|
||||||
void moveItem(int from, int to); |
|
||||||
|
|
||||||
void movingDone(); |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue