Update to Android 6.0.

Replace my homemade swipe-to-dismiss and drag implementation SwipeListener with the RecyclerView native ItemTouchHelper.
Also add an undo snackbar when removing boards.
multisite
Floens 10 years ago
parent 48d8f94e94
commit 3ac7a1a348
  1. 21
      Clover/app/build.gradle
  2. 71
      Clover/app/src/main/java/org/floens/chan/ui/adapter/PinAdapter.java
  3. 110
      Clover/app/src/main/java/org/floens/chan/ui/controller/BoardEditController.java
  4. 9
      Clover/app/src/main/java/org/floens/chan/ui/controller/DrawerController.java
  5. 673
      Clover/app/src/main/java/org/floens/chan/ui/helper/SwipeItemAnimator.java
  6. 408
      Clover/app/src/main/java/org/floens/chan/ui/helper/SwipeListener.java
  7. 1
      Clover/app/src/main/res/values/strings.xml

@ -1,12 +1,12 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
minSdkVersion 14
targetSdkVersion 22
targetSdkVersion 23
versionName "v2.0.0"
versionCode 51
@ -21,6 +21,9 @@ android {
abortOnError false
}
// Needed for volley
useLibrary 'org.apache.http.legacy'
/*
If you want to sign releases, make a file in app/keys.properties with the following content:
keystoreFile=yourkey.store
@ -71,12 +74,12 @@ android {
}
dependencies {
compile 'com.android.support:support-v13:22.2.1'
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.android.support:recyclerview-v7:22.2.1'
compile 'com.android.support:cardview-v7:22.2.1'
compile 'com.android.support:support-annotations:22.2.1'
compile 'com.android.support:design:22.2.1'
compile 'com.android.support:support-v13:23.1.0'
compile 'com.android.support:appcompat-v7:23.1.0'
compile 'com.android.support:recyclerview-v7:23.1.0'
compile 'com.android.support:cardview-v7:23.1.0'
compile 'com.android.support:support-annotations:23.1.0'
compile 'com.android.support:design:23.1.0'
compile 'org.jsoup:jsoup:1.8.2'
compile 'com.j256.ormlite:ormlite-core:4.48'

@ -18,6 +18,7 @@
package org.floens.chan.ui.adapter;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
@ -29,7 +30,6 @@ import org.floens.chan.R;
import org.floens.chan.core.model.Pin;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.helper.PostHelper;
import org.floens.chan.ui.helper.SwipeListener;
import org.floens.chan.ui.view.ThumbnailView;
import org.floens.chan.utils.AndroidUtils;
@ -43,7 +43,7 @@ import static org.floens.chan.utils.AndroidUtils.getAttrColor;
import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground;
import static org.floens.chan.utils.AndroidUtils.sp;
public class PinAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements SwipeListener.Callback {
public class PinAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int PIN_OFFSET = 3;
private static final int TYPE_HEADER = 0;
@ -64,6 +64,41 @@ public class PinAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> im
this.highlighted = highlighted;
}
public ItemTouchHelper.Callback getItemTouchHelperCallback() {
return new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
boolean pin = getItemViewType(viewHolder.getAdapterPosition()) == TYPE_PIN;
int dragFlags = pin ? ItemTouchHelper.UP | ItemTouchHelper.DOWN : 0;
int swipeFlags = pin ? ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT : 0;
return makeMovementFlags(dragFlags, swipeFlags);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int from = viewHolder.getAdapterPosition();
int to = target.getAdapterPosition();
if (getItemViewType(to) == TYPE_PIN) {
Pin item = pins.remove(from - PIN_OFFSET);
pins.add(to - PIN_OFFSET, item);
notifyItemMoved(from, to);
applyOrder();
return true;
} else {
return false;
}
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
// Will call #onPinRemoved, and remove the pin from pins
callback.onPinRemoved(pins.get(viewHolder.getAdapterPosition() - PIN_OFFSET));
}
};
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
@ -173,34 +208,6 @@ public class PinAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> im
}
}
@Override
public SwipeListener.Swipeable getSwipeable(int position) {
return getItemViewType(position) == TYPE_PIN ? SwipeListener.Swipeable.RIGHT : SwipeListener.Swipeable.NO;
}
@Override
public void removeItem(int position) {
applyOrder();
callback.onPinRemoved(pins.get(position - PIN_OFFSET));
}
@Override
public boolean isMoveable(int position) {
return getItemViewType(position) == TYPE_PIN;
}
@Override
public void moveItem(int from, int to) {
Pin item = pins.remove(from - PIN_OFFSET);
pins.add(to - PIN_OFFSET, item);
notifyItemMoved(from, to);
}
@Override
public void movingDone() {
applyOrder();
}
public void updatePinViewHolder(PinViewHolder holder, Pin pin) {
CharSequence text = pin.loadable.title;
if (pin.archived) {
@ -290,7 +297,7 @@ public class PinAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> im
}
});
itemView.setOnLongClickListener(new View.OnLongClickListener() {
/*itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int pos = getAdapterPosition() - PIN_OFFSET;
@ -300,7 +307,7 @@ public class PinAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> im
return true;
}
});
});*/
watchCountText.setOnClickListener(new View.OnClickListener() {
@Override

@ -23,14 +23,15 @@ import android.content.DialogInterface;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Filter;
@ -46,7 +47,6 @@ import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.model.Board;
import org.floens.chan.ui.drawable.ThumbDrawable;
import org.floens.chan.ui.helper.BoardHelper;
import org.floens.chan.ui.helper.SwipeListener;
import org.floens.chan.ui.toolbar.ToolbarMenu;
import org.floens.chan.ui.toolbar.ToolbarMenuItem;
import org.floens.chan.ui.view.FloatingMenuItem;
@ -61,7 +61,7 @@ import java.util.Locale;
import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.fixSnackbarText;
public class BoardEditController extends Controller implements SwipeListener.Callback, View.OnClickListener, ToolbarMenuItem.ToolbarMenuItemCallback {
public class BoardEditController extends Controller implements View.OnClickListener, ToolbarMenuItem.ToolbarMenuItemCallback {
private static final int OPTION_SORT_A_Z = 1;
private final BoardManager boardManager = Chan.getBoardManager();
@ -69,6 +69,7 @@ public class BoardEditController extends Controller implements SwipeListener.Cal
private RecyclerView recyclerView;
private BoardEditAdapter adapter;
private FloatingActionButton add;
private ItemTouchHelper itemTouchHelper;
private List<Board> boards;
@ -90,6 +91,7 @@ public class BoardEditController extends Controller implements SwipeListener.Cal
view = inflateRes(R.layout.controller_board_edit);
recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
add = (FloatingActionButton) view.findViewById(R.id.add);
add.setOnClickListener(this);
@ -98,7 +100,53 @@ public class BoardEditController extends Controller implements SwipeListener.Cal
adapter = new BoardEditAdapter();
recyclerView.setAdapter(adapter);
new SwipeListener(context, recyclerView, this);
itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
boolean isBoardItem = viewHolder.getAdapterPosition() > 0;
int dragFlags = isBoardItem ? ItemTouchHelper.UP | ItemTouchHelper.DOWN : 0;
int swipeFlags = isBoardItem ? ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT : 0;
return makeMovementFlags(dragFlags, swipeFlags);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int from = viewHolder.getAdapterPosition();
int to = target.getAdapterPosition();
if (to > 0) {
Board item = boards.remove(from - 1);
boards.add(to - 1, item);
adapter.notifyItemMoved(from, to);
return true;
} else {
return false;
}
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
final int position = viewHolder.getAdapterPosition();
final Board board = boards.get(position - 1);
board.saved = false;
boards.remove(position - 1);
adapter.notifyItemRemoved(position);
Snackbar snackbar = Snackbar.make(view, context.getString(R.string.board_edit_board_removed, board.key), Snackbar.LENGTH_LONG);
fixSnackbarText(context, snackbar);
snackbar.setAction(R.string.undo, new View.OnClickListener() {
@Override
public void onClick(View v) {
board.saved = true;
boards.add(position - 1, board);
adapter.notifyDataSetChanged();
}
});
snackbar.show();
}
});
itemTouchHelper.attachToRecyclerView(recyclerView);
}
@Override
@ -136,35 +184,6 @@ public class BoardEditController extends Controller implements SwipeListener.Cal
}
}
@Override
public SwipeListener.Swipeable getSwipeable(int position) {
return (position > 0 && boards.size() > 1) ? SwipeListener.Swipeable.BOTH : SwipeListener.Swipeable.NO;
}
@Override
public void removeItem(int position) {
Board board = boards.get(position - 1);
board.saved = false;
boards.remove(position - 1);
adapter.notifyItemRemoved(position);
}
@Override
public boolean isMoveable(int position) {
return position > 0;
}
@Override
public void moveItem(int from, int to) {
Board item = boards.remove(from - 1);
boards.add(to - 1, item);
adapter.notifyItemMoved(from, to);
}
@Override
public void movingDone() {
}
private void showAddBoardDialog() {
LinearLayout wrap = new LinearLayout(context);
wrap.setPadding(dp(16), dp(16), dp(16), 0);
@ -303,10 +322,8 @@ public class BoardEditController extends Controller implements SwipeListener.Cal
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@SuppressLint("ViewHolder")
TextView view = (TextView) inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
TextView view = (TextView) LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1, parent, false);
Board b = filtered.get(position);
view.setText("/" + b.value + "/ - " + b.key);
@ -314,9 +331,7 @@ public class BoardEditController extends Controller implements SwipeListener.Cal
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(autoCompleteView.getWindowToken(), 0);
AndroidUtils.hideKeyboard(autoCompleteView);
}
return false;
@ -353,7 +368,7 @@ public class BoardEditController extends Controller implements SwipeListener.Cal
private List<Board> getBoards() {
// Lets be cheaty here: if the user has nsfw boards in the list,
// show them in the autofiller.
// show them in the autofiller, hide them otherwise.
boolean showUnsafe = false;
for (Board has : currentlyEditing) {
if (!has.workSafe) {
@ -422,16 +437,25 @@ public class BoardEditController extends Controller implements SwipeListener.Cal
}
private class BoardEditItem extends RecyclerView.ViewHolder {
private ImageView image;
private ImageView thumb;
private TextView text;
private TextView description;
public BoardEditItem(View itemView) {
super(itemView);
image = (ImageView) itemView.findViewById(R.id.thumb);
thumb = (ImageView) itemView.findViewById(R.id.thumb);
text = (TextView) itemView.findViewById(R.id.text);
description = (TextView) itemView.findViewById(R.id.description);
image.setImageDrawable(new ThumbDrawable());
thumb.setImageDrawable(new ThumbDrawable());
thumb.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
itemTouchHelper.startDrag(BoardEditItem.this);
}
return false;
}
});
}
}

@ -23,7 +23,9 @@ import android.content.res.Configuration;
import android.support.design.widget.Snackbar;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.KeyEvent;
@ -42,7 +44,6 @@ import org.floens.chan.controller.NavigationController;
import org.floens.chan.core.manager.WatchManager;
import org.floens.chan.core.model.Pin;
import org.floens.chan.ui.adapter.PinAdapter;
import org.floens.chan.ui.helper.SwipeListener;
import org.floens.chan.utils.AndroidUtils;
import java.util.List;
@ -85,6 +86,7 @@ public class DrawerController extends Controller implements PinAdapter.Callback,
drawer = (LinearLayout) view.findViewById(R.id.drawer);
recyclerView = (RecyclerView) view.findViewById(R.id.drawer_recycler_view);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
settings = (LinearLayout) view.findViewById(R.id.settings);
settings.setOnClickListener(this);
theme().settingsDrawable.apply((ImageView) settings.findViewById(R.id.image));
@ -93,10 +95,11 @@ public class DrawerController extends Controller implements PinAdapter.Callback,
pinAdapter = new PinAdapter(this);
recyclerView.setAdapter(pinAdapter);
new SwipeListener(context, recyclerView, pinAdapter);
pinAdapter.onPinsChanged(watchManager.getPins());
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(pinAdapter.getItemTouchHelperCallback());
itemTouchHelper.attachToRecyclerView(recyclerView);
updateBadge();
AndroidUtils.waitForMeasure(drawer, new AndroidUtils.OnMeasuredCallback() {

@ -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();
}
}

@ -142,6 +142,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<string name="board_add_unknown_title">Unknown board code</string>
<string name="board_add_unknown">The board with code %1$s is not known.</string>
<string name="board_edit_sort_a_z">Sort A-Z</string>
<string name="board_edit_board_removed">Removed board \'%1$s\'</string>
<string name="filter_summary_all_boards">all boards</string>
<string name="filter_enabled">Enabled</string>

Loading…
Cancel
Save