History swipe to dismiss
This commit is contained in:
1 parent
59dccfa513
commit
063fa658f2
4 files changed
+431
-5
No files matched your search
@@ -681,7 +681,7 @@ public class Data {
|
||||
public static final int READ = 0x2;
|
||||
|
||||
public synchronized List<HistoryItem> getHistory(int mode, boolean desc) {
|
||||
List<HistoryItem> history = new ArrayList<HistoryItem>();
|
||||
List<HistoryItem> history = new ArrayList<>();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("select * from ").append(TABLE_HISTORY);
|
||||
@@ -759,6 +759,11 @@ public class Data {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void deleteHistory(int gid) {
|
||||
mDatabase.delete(TABLE_HISTORY, COLUMN_GID + "=?", new String[]{String.valueOf(gid)});
|
||||
deleteGallery(gid);
|
||||
}
|
||||
|
||||
public void clearHistory() {
|
||||
truncateHistory(0);
|
||||
}
|
||||
|
||||
@@ -16,15 +16,13 @@
|
||||
|
||||
package com.hippo.ehviewer.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -43,6 +41,10 @@ import com.hippo.ehviewer.widget.FitWindowView;
|
||||
import com.hippo.ehviewer.widget.GalleryListView;
|
||||
import com.hippo.ehviewer.widget.GalleryListView.OnGetListListener;
|
||||
import com.hippo.ehviewer.widget.recyclerview.EasyRecyclerView;
|
||||
import com.hippo.ehviewer.widget.recyclerview.SwipeToDismissTouchListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class HistoryActivity extends AbsTranslucentActivity
|
||||
implements EasyRecyclerView.OnItemClickListener, MaterialAlertDialog.OnClickListener,
|
||||
@@ -175,6 +177,37 @@ public class HistoryActivity extends AbsTranslucentActivity
|
||||
mGalleryListView.setEnabledFooter(false);
|
||||
mGalleryListView.setOnItemClickListener(this);
|
||||
|
||||
SwipeToDismissTouchListener.DismissCallbacks callbacks = new SwipeToDismissTouchListener.DismissCallbacks() {
|
||||
@Override
|
||||
public SwipeToDismissTouchListener.SwipeDirection dismissDirection(int position) {
|
||||
return SwipeToDismissTouchListener.SwipeDirection.BOTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(RecyclerView view, List<SwipeToDismissTouchListener.PendingDismissData> dismissData) {
|
||||
for (SwipeToDismissTouchListener.PendingDismissData data : dismissData) {
|
||||
// TODO After delete all show NONE tip
|
||||
int position = data.position;
|
||||
mData.deleteHistory(mGalleryListView.getGalleryInfo(position).gid);
|
||||
mGalleryListView.removeGalleryInfo(position);
|
||||
mGalleryListView.getGalleryAdapter().notifyItemRemoved(data.position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResetMotion() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchDown() {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
mGalleryListView.addOnItemTouchListener(
|
||||
new SwipeToDismissTouchListener(mGalleryListView.getEasyRecyclerView(), callbacks));
|
||||
|
||||
createFilterDialog();
|
||||
createClearDialog();
|
||||
}
|
||||
@@ -219,7 +252,7 @@ public class HistoryActivity extends AbsTranslucentActivity
|
||||
OnGetListListener listener) {
|
||||
if (url.equals(HISTORY_URL)) {
|
||||
List<GalleryInfo> giList = new ArrayList<GalleryInfo>(mData.getHistory(mFilterMode, true));
|
||||
if (giList == null || giList.size() == 0)
|
||||
if (giList.size() == 0)
|
||||
listener.onSuccess(taskStamp, giList, 0);
|
||||
else
|
||||
listener.onSuccess(taskStamp, giList, 1);
|
||||
|
||||
@@ -228,6 +228,14 @@ public class GalleryListView extends FrameLayout implements RefreshLayout.OnHead
|
||||
}
|
||||
}
|
||||
|
||||
public EasyRecyclerView getEasyRecyclerView() {
|
||||
return mEasyRecyclerView;
|
||||
}
|
||||
|
||||
public GalleryAdapter getGalleryAdapter() {
|
||||
return mAdapter;
|
||||
}
|
||||
|
||||
public void setGalleryListViewHelper(GalleryListViewHelper h) {
|
||||
mHelper = h;
|
||||
}
|
||||
@@ -273,6 +281,10 @@ public class GalleryListView extends FrameLayout implements RefreshLayout.OnHead
|
||||
mEasyRecyclerView.setMultiChoiceModeListener(listener);
|
||||
}
|
||||
|
||||
public void addOnItemTouchListener(RecyclerView.OnItemTouchListener listener) {
|
||||
mEasyRecyclerView.addOnItemTouchListener(listener);
|
||||
}
|
||||
|
||||
public void notifyDataSetChanged() {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
@@ -446,6 +458,10 @@ public class GalleryListView extends FrameLayout implements RefreshLayout.OnHead
|
||||
return mGiList.get(position);
|
||||
}
|
||||
|
||||
public GalleryInfo removeGalleryInfo(int position) {
|
||||
return mGiList.remove(position);
|
||||
}
|
||||
|
||||
public static interface OnGetListListener {
|
||||
public void onSuccess(long taskStamp, List<GalleryInfo> gis, int maxPage);
|
||||
public void onFailure(long taskStamp, String eMsg);
|
||||
|
||||
+372
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.widget.recyclerview;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Based on Roman Nurik's Android-SwipeToDismiss lib <a href="https://github.com/romannurik/Android-SwipeToDismiss">https://github.com/romannurik/Android-SwipeToDismiss</a>
|
||||
* <p/>
|
||||
* RecyclerView.OnItemTouchListener that allows items to be swiped and dismissed.
|
||||
* <p/>
|
||||
* Typical usage:
|
||||
* <p/>
|
||||
* <pre>
|
||||
* {@code
|
||||
* swipeToDismissTouchListener = new SwipeToDismissTouchListener(recyclerView, new SwipeToDismissTouchListener.DismissCallbacks() {
|
||||
* @Override
|
||||
* public SwipeToDismissTouchListener.SwipeDirection dismissDirection(int position) {
|
||||
* return SwipeToDismissTouchListener.SwipeDirection.RIGHT;
|
||||
* }
|
||||
* @Override
|
||||
* public void onDismiss(RecyclerView view, List<SwipeToDismissTouchListener.PendingDismissData> dismissData) {
|
||||
* for (SwipeToDismissTouchListener.PendingDismissData data : dismissData) {
|
||||
* adapter.removeItem(data.position);
|
||||
* adapter.notifyItemRemoved(data.position);
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class SwipeToDismissTouchListener implements RecyclerView.OnItemTouchListener {
|
||||
|
||||
private final RecyclerView mRecyclerView;
|
||||
private int mSlop;
|
||||
private int mMinFlingVelocity;
|
||||
private int mMaxFlingVelocity;
|
||||
private long mAnimationTime;
|
||||
|
||||
private DismissCallbacks mCallbacks;
|
||||
private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
|
||||
|
||||
// Transient properties
|
||||
private float mDownX;
|
||||
private float mDownY;
|
||||
private boolean mSwiping;
|
||||
private int mSwipingSlop;
|
||||
private VelocityTracker mVelocityTracker;
|
||||
private float mTranslationX;
|
||||
private boolean mPaused = false;
|
||||
private View mSwipeView;
|
||||
private int mDismissCount = 0;
|
||||
private List<PendingDismissData> mPendingDismisses = new ArrayList<PendingDismissData>();
|
||||
private SwipeDirection mAllowedSwipeDirection = SwipeDirection.NONE;
|
||||
|
||||
|
||||
private int[] notToDismissPositionArray;
|
||||
|
||||
/**
|
||||
* Constructs a new swipe-to-dismiss OnItemTouchListener for RecyclerView
|
||||
*
|
||||
* @param recyclerView RecyclerView
|
||||
* @param callbacks The callback to trigger when the user has indicated that she would like to
|
||||
* dismiss this view.
|
||||
*/
|
||||
public SwipeToDismissTouchListener(RecyclerView recyclerView, DismissCallbacks callbacks) {
|
||||
ViewConfiguration vc = ViewConfiguration.get(recyclerView.getContext());
|
||||
mSlop = vc.getScaledTouchSlop();
|
||||
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 4;
|
||||
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
|
||||
mAnimationTime = recyclerView.getContext().getResources().getInteger(android.R.integer.config_shortAnimTime);
|
||||
mRecyclerView = recyclerView;
|
||||
mCallbacks = callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new swipe-to-dismiss OnItemTouchListener for RecyclerView
|
||||
*
|
||||
* @param recyclerView RecyclerView
|
||||
* @param callbacks The callback to trigger when the user has indicated that she would like to
|
||||
* dismiss this view.
|
||||
*/
|
||||
public SwipeToDismissTouchListener(RecyclerView recyclerView, DismissCallbacks callbacks, int[] notToDismiss) {
|
||||
ViewConfiguration vc = ViewConfiguration.get(recyclerView.getContext());
|
||||
mSlop = vc.getScaledTouchSlop();
|
||||
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 4;
|
||||
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
|
||||
mAnimationTime = recyclerView.getContext().getResources().getInteger(android.R.integer.config_shortAnimTime);
|
||||
mRecyclerView = recyclerView;
|
||||
mCallbacks = callbacks;
|
||||
notToDismissPositionArray = notToDismiss;
|
||||
}
|
||||
|
||||
public int[] getNotToDismissPositionArray() {
|
||||
return notToDismissPositionArray;
|
||||
}
|
||||
|
||||
public void setNotToDismissPositionArray(int[] notToDismissPositionArray) {
|
||||
this.notToDismissPositionArray = notToDismissPositionArray;
|
||||
}
|
||||
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
mPaused = !enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) {
|
||||
motionEvent.offsetLocation(mTranslationX, 0);
|
||||
|
||||
switch (motionEvent.getActionMasked()) {
|
||||
case MotionEvent.ACTION_UP: {
|
||||
up(motionEvent);
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
cancel();
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
move(motionEvent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(final RecyclerView view, MotionEvent motionEvent) {
|
||||
if (mPaused) return false;
|
||||
// offset because the view is translated during swipe
|
||||
motionEvent.offsetLocation(mTranslationX, 0);
|
||||
|
||||
if (mViewWidth < 2) {
|
||||
mViewWidth = view.getWidth();
|
||||
}
|
||||
|
||||
switch (motionEvent.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
return down(motionEvent);
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
return move(motionEvent);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean down(MotionEvent motionEvent) {
|
||||
|
||||
if (mPaused) return false;
|
||||
|
||||
mDownX = motionEvent.getRawX();
|
||||
mDownY = motionEvent.getRawY();
|
||||
mSwipeView = mRecyclerView.findChildViewUnder(motionEvent.getX(), motionEvent.getY());
|
||||
if (mSwipeView == null) return false;
|
||||
int pos = mRecyclerView.getChildLayoutPosition(mSwipeView);
|
||||
if (notToDismissPositionArray != null && notToDismissPositionArray.length > 0) {
|
||||
for (int notToDismiss : notToDismissPositionArray) {
|
||||
if (pos == notToDismiss) return false;
|
||||
}
|
||||
}
|
||||
mAllowedSwipeDirection = mCallbacks.dismissDirection(pos);
|
||||
if (mAllowedSwipeDirection != SwipeDirection.NONE) {
|
||||
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
mVelocityTracker.addMovement(motionEvent);
|
||||
return false;
|
||||
}
|
||||
resetMotion();
|
||||
return false;
|
||||
}
|
||||
|
||||
private void cancel() {
|
||||
if (mVelocityTracker == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mSwipeView.animate()
|
||||
.translationX(0)
|
||||
.alpha(1)
|
||||
.setDuration(mAnimationTime)
|
||||
.setListener(null);
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
mTranslationX = 0;
|
||||
mDownX = 0;
|
||||
mDownY = 0;
|
||||
mSwiping = false;
|
||||
mSwipeView = null;
|
||||
}
|
||||
|
||||
private void up(MotionEvent motionEvent) {
|
||||
if (mPaused || mVelocityTracker == null || mSwipeView == null) {
|
||||
return;
|
||||
}
|
||||
mSwipeView.setPressed(false);
|
||||
float deltaX = motionEvent.getRawX() - mDownX;
|
||||
mVelocityTracker.addMovement(motionEvent);
|
||||
mVelocityTracker.computeCurrentVelocity(1000);
|
||||
float velocityX = mVelocityTracker.getXVelocity();
|
||||
float absVelocityX = Math.abs(velocityX);
|
||||
float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
|
||||
boolean dismiss = false;
|
||||
boolean dismissRight = false;
|
||||
if (Math.abs(deltaX) > mViewWidth / 2 && mSwiping) {
|
||||
dismiss = true;
|
||||
dismissRight = deltaX > 0;
|
||||
} else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
|
||||
&& absVelocityY < absVelocityX
|
||||
&& absVelocityY < absVelocityX && mSwiping) {
|
||||
// dismiss only if flinging in the same direction as dragging
|
||||
dismiss = (velocityX < 0) == (deltaX < 0);
|
||||
dismissRight = mVelocityTracker.getXVelocity() > 0;
|
||||
}
|
||||
if (dismiss) {
|
||||
// dismiss
|
||||
final int pos = mRecyclerView.getChildLayoutPosition(mSwipeView);
|
||||
final View swipeViewCopy = mSwipeView;
|
||||
final SwipeDirection swipeDirection = dismissRight ? SwipeDirection.RIGHT : SwipeDirection.LEFT;
|
||||
++mDismissCount;
|
||||
mSwipeView.animate()
|
||||
.translationX(dismissRight ? mViewWidth : -mViewWidth)
|
||||
.alpha(0)
|
||||
.setDuration(mAnimationTime);
|
||||
|
||||
//this is instead of unreliable onAnimationEnd callback
|
||||
swipeViewCopy.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
performDismiss(swipeViewCopy, pos, swipeDirection);
|
||||
swipeViewCopy.setTranslationX(0);
|
||||
// swipeViewCopy.setAlpha(1);
|
||||
|
||||
}
|
||||
}, mAnimationTime + 100);
|
||||
|
||||
} else if (mSwiping) {
|
||||
// cancel
|
||||
mSwipeView.animate()
|
||||
.translationX(0)
|
||||
.alpha(1)
|
||||
.setDuration(mAnimationTime)
|
||||
.setListener(null);
|
||||
}
|
||||
|
||||
|
||||
resetMotion();
|
||||
}
|
||||
|
||||
private boolean move(MotionEvent motionEvent) {
|
||||
if (mSwipeView == null || mVelocityTracker == null || mPaused) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mVelocityTracker.addMovement(motionEvent);
|
||||
float deltaX = motionEvent.getRawX() - mDownX;
|
||||
float deltaY = motionEvent.getRawY() - mDownY;
|
||||
if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) {
|
||||
mSwiping = true;
|
||||
mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);
|
||||
mSwipeView.setPressed(false);
|
||||
|
||||
MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
|
||||
cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (motionEvent.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
|
||||
mSwipeView.onTouchEvent(cancelEvent);
|
||||
}
|
||||
|
||||
//Prevent swipes to disallowed directions
|
||||
if ((deltaX < 0 && mAllowedSwipeDirection == SwipeDirection.RIGHT) || (deltaX > 0 && mAllowedSwipeDirection == SwipeDirection.LEFT)) {
|
||||
resetMotion();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mSwiping) {
|
||||
mCallbacks.onTouchDown();
|
||||
mTranslationX = deltaX;
|
||||
mSwipeView.setTranslationX(deltaX - mSwipingSlop);
|
||||
mSwipeView.setAlpha(Math.max(0f, Math.min(1f,
|
||||
1f - 2f * Math.abs(deltaX) / mViewWidth)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void resetMotion() {
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
mCallbacks.onResetMotion();
|
||||
mTranslationX = 0;
|
||||
mDownX = 0;
|
||||
mDownY = 0;
|
||||
mSwiping = false;
|
||||
mSwipeView = null;
|
||||
mAllowedSwipeDirection = SwipeDirection.NONE;
|
||||
}
|
||||
|
||||
private void performDismiss(View dismissView, int pos, SwipeDirection direction) {
|
||||
--mDismissCount;
|
||||
mPendingDismisses.add(new PendingDismissData(pos, dismissView, direction));
|
||||
if (mDismissCount == 0) {
|
||||
Collections.sort(mPendingDismisses);
|
||||
List<PendingDismissData> dismissData = new ArrayList<>(mPendingDismisses);
|
||||
mCallbacks.onDismiss(mRecyclerView, dismissData);
|
||||
mPendingDismisses.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface DismissCallbacks {
|
||||
SwipeDirection dismissDirection(int position);
|
||||
|
||||
void onDismiss(RecyclerView view, List<PendingDismissData> dismissData);
|
||||
|
||||
void onResetMotion();
|
||||
|
||||
void onTouchDown();
|
||||
}
|
||||
|
||||
|
||||
public class PendingDismissData implements Comparable<PendingDismissData> {
|
||||
public int position;
|
||||
public View view;
|
||||
public SwipeDirection direction;
|
||||
|
||||
public PendingDismissData(int position, View view, SwipeDirection direction) {
|
||||
this.position = position;
|
||||
this.view = view;
|
||||
this.direction = direction;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull PendingDismissData other) {
|
||||
return other.position - position;
|
||||
}
|
||||
}
|
||||
|
||||
public enum SwipeDirection {
|
||||
LEFT, RIGHT, BOTH, NONE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user