Add quick search
This commit is contained in:
1 parent
c015d05e77
commit
1d65db78f2
100 files changed
+12863
-192
No files matched your search
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.animator;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
public abstract class BaseItemAnimator extends RecyclerView.ItemAnimator {
|
||||
private ItemAnimatorListener mListener;
|
||||
|
||||
/**
|
||||
* The interface to be implemented by listeners to animation events from this
|
||||
* ItemAnimator. This is used internally and is not intended for developers to
|
||||
* create directly.
|
||||
*/
|
||||
public interface ItemAnimatorListener {
|
||||
void onRemoveFinished(RecyclerView.ViewHolder item);
|
||||
|
||||
void onAddFinished(RecyclerView.ViewHolder item);
|
||||
|
||||
void onMoveFinished(RecyclerView.ViewHolder item);
|
||||
|
||||
void onChangeFinished(RecyclerView.ViewHolder item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal only:
|
||||
* Sets the listener that must be called when the animator is finished
|
||||
* animating the item (or immediately if no animation happens). This is set
|
||||
* internally and is not intended to be set by external code.
|
||||
*
|
||||
* @param listener The listener that must be called.
|
||||
*/
|
||||
public void setListener(ItemAnimatorListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onAddStarting(RecyclerView.ViewHolder item) {
|
||||
onAddStartingImpl(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onAddFinished(RecyclerView.ViewHolder item) {
|
||||
onAddFinishedImpl(item);
|
||||
|
||||
if (mListener != null) {
|
||||
mListener.onAddFinished(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onChangeStarting(RecyclerView.ViewHolder item, boolean oldItem) {
|
||||
onChangeStartingItem(item, oldItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onChangeFinished(RecyclerView.ViewHolder item, boolean oldItem) {
|
||||
onChangeFinishedImpl(item, oldItem);
|
||||
|
||||
if (mListener != null) {
|
||||
mListener.onChangeFinished(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onMoveStarting(RecyclerView.ViewHolder item) {
|
||||
onMoveStartingImpl(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onMoveFinished(RecyclerView.ViewHolder item) {
|
||||
onMoveFinishedImpl(item);
|
||||
|
||||
if (mListener != null) {
|
||||
mListener.onMoveFinished(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onRemoveStarting(RecyclerView.ViewHolder item) {
|
||||
onRemoveStartingImpl(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onRemoveFinished(RecyclerView.ViewHolder item) {
|
||||
onRemoveFinishedImpl(item);
|
||||
|
||||
if (mListener != null) {
|
||||
mListener.onRemoveFinished(item);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
protected void onAddStartingImpl(RecyclerView.ViewHolder item) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
protected void onAddFinishedImpl(RecyclerView.ViewHolder item) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
protected void onChangeStartingItem(RecyclerView.ViewHolder item, boolean oldItem) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
protected void onChangeFinishedImpl(RecyclerView.ViewHolder item, boolean oldItem) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
protected void onMoveStartingImpl(RecyclerView.ViewHolder item) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
protected void onMoveFinishedImpl(RecyclerView.ViewHolder item) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
protected void onRemoveStartingImpl(RecyclerView.ViewHolder item) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
protected void onRemoveFinishedImpl(RecyclerView.ViewHolder item) {
|
||||
}
|
||||
|
||||
public boolean dispatchFinishedWhenDone() {
|
||||
if (!isRunning()) {
|
||||
dispatchAnimationsFinished();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean debugLogEnabled() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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 langua
|
||||
* ge governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.h6ah4i.android.widget.advrecyclerview.animator;
|
||||
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.ItemAddAnimationManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.ItemChangeAnimationManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.ItemMoveAnimationManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.ItemRemoveAnimationManager;
|
||||
|
||||
public abstract class GeneralItemAnimator extends BaseItemAnimator {
|
||||
private static final String TAG = "ARVGeneralItemAnimator";
|
||||
|
||||
private boolean mDebug;
|
||||
|
||||
private ItemRemoveAnimationManager mRemoveAnimationManager;
|
||||
private ItemAddAnimationManager mAddAnimationsManager;
|
||||
private ItemChangeAnimationManager mChangeAnimationsManager;
|
||||
private ItemMoveAnimationManager mMoveAnimationsManager;
|
||||
|
||||
protected GeneralItemAnimator() {
|
||||
setup();
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
onSetup();
|
||||
|
||||
if (mRemoveAnimationManager == null ||
|
||||
mAddAnimationsManager == null ||
|
||||
mChangeAnimationsManager == null ||
|
||||
mMoveAnimationsManager == null) {
|
||||
throw new IllegalStateException("setup incomplete");
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void onSetup();
|
||||
|
||||
@Override
|
||||
public void runPendingAnimations() {
|
||||
if (!hasPendingAnimations()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onSchedulePendingAnimations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateRemove(final RecyclerView.ViewHolder holder) {
|
||||
if (mDebug) {
|
||||
Log.d(TAG, "animateRemove(id = " + holder.getItemId() + ", position = " + holder.getLayoutPosition() + ")");
|
||||
}
|
||||
|
||||
return mRemoveAnimationManager.addPendingAnimation(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
|
||||
if (mDebug) {
|
||||
Log.d(TAG, "animateAdd(id = " + holder.getItemId() + ", position = " + holder.getLayoutPosition() + ")");
|
||||
}
|
||||
|
||||
return mAddAnimationsManager.addPendingAnimation(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
|
||||
if (mDebug) {
|
||||
Log.d(TAG, "animateMove(id = " + holder.getItemId() + ", position = " + holder.getLayoutPosition() + ", fromX = " + fromX + ", fromY = " + fromY + ", toX = " + toX + ", toY = " + toY + ")");
|
||||
}
|
||||
|
||||
return mMoveAnimationsManager.addPendingAnimation(holder, fromX, fromY, toX, toY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateChange(
|
||||
RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) {
|
||||
if (mDebug) {
|
||||
final String oldId = (oldHolder != null) ? Long.toString(oldHolder.getItemId()) : "-";
|
||||
final String oldPosition = (oldHolder != null) ? Long.toString(oldHolder.getLayoutPosition()) : "-";
|
||||
final String newId = (newHolder != null) ? Long.toString(newHolder.getItemId()) : "-";
|
||||
final String newPosition = (newHolder != null) ? Long.toString(newHolder.getLayoutPosition()) : "-";
|
||||
|
||||
Log.d(TAG, "animateChange(old.id = " + oldId + ", old.position = " + oldPosition + ", new.id = " + newId + ", new.position = " + newPosition
|
||||
+ ", fromX = " + fromX + ", fromY = " + fromY + ", toX = " + toX + ", toY = " + toY + ")");
|
||||
}
|
||||
|
||||
return mChangeAnimationsManager.addPendingAnimation(oldHolder, newHolder, fromX, fromY, toX, toY);
|
||||
}
|
||||
|
||||
protected void cancelAnimations(RecyclerView.ViewHolder item) {
|
||||
ViewCompat.animate(item.itemView).cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimation(RecyclerView.ViewHolder item) {
|
||||
|
||||
// this will trigger end callback which should set properties to their target values.
|
||||
cancelAnimations(item);
|
||||
|
||||
mMoveAnimationsManager.endPendingAnimations(item);
|
||||
mChangeAnimationsManager.endPendingAnimations(item);
|
||||
mRemoveAnimationManager.endPendingAnimations(item);
|
||||
mAddAnimationsManager.endPendingAnimations(item);
|
||||
|
||||
mMoveAnimationsManager.endDeferredReadyAnimations(item);
|
||||
mChangeAnimationsManager.endDeferredReadyAnimations(item);
|
||||
mRemoveAnimationManager.endDeferredReadyAnimations(item);
|
||||
mAddAnimationsManager.endDeferredReadyAnimations(item);
|
||||
|
||||
// animations should be ended by the cancel above.
|
||||
if (mRemoveAnimationManager.removeFromActive(item) && mDebug) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in the active animation list [remove]");
|
||||
}
|
||||
|
||||
if (mAddAnimationsManager.removeFromActive(item) && mDebug) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in the active animation list [add]");
|
||||
}
|
||||
|
||||
if (mChangeAnimationsManager.removeFromActive(item) && mDebug) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in the active animation list [change]");
|
||||
}
|
||||
|
||||
if (mMoveAnimationsManager.removeFromActive(item) && mDebug) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in the active animation list [move]");
|
||||
}
|
||||
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return (mRemoveAnimationManager.isRunning() ||
|
||||
mAddAnimationsManager.isRunning() ||
|
||||
mChangeAnimationsManager.isRunning() ||
|
||||
mMoveAnimationsManager.isRunning());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimations() {
|
||||
// end all pending animations
|
||||
mMoveAnimationsManager.endAllPendingAnimations();
|
||||
mRemoveAnimationManager.endAllPendingAnimations();
|
||||
mAddAnimationsManager.endAllPendingAnimations();
|
||||
mChangeAnimationsManager.endAllPendingAnimations();
|
||||
|
||||
if (!isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// end all deferred animations
|
||||
mMoveAnimationsManager.endAllDeferredReadyAnimations();
|
||||
mAddAnimationsManager.endAllDeferredReadyAnimations();
|
||||
mChangeAnimationsManager.endAllDeferredReadyAnimations();
|
||||
|
||||
// cancel all started animations
|
||||
mRemoveAnimationManager.cancelAllStartedAnimations();
|
||||
mMoveAnimationsManager.cancelAllStartedAnimations();
|
||||
mAddAnimationsManager.cancelAllStartedAnimations();
|
||||
mChangeAnimationsManager.cancelAllStartedAnimations();
|
||||
|
||||
dispatchAnimationsFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean debugLogEnabled() {
|
||||
return mDebug;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchFinishedWhenDone() {
|
||||
if (mDebug && !isRunning()) {
|
||||
Log.d(TAG, "dispatchFinishedWhenDone()");
|
||||
}
|
||||
|
||||
return super.dispatchFinishedWhenDone();
|
||||
}
|
||||
|
||||
protected boolean hasPendingAnimations() {
|
||||
return (mRemoveAnimationManager.hasPending() ||
|
||||
mMoveAnimationsManager.hasPending() ||
|
||||
mChangeAnimationsManager.hasPending() ||
|
||||
mAddAnimationsManager.hasPending());
|
||||
}
|
||||
|
||||
protected ItemRemoveAnimationManager getRemoveAnimationManager() {
|
||||
return mRemoveAnimationManager;
|
||||
}
|
||||
|
||||
protected void setItemRemoveAnimationManager(ItemRemoveAnimationManager removeAnimationManager) {
|
||||
mRemoveAnimationManager = removeAnimationManager;
|
||||
}
|
||||
|
||||
protected ItemAddAnimationManager getItemAddAnimationsManager() {
|
||||
return mAddAnimationsManager;
|
||||
}
|
||||
|
||||
protected void setItemAddAnimationsManager(ItemAddAnimationManager addAnimationsManager) {
|
||||
mAddAnimationsManager = addAnimationsManager;
|
||||
}
|
||||
|
||||
protected ItemChangeAnimationManager getItemChangeAnimationsManager() {
|
||||
return mChangeAnimationsManager;
|
||||
}
|
||||
|
||||
protected void setItemChangeAnimationsManager(ItemChangeAnimationManager changeAnimationsManager) {
|
||||
mChangeAnimationsManager = changeAnimationsManager;
|
||||
}
|
||||
|
||||
protected ItemMoveAnimationManager getItemMoveAnimationsManager() {
|
||||
return mMoveAnimationsManager;
|
||||
}
|
||||
|
||||
protected void setItemMoveAnimationsManager(ItemMoveAnimationManager moveAnimationsManager) {
|
||||
mMoveAnimationsManager = moveAnimationsManager;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return mDebug;
|
||||
}
|
||||
|
||||
public void setDebug(boolean debug) {
|
||||
mDebug = debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule order and timing of pending animations.
|
||||
* Override this method to custom animation order.
|
||||
*/
|
||||
protected void onSchedulePendingAnimations() {
|
||||
schedulePendingAnimationsByDefaultRule();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
protected void schedulePendingAnimationsByDefaultRule() {
|
||||
final boolean removalsPending = mRemoveAnimationManager.hasPending();
|
||||
final boolean movesPending = mMoveAnimationsManager.hasPending();
|
||||
final boolean changesPending = mChangeAnimationsManager.hasPending();
|
||||
final boolean additionsPending = mAddAnimationsManager.hasPending();
|
||||
|
||||
final long removeDuration = removalsPending ? getRemoveDuration() : 0;
|
||||
final long moveDuration = movesPending ? getMoveDuration() : 0;
|
||||
final long changeDuration = changesPending ? getChangeDuration() : 0;
|
||||
|
||||
if (removalsPending) {
|
||||
mRemoveAnimationManager.runPendingAnimations(false, 0);
|
||||
}
|
||||
|
||||
if (movesPending) {
|
||||
final boolean deferred = removalsPending;
|
||||
final long deferredDelay = removeDuration;
|
||||
mMoveAnimationsManager.runPendingAnimations(deferred, deferredDelay);
|
||||
}
|
||||
|
||||
if (changesPending) {
|
||||
final boolean deferred = removalsPending;
|
||||
final long deferredDelay = removeDuration;
|
||||
mChangeAnimationsManager.runPendingAnimations(deferred, deferredDelay);
|
||||
}
|
||||
|
||||
if (additionsPending) {
|
||||
final boolean deferred = (removalsPending || movesPending || changesPending);
|
||||
final long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
|
||||
final long deferredDelay = (deferred) ? totalDelay : 0;
|
||||
mAddAnimationsManager.runPendingAnimations(deferred, deferredDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.animator;
|
||||
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.ViewPropertyAnimatorCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.AddAnimationInfo;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.ChangeAnimationInfo;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.ItemAddAnimationManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.ItemChangeAnimationManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.ItemMoveAnimationManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.ItemRemoveAnimationManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.MoveAnimationInfo;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.RemoveAnimationInfo;
|
||||
|
||||
public class RefactoredDefaultItemAnimator extends GeneralItemAnimator {
|
||||
|
||||
@Override
|
||||
protected void onSetup() {
|
||||
setItemAddAnimationsManager(new DefaultItemAddAnimationManager(this));
|
||||
setItemRemoveAnimationManager(new DefaultItemRemoveAnimationManager(this));
|
||||
setItemChangeAnimationsManager(new DefaultItemChangeAnimationManager(this));
|
||||
setItemMoveAnimationsManager(new DefaultItemMoveAnimationManager(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSchedulePendingAnimations() {
|
||||
schedulePendingAnimationsByDefaultRule();
|
||||
}
|
||||
|
||||
/**
|
||||
* Item Animation manager for ADD operation (Same behavior as DefaultItemAnimator class)
|
||||
*/
|
||||
protected static class DefaultItemAddAnimationManager extends ItemAddAnimationManager {
|
||||
|
||||
public DefaultItemAddAnimationManager(BaseItemAnimator itemAnimator) {
|
||||
super(itemAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateAnimation(AddAnimationInfo info) {
|
||||
final ViewPropertyAnimatorCompat animator = ViewCompat.animate(info.holder.itemView);
|
||||
|
||||
animator.alpha(1);
|
||||
animator.setDuration(getDuration());
|
||||
|
||||
startActiveItemAnimation(info, info.holder, animator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedSuccessfully(AddAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedBeforeStarted(AddAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
ViewCompat.setAlpha(item.itemView, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationCancel(AddAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
ViewCompat.setAlpha(item.itemView, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPendingAnimation(RecyclerView.ViewHolder item) {
|
||||
endAnimation(item);
|
||||
|
||||
ViewCompat.setAlpha(item.itemView, 0);
|
||||
|
||||
enqueuePendingAnimationInfo(new AddAnimationInfo(item));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Item Animation manager for REMOVE operation (Same behavior as DefaultItemAnimator class)
|
||||
*/
|
||||
protected static class DefaultItemRemoveAnimationManager extends ItemRemoveAnimationManager {
|
||||
|
||||
public DefaultItemRemoveAnimationManager(BaseItemAnimator itemAnimator) {
|
||||
super(itemAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateAnimation(RemoveAnimationInfo info) {
|
||||
final ViewPropertyAnimatorCompat animator = ViewCompat.animate(info.holder.itemView);
|
||||
|
||||
animator.setDuration(getDuration());
|
||||
animator.alpha(0);
|
||||
|
||||
startActiveItemAnimation(info, info.holder, animator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedSuccessfully(RemoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedBeforeStarted(RemoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationCancel(RemoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPendingAnimation(RecyclerView.ViewHolder holder) {
|
||||
endAnimation(holder);
|
||||
|
||||
enqueuePendingAnimationInfo(new RemoveAnimationInfo(holder));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Item Animation manager for CHANGE operation (Same behavior as DefaultItemAnimator class)
|
||||
*/
|
||||
protected static class DefaultItemChangeAnimationManager extends ItemChangeAnimationManager {
|
||||
public DefaultItemChangeAnimationManager(BaseItemAnimator itemAnimator) {
|
||||
super(itemAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateChangeAnimationForOldItem(ChangeAnimationInfo info) {
|
||||
final ViewPropertyAnimatorCompat animator = ViewCompat.animate(info.oldHolder.itemView);
|
||||
|
||||
animator.setDuration(getDuration());
|
||||
animator.translationX(info.toX - info.fromX);
|
||||
animator.translationY(info.toY - info.fromY);
|
||||
animator.alpha(0);
|
||||
|
||||
startActiveItemAnimation(info, info.oldHolder, animator);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreateChangeAnimationForNewItem(ChangeAnimationInfo info) {
|
||||
final ViewPropertyAnimatorCompat animator = ViewCompat.animate(info.newHolder.itemView);
|
||||
|
||||
animator.translationX(0);
|
||||
animator.translationY(0);
|
||||
animator.setDuration(getDuration());
|
||||
animator.alpha(1);
|
||||
|
||||
startActiveItemAnimation(info, info.newHolder, animator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedSuccessfully(ChangeAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedBeforeStarted(ChangeAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationCancel(ChangeAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPendingAnimation(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);
|
||||
|
||||
final int deltaX = (int) (toX - fromX - prevTranslationX);
|
||||
final 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);
|
||||
}
|
||||
|
||||
enqueuePendingAnimationInfo(new ChangeAnimationInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Item Animation manager for MOVE operation (Same behavior as DefaultItemAnimator class)
|
||||
*/
|
||||
protected static class DefaultItemMoveAnimationManager extends ItemMoveAnimationManager {
|
||||
|
||||
public DefaultItemMoveAnimationManager(BaseItemAnimator itemAnimator) {
|
||||
super(itemAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateAnimation(MoveAnimationInfo info) {
|
||||
final View view = info.holder.itemView;
|
||||
final int deltaX = info.toX - info.fromX;
|
||||
final int deltaY = info.toY - info.fromY;
|
||||
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.animate(view).translationX(0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.animate(view).translationY(0);
|
||||
}
|
||||
|
||||
final ViewPropertyAnimatorCompat animator = ViewCompat.animate(view);
|
||||
|
||||
animator.setDuration(getDuration());
|
||||
|
||||
startActiveItemAnimation(info, info.holder, animator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedSuccessfully(MoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedBeforeStarted(MoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationCancel(MoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
final int deltaX = info.toX - info.fromX;
|
||||
final int deltaY = info.toY - info.fromY;
|
||||
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.animate(view).translationX(0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.animate(view).translationY(0);
|
||||
}
|
||||
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPendingAnimation(RecyclerView.ViewHolder item, int fromX, int fromY, int toX, int toY) {
|
||||
final View view = item.itemView;
|
||||
|
||||
fromX += ViewCompat.getTranslationX(item.itemView);
|
||||
fromY += ViewCompat.getTranslationY(item.itemView);
|
||||
|
||||
endAnimation(item);
|
||||
|
||||
final int deltaX = toX - fromX;
|
||||
final int deltaY = toY - fromY;
|
||||
|
||||
final MoveAnimationInfo info = new MoveAnimationInfo(item, fromX, fromY, toX, toY);
|
||||
|
||||
if (deltaX == 0 && deltaY == 0) {
|
||||
dispatchFinished(info, info.holder);
|
||||
info.clear(info.holder);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.setTranslationX(view, -deltaX);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.setTranslationY(view, -deltaY);
|
||||
}
|
||||
|
||||
enqueuePendingAnimationInfo(info);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+395
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.animator;
|
||||
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.ViewPropertyAnimatorCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.AddAnimationInfo;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.ChangeAnimationInfo;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.ItemAddAnimationManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.ItemChangeAnimationManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.ItemMoveAnimationManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.ItemRemoveAnimationManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.MoveAnimationInfo;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.impl.RemoveAnimationInfo;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemViewHolder;
|
||||
|
||||
public class SwipeDismissItemAnimator extends GeneralItemAnimator {
|
||||
|
||||
public static final Interpolator MOVE_INTERPOLATOR = new AccelerateDecelerateInterpolator();
|
||||
|
||||
@Override
|
||||
protected void onSetup() {
|
||||
setItemAddAnimationsManager(new DefaultItemAddAnimationManager(this));
|
||||
setItemRemoveAnimationManager(new SwipeDismissItemRemoveAnimationManager(this));
|
||||
setItemChangeAnimationsManager(new SwipeDismissItemChangeAnimationManager(this));
|
||||
setItemMoveAnimationsManager(new SwipeDismissItemMoveAnimationManager(this));
|
||||
|
||||
setRemoveDuration(150);
|
||||
setMoveDuration(150);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSchedulePendingAnimations() {
|
||||
schedulePendingAnimationsByDefaultRule();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelAnimations(RecyclerView.ViewHolder item) {
|
||||
super.cancelAnimations(item);
|
||||
|
||||
// if (item instanceof SwipeableItemViewHolder) {
|
||||
// final View containerView = ((SwipeableItemViewHolder) item).getSwipeableContainerView();
|
||||
// ViewCompat.animate(containerView).cancel();
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Item Animation manager for ADD operation (Same behavior as DefaultItemAnimator class)
|
||||
*/
|
||||
private static class DefaultItemAddAnimationManager extends ItemAddAnimationManager {
|
||||
|
||||
public DefaultItemAddAnimationManager(BaseItemAnimator itemAnimator) {
|
||||
super(itemAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateAnimation(AddAnimationInfo info) {
|
||||
final ViewPropertyAnimatorCompat animator = ViewCompat.animate(info.holder.itemView);
|
||||
|
||||
animator.alpha(1);
|
||||
animator.setDuration(getDuration());
|
||||
|
||||
startActiveItemAnimation(info, info.holder, animator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedSuccessfully(AddAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedBeforeStarted(AddAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
ViewCompat.setAlpha(item.itemView, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationCancel(AddAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
ViewCompat.setAlpha(item.itemView, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPendingAnimation(RecyclerView.ViewHolder item) {
|
||||
endAnimation(item);
|
||||
|
||||
ViewCompat.setAlpha(item.itemView, 0);
|
||||
|
||||
enqueuePendingAnimationInfo(new AddAnimationInfo(item));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Item Animation manager for REMOVE operation (Same behavior as DefaultItemAnimator class)
|
||||
*/
|
||||
private static class SwipeDismissItemRemoveAnimationManager extends ItemRemoveAnimationManager {
|
||||
private static final Interpolator DEFAULT_INTERPOLATOR = new AccelerateDecelerateInterpolator();
|
||||
|
||||
public SwipeDismissItemRemoveAnimationManager(BaseItemAnimator itemAnimator) {
|
||||
super(itemAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateAnimation(RemoveAnimationInfo info) {
|
||||
final ViewPropertyAnimatorCompat animator;
|
||||
|
||||
if (isSwipeDismissed(info.holder)) {
|
||||
final View view = info.holder.itemView;
|
||||
animator = ViewCompat.animate(view);
|
||||
animator.setDuration(getDuration());
|
||||
} else {
|
||||
final View view = info.holder.itemView;
|
||||
animator = ViewCompat.animate(view);
|
||||
animator.setDuration(getDuration());
|
||||
animator.setInterpolator(DEFAULT_INTERPOLATOR);
|
||||
animator.alpha(0);
|
||||
}
|
||||
|
||||
startActiveItemAnimation(info, info.holder, animator);
|
||||
}
|
||||
|
||||
private static boolean isSwipeDismissed(RecyclerView.ViewHolder item) {
|
||||
if (!(item instanceof SwipeableItemViewHolder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final SwipeableItemViewHolder item2 = (SwipeableItemViewHolder) item;
|
||||
final int result = item2.getSwipeResult();
|
||||
final int reaction = item2.getAfterSwipeReaction();
|
||||
|
||||
return ((result == RecyclerViewSwipeManager.RESULT_SWIPED_LEFT) ||
|
||||
(result == RecyclerViewSwipeManager.RESULT_SWIPED_RIGHT)) &&
|
||||
(reaction == RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_REMOVE_ITEM);
|
||||
}
|
||||
|
||||
private static boolean isSwipeDismissed(RemoveAnimationInfo info) {
|
||||
return (info instanceof SwipeDismissRemoveAnimationInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedSuccessfully(RemoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
|
||||
if (isSwipeDismissed(info)) {
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
} else {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedBeforeStarted(RemoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
|
||||
if (isSwipeDismissed(info)) {
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
} else {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationCancel(RemoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPendingAnimation(RecyclerView.ViewHolder holder) {
|
||||
if (isSwipeDismissed(holder)) {
|
||||
final View itemView = holder.itemView;
|
||||
final int prevItemX = (int) (ViewCompat.getTranslationX(itemView) + 0.5f);
|
||||
final int prevItemY = (int) (ViewCompat.getTranslationY(itemView) + 0.5f);
|
||||
|
||||
endAnimation(holder);
|
||||
|
||||
ViewCompat.setTranslationX(itemView, prevItemX);
|
||||
ViewCompat.setTranslationY(itemView, prevItemY);
|
||||
|
||||
enqueuePendingAnimationInfo(new SwipeDismissRemoveAnimationInfo(holder));
|
||||
|
||||
return true;
|
||||
} else {
|
||||
endAnimation(holder);
|
||||
|
||||
enqueuePendingAnimationInfo(new RemoveAnimationInfo(holder));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class SwipeDismissRemoveAnimationInfo extends RemoveAnimationInfo {
|
||||
public SwipeDismissRemoveAnimationInfo(RecyclerView.ViewHolder holder) {
|
||||
super(holder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Item Animation manager for CHANGE operation (Same behavior as DefaultItemAnimator class)
|
||||
*/
|
||||
private static class SwipeDismissItemChangeAnimationManager extends ItemChangeAnimationManager {
|
||||
public SwipeDismissItemChangeAnimationManager(BaseItemAnimator itemAnimator) {
|
||||
super(itemAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateChangeAnimationForOldItem(ChangeAnimationInfo info) {
|
||||
final ViewPropertyAnimatorCompat animator = ViewCompat.animate(info.oldHolder.itemView);
|
||||
|
||||
animator.setDuration(getDuration());
|
||||
animator.translationX(info.toX - info.fromX);
|
||||
animator.translationY(info.toY - info.fromY);
|
||||
animator.alpha(0);
|
||||
|
||||
startActiveItemAnimation(info, info.oldHolder, animator);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreateChangeAnimationForNewItem(ChangeAnimationInfo info) {
|
||||
final ViewPropertyAnimatorCompat animator = ViewCompat.animate(info.newHolder.itemView);
|
||||
|
||||
animator.translationX(0);
|
||||
animator.translationY(0);
|
||||
animator.setDuration(getDuration());
|
||||
animator.alpha(1);
|
||||
|
||||
startActiveItemAnimation(info, info.newHolder, animator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedSuccessfully(ChangeAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedBeforeStarted(ChangeAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationCancel(ChangeAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPendingAnimation(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);
|
||||
|
||||
final int deltaX = (int) (toX - fromX - prevTranslationX);
|
||||
final 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);
|
||||
}
|
||||
|
||||
enqueuePendingAnimationInfo(new ChangeAnimationInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Item Animation manager for MOVE operation (Same behavior as DefaultItemAnimator class)
|
||||
*/
|
||||
private static class SwipeDismissItemMoveAnimationManager extends ItemMoveAnimationManager {
|
||||
|
||||
public SwipeDismissItemMoveAnimationManager(BaseItemAnimator itemAnimator) {
|
||||
super(itemAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateAnimation(MoveAnimationInfo info) {
|
||||
final View view = info.holder.itemView;
|
||||
final int deltaX = info.toX - info.fromX;
|
||||
final int deltaY = info.toY - info.fromY;
|
||||
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.animate(view).translationX(0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.animate(view).translationY(0);
|
||||
}
|
||||
|
||||
final ViewPropertyAnimatorCompat animator = ViewCompat.animate(view);
|
||||
|
||||
animator.setDuration(getDuration());
|
||||
animator.setInterpolator(MOVE_INTERPOLATOR);
|
||||
|
||||
startActiveItemAnimation(info, info.holder, animator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedSuccessfully(MoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEndedBeforeStarted(MoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationCancel(MoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
final int deltaX = info.toX - info.fromX;
|
||||
final int deltaY = info.toY - info.fromY;
|
||||
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.animate(view).translationX(0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.animate(view).translationY(0);
|
||||
}
|
||||
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPendingAnimation(RecyclerView.ViewHolder item, int fromX, int fromY, int toX, int toY) {
|
||||
final View view = item.itemView;
|
||||
|
||||
fromX += ViewCompat.getTranslationX(item.itemView);
|
||||
fromY += ViewCompat.getTranslationY(item.itemView);
|
||||
|
||||
endAnimation(item);
|
||||
|
||||
final int deltaX = toX - fromX;
|
||||
final int deltaY = toY - fromY;
|
||||
|
||||
final MoveAnimationInfo info = new MoveAnimationInfo(item, fromX, fromY, toX, toY);
|
||||
|
||||
if (deltaX == 0 && deltaY == 0) {
|
||||
dispatchFinished(info, info.holder);
|
||||
info.clear(info.holder);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.setTranslationX(view, -deltaX);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.setTranslationY(view, -deltaY);
|
||||
}
|
||||
|
||||
enqueuePendingAnimationInfo(info);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.animator.impl;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
public class AddAnimationInfo extends ItemAnimationInfo {
|
||||
public RecyclerView.ViewHolder holder;
|
||||
|
||||
public AddAnimationInfo(RecyclerView.ViewHolder holder) {
|
||||
this.holder = holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder getAvailableViewHolder() {
|
||||
return holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(RecyclerView.ViewHolder item) {
|
||||
if (holder == null) {
|
||||
holder = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AddAnimationInfo{" +
|
||||
"holder=" + holder +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.animator.impl;
|
||||
|
||||
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 com.h6ah4i.android.widget.advrecyclerview.animator.BaseItemAnimator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class BaseItemAnimationManager<T extends ItemAnimationInfo> {
|
||||
protected BaseItemAnimator mItemAnimator;
|
||||
protected List<T> mPending;
|
||||
protected List<List<T>> mDeferredReadySets;
|
||||
protected List<RecyclerView.ViewHolder> mActive;
|
||||
|
||||
public BaseItemAnimationManager(BaseItemAnimator itemAnimator) {
|
||||
mItemAnimator = itemAnimator;
|
||||
mPending = new ArrayList<>();
|
||||
mActive = new ArrayList<>();
|
||||
mDeferredReadySets = new ArrayList<>();
|
||||
}
|
||||
|
||||
protected final boolean debugLogEnabled() {
|
||||
return mItemAnimator.debugLogEnabled();
|
||||
}
|
||||
|
||||
public boolean hasPending() {
|
||||
return !mPending.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return !mPending.isEmpty() || !mActive.isEmpty() || !mDeferredReadySets.isEmpty();
|
||||
}
|
||||
|
||||
public boolean removeFromActive(RecyclerView.ViewHolder item) {
|
||||
return mActive.remove(item);
|
||||
}
|
||||
|
||||
public void cancelAllStartedAnimations() {
|
||||
final List<RecyclerView.ViewHolder> active = mActive;
|
||||
for (int i = active.size() - 1; i >= 0; i--) {
|
||||
final View view = active.get(i).itemView;
|
||||
ViewCompat.animate(view).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public void runPendingAnimations(boolean deferred, long deferredDelay) {
|
||||
final List<T> ready = new ArrayList<>();
|
||||
|
||||
ready.addAll(mPending);
|
||||
mPending.clear();
|
||||
|
||||
if (deferred) {
|
||||
mDeferredReadySets.add(ready);
|
||||
|
||||
final Runnable process = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (T info : ready) {
|
||||
createAnimation(info);
|
||||
}
|
||||
ready.clear();
|
||||
mDeferredReadySets.remove(ready);
|
||||
}
|
||||
};
|
||||
|
||||
final View view = ready.get(0).getAvailableViewHolder().itemView;
|
||||
ViewCompat.postOnAnimationDelayed(view, process, deferredDelay);
|
||||
} else {
|
||||
for (T info : ready) {
|
||||
createAnimation(info);
|
||||
}
|
||||
ready.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void dispatchStarting(T info, RecyclerView.ViewHolder item);
|
||||
|
||||
public abstract void dispatchFinished(T info, RecyclerView.ViewHolder item);
|
||||
|
||||
public abstract long getDuration();
|
||||
|
||||
public abstract void setDuration(long duration);
|
||||
|
||||
public void endPendingAnimations(RecyclerView.ViewHolder item) {
|
||||
final List<T> pending = mPending;
|
||||
|
||||
for (int i = pending.size() - 1; i >= 0; i--) {
|
||||
final T info = pending.get(i);
|
||||
|
||||
if (endNotStartedAnimation(info, item) && (item != null)) {
|
||||
pending.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
pending.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void endAllPendingAnimations() {
|
||||
endPendingAnimations(null);
|
||||
}
|
||||
|
||||
public void endDeferredReadyAnimations(RecyclerView.ViewHolder item) {
|
||||
for (int i = mDeferredReadySets.size() - 1; i >= 0; i--) {
|
||||
final List<T> ready = mDeferredReadySets.get(i);
|
||||
|
||||
for (int j = ready.size() - 1; j >= 0; j--) {
|
||||
final T info = ready.get(j);
|
||||
|
||||
if (endNotStartedAnimation(info, item) && (item != null)) {
|
||||
ready.remove(j);
|
||||
}
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
ready.clear();
|
||||
}
|
||||
|
||||
if (ready.isEmpty()) {
|
||||
mDeferredReadySets.remove(ready);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void endAllDeferredReadyAnimations() {
|
||||
endDeferredReadyAnimations(null);
|
||||
}
|
||||
|
||||
/*package*/ void createAnimation(T info) {
|
||||
onCreateAnimation(info);
|
||||
}
|
||||
|
||||
protected void endAnimation(RecyclerView.ViewHolder holder) {
|
||||
mItemAnimator.endAnimation(holder);
|
||||
}
|
||||
|
||||
protected void dispatchFinishedWhenDone() {
|
||||
mItemAnimator.dispatchFinishedWhenDone();
|
||||
}
|
||||
|
||||
protected void enqueuePendingAnimationInfo(T info) {
|
||||
if (info == null) {
|
||||
throw new IllegalStateException("info is null");
|
||||
}
|
||||
mPending.add(info);
|
||||
}
|
||||
|
||||
protected void startActiveItemAnimation(T info, RecyclerView.ViewHolder holder, ViewPropertyAnimatorCompat animator) {
|
||||
animator.setListener(new BaseAnimatorListener(this, info, holder, animator));
|
||||
addActiveAnimationTarget(holder);
|
||||
animator.start();
|
||||
}
|
||||
|
||||
private void addActiveAnimationTarget(RecyclerView.ViewHolder item) {
|
||||
if (item == null) {
|
||||
throw new IllegalStateException("item is null");
|
||||
}
|
||||
mActive.add(item);
|
||||
}
|
||||
|
||||
protected abstract void onCreateAnimation(final T info);
|
||||
|
||||
protected abstract void onAnimationEndedSuccessfully(T info, RecyclerView.ViewHolder item);
|
||||
|
||||
protected abstract void onAnimationEndedBeforeStarted(T info, RecyclerView.ViewHolder item);
|
||||
|
||||
protected abstract void onAnimationCancel(T info, RecyclerView.ViewHolder item);
|
||||
|
||||
protected abstract boolean endNotStartedAnimation(T info, RecyclerView.ViewHolder item);
|
||||
|
||||
|
||||
protected static class BaseAnimatorListener implements ViewPropertyAnimatorListener {
|
||||
private BaseItemAnimationManager mManager;
|
||||
private ItemAnimationInfo mAnimationInfo;
|
||||
private RecyclerView.ViewHolder mHolder;
|
||||
private ViewPropertyAnimatorCompat mAnimator;
|
||||
|
||||
public BaseAnimatorListener(BaseItemAnimationManager manager, ItemAnimationInfo info, RecyclerView.ViewHolder holder, ViewPropertyAnimatorCompat animator) {
|
||||
mManager = manager;
|
||||
mAnimationInfo = info;
|
||||
mHolder = holder;
|
||||
mAnimator = animator;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
mManager.dispatchStarting(mAnimationInfo, mHolder);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
final BaseItemAnimationManager manager = mManager;
|
||||
final ItemAnimationInfo info = mAnimationInfo;
|
||||
final RecyclerView.ViewHolder holder = mHolder;
|
||||
|
||||
mAnimator.setListener(null);
|
||||
mManager = null;
|
||||
mAnimationInfo = null;
|
||||
mHolder = null;
|
||||
mAnimator = null;
|
||||
|
||||
manager.onAnimationEndedSuccessfully(info, holder);
|
||||
manager.dispatchFinished(info, holder);
|
||||
info.clear(holder);
|
||||
manager.mActive.remove(holder);
|
||||
manager.dispatchFinishedWhenDone();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
mManager.onAnimationCancel(mAnimationInfo, mHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.animator.impl;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
public class ChangeAnimationInfo extends ItemAnimationInfo {
|
||||
public RecyclerView.ViewHolder newHolder, oldHolder;
|
||||
public int fromX, fromY, toX, toY;
|
||||
|
||||
public ChangeAnimationInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
|
||||
int fromX, int fromY, int toX, int toY) {
|
||||
this.oldHolder = oldHolder;
|
||||
this.newHolder = newHolder;
|
||||
this.fromX = fromX;
|
||||
this.fromY = fromY;
|
||||
this.toX = toX;
|
||||
this.toY = toY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder getAvailableViewHolder() {
|
||||
return (oldHolder != null) ? oldHolder : newHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(RecyclerView.ViewHolder item) {
|
||||
if (oldHolder == item) {
|
||||
oldHolder = null;
|
||||
}
|
||||
if (newHolder == item) {
|
||||
newHolder = null;
|
||||
}
|
||||
if (oldHolder == null && newHolder == null) {
|
||||
fromX = 0;
|
||||
fromY = 0;
|
||||
toX = 0;
|
||||
toY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChangeInfo{" +
|
||||
", oldHolder=" + oldHolder +
|
||||
", newHolder=" + newHolder +
|
||||
", fromX=" + fromX +
|
||||
", fromY=" + fromY +
|
||||
", toX=" + toX +
|
||||
", toY=" + toY +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.animator.impl;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.BaseItemAnimator;
|
||||
|
||||
public abstract class ItemAddAnimationManager extends BaseItemAnimationManager<AddAnimationInfo> {
|
||||
private static final String TAG = "ARVItemAddAnimMgr";
|
||||
|
||||
public ItemAddAnimationManager(BaseItemAnimator itemAnimator) {
|
||||
super(itemAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
return mItemAnimator.getAddDuration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDuration(long duration) {
|
||||
mItemAnimator.setAddDuration(duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchStarting(AddAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
if (debugLogEnabled()) {
|
||||
Log.d(TAG, "dispatchAddStarting(" + item + ")");
|
||||
}
|
||||
mItemAnimator.dispatchAddStarting(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchFinished(AddAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
if (debugLogEnabled()) {
|
||||
Log.d(TAG, "dispatchAddFinished(" + item + ")");
|
||||
}
|
||||
mItemAnimator.dispatchAddFinished(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean endNotStartedAnimation(AddAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
if ((info.holder != null) && ((item == null) || (info.holder == item))) {
|
||||
onAnimationEndedBeforeStarted(info, info.holder);
|
||||
dispatchFinished(info, info.holder);
|
||||
info.clear(info.holder);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract boolean addPendingAnimation(RecyclerView.ViewHolder item);
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.animator.impl;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
public abstract class ItemAnimationInfo {
|
||||
public abstract RecyclerView.ViewHolder getAvailableViewHolder();
|
||||
|
||||
public abstract void clear(RecyclerView.ViewHolder holder);
|
||||
}
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.animator.impl;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.BaseItemAnimator;
|
||||
|
||||
public abstract class ItemChangeAnimationManager extends BaseItemAnimationManager<ChangeAnimationInfo> {
|
||||
private static final String TAG = "ARVItemChangeAnimMgr";
|
||||
|
||||
public ItemChangeAnimationManager(BaseItemAnimator itemAnimator) {
|
||||
super(itemAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchStarting(ChangeAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
if (debugLogEnabled()) {
|
||||
Log.d(TAG, "dispatchChangeStarting(" + item + ")");
|
||||
}
|
||||
mItemAnimator.dispatchChangeStarting(item, (item == info.oldHolder));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchFinished(ChangeAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
if (debugLogEnabled()) {
|
||||
Log.d(TAG, "dispatchChangeFinished(" + item + ")");
|
||||
}
|
||||
mItemAnimator.dispatchChangeFinished(item, (item == info.oldHolder));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
return mItemAnimator.getChangeDuration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDuration(long duration) {
|
||||
mItemAnimator.setChangeDuration(duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateAnimation(ChangeAnimationInfo info) {
|
||||
if (info.oldHolder != null && info.oldHolder.itemView != null) {
|
||||
onCreateChangeAnimationForOldItem(info);
|
||||
}
|
||||
|
||||
if (info.newHolder != null && info.newHolder.itemView != null) {
|
||||
onCreateChangeAnimationForNewItem(info);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean endNotStartedAnimation(ChangeAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
if ((info.oldHolder != null) && ((item == null) || (info.oldHolder == item))) {
|
||||
onAnimationEndedBeforeStarted(info, info.oldHolder);
|
||||
dispatchFinished(info, info.oldHolder);
|
||||
info.clear(info.oldHolder);
|
||||
}
|
||||
|
||||
if ((info.newHolder != null) && ((item == null) || (info.newHolder == item))) {
|
||||
onAnimationEndedBeforeStarted(info, info.newHolder);
|
||||
dispatchFinished(info, info.newHolder);
|
||||
info.clear(info.newHolder);
|
||||
}
|
||||
|
||||
return (info.oldHolder == null && info.newHolder == null);
|
||||
}
|
||||
|
||||
protected abstract void onCreateChangeAnimationForNewItem(ChangeAnimationInfo info);
|
||||
|
||||
protected abstract void onCreateChangeAnimationForOldItem(ChangeAnimationInfo info);
|
||||
|
||||
public abstract boolean addPendingAnimation(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY);
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.animator.impl;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.BaseItemAnimator;
|
||||
|
||||
public abstract class ItemMoveAnimationManager extends BaseItemAnimationManager<MoveAnimationInfo> {
|
||||
public static final String TAG = "ARVItemMoveAnimMgr";
|
||||
|
||||
public ItemMoveAnimationManager(BaseItemAnimator itemAnimator) {
|
||||
super(itemAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
return mItemAnimator.getMoveDuration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDuration(long duration) {
|
||||
mItemAnimator.setMoveDuration(duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchStarting(MoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
if (debugLogEnabled()) {
|
||||
Log.d(TAG, "dispatchMoveStarting(" + item + ")");
|
||||
}
|
||||
mItemAnimator.dispatchMoveStarting(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchFinished(MoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
if (debugLogEnabled()) {
|
||||
Log.d(TAG, "dispatchMoveFinished(" + item + ")");
|
||||
}
|
||||
mItemAnimator.dispatchMoveFinished(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean endNotStartedAnimation(MoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
if ((info.holder != null) && ((item == null) || (info.holder == item))) {
|
||||
onAnimationEndedBeforeStarted(info, info.holder);
|
||||
dispatchFinished(info, info.holder);
|
||||
info.clear(info.holder);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract boolean addPendingAnimation(RecyclerView.ViewHolder item, int fromX, int fromY, int toX, int toY);
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.animator.impl;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.BaseItemAnimator;
|
||||
|
||||
public abstract class ItemRemoveAnimationManager extends BaseItemAnimationManager<RemoveAnimationInfo> {
|
||||
private static final String TAG = "ARVItemRemoveAnimMgr";
|
||||
|
||||
public ItemRemoveAnimationManager(BaseItemAnimator itemAnimator) {
|
||||
super(itemAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
return mItemAnimator.getRemoveDuration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDuration(long duration) {
|
||||
mItemAnimator.setRemoveDuration(duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchStarting(RemoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
if (debugLogEnabled()) {
|
||||
Log.d(TAG, "dispatchRemoveStarting(" + item + ")");
|
||||
}
|
||||
mItemAnimator.dispatchRemoveStarting(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchFinished(RemoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
if (debugLogEnabled()) {
|
||||
Log.d(TAG, "dispatchRemoveFinished(" + item + ")");
|
||||
}
|
||||
mItemAnimator.dispatchRemoveFinished(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean endNotStartedAnimation(RemoveAnimationInfo info, RecyclerView.ViewHolder item) {
|
||||
if ((info.holder != null) && ((item == null) || (info.holder == item))) {
|
||||
onAnimationEndedBeforeStarted(info, info.holder);
|
||||
dispatchFinished(info, info.holder);
|
||||
info.clear(info.holder);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract boolean addPendingAnimation(RecyclerView.ViewHolder holder);
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.animator.impl;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
public class MoveAnimationInfo extends ItemAnimationInfo {
|
||||
public RecyclerView.ViewHolder holder;
|
||||
public int fromX, fromY, toX, toY;
|
||||
|
||||
public MoveAnimationInfo(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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder getAvailableViewHolder() {
|
||||
return holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(RecyclerView.ViewHolder item) {
|
||||
if (holder == item) {
|
||||
holder = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MoveAnimationInfo{" +
|
||||
"holder=" + holder +
|
||||
", fromX=" + fromX +
|
||||
", fromY=" + fromY +
|
||||
", toX=" + toX +
|
||||
", toY=" + toY +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.animator.impl;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
public class RemoveAnimationInfo extends ItemAnimationInfo {
|
||||
public RecyclerView.ViewHolder holder;
|
||||
|
||||
public RemoveAnimationInfo(RecyclerView.ViewHolder holder) {
|
||||
this.holder = holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder getAvailableViewHolder() {
|
||||
return holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(RecyclerView.ViewHolder item) {
|
||||
if (holder == item) {
|
||||
holder = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RemoveAnimationInfo{" +
|
||||
"holder=" + holder +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.decoration;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.NinePatchDrawable;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Item decoration which draws drop shadow of each item views.
|
||||
*/
|
||||
public class ItemShadowDecorator extends RecyclerView.ItemDecoration {
|
||||
private final NinePatchDrawable mShadowDrawable;
|
||||
private final Rect mShadowPadding = new Rect();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param shadow 9-patch drawable used for drop shadow
|
||||
*/
|
||||
public ItemShadowDecorator(NinePatchDrawable shadow) {
|
||||
mShadowDrawable = shadow;
|
||||
mShadowDrawable.getPadding(mShadowPadding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
final int childCount = parent.getChildCount();
|
||||
|
||||
if (childCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int savedCount = c.save(Canvas.CLIP_SAVE_FLAG);
|
||||
|
||||
c.clipRect(
|
||||
parent.getLeft() + Math.max(0, parent.getPaddingLeft() - mShadowPadding.left),
|
||||
parent.getTop()/* + Math.max(0, parent.getPaddingTop() - mShadowPadding.top)*/,
|
||||
parent.getRight() - Math.max(0, parent.getPaddingRight() - mShadowPadding.right),
|
||||
parent.getBottom()/* - Math.max(0, parent.getPaddingBottom() - mShadowPadding.bottom)*/);
|
||||
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final View child = parent.getChildAt(i);
|
||||
|
||||
if (!shouldDrawDropShadow(child)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final int tx = (int) (ViewCompat.getTranslationX(child) + 0.5f);
|
||||
final int ty = (int) (ViewCompat.getTranslationY(child) + 0.5f);
|
||||
|
||||
final int left = child.getLeft() - mShadowPadding.left;
|
||||
final int right = child.getRight() + mShadowPadding.right;
|
||||
final int top = child.getTop() - mShadowPadding.top;
|
||||
final int bottom = child.getBottom() + mShadowPadding.bottom;
|
||||
|
||||
mShadowDrawable.setBounds(left + tx, top + ty, right + tx, bottom + ty);
|
||||
mShadowDrawable.draw(c);
|
||||
}
|
||||
|
||||
c.restoreToCount(savedCount);
|
||||
}
|
||||
|
||||
private static boolean shouldDrawDropShadow(View child) {
|
||||
if (child.getVisibility() != View.VISIBLE) {
|
||||
return false;
|
||||
}
|
||||
if (ViewCompat.getAlpha(child) != 1.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Drawable background = child.getBackground();
|
||||
if (background == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (background instanceof ColorDrawable) {
|
||||
//noinspection RedundantCast
|
||||
if (((ColorDrawable) background).getAlpha() == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||
outRect.set(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.decoration;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Item decoration which draws item divider between each items.
|
||||
*/
|
||||
public class SimpleListDividerDecorator extends RecyclerView.ItemDecoration {
|
||||
private final Drawable mDividerDrawable;
|
||||
private final int mDividerHeight;
|
||||
private boolean mOverlap;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param divider divider drawable
|
||||
* @param overlap whether the divider is drawn overlapped on bottom of the item.
|
||||
*/
|
||||
public SimpleListDividerDecorator(Drawable divider, boolean overlap) {
|
||||
mDividerDrawable = divider;
|
||||
mDividerHeight = mDividerDrawable.getIntrinsicHeight();
|
||||
mOverlap = overlap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
final int childCount = parent.getChildCount();
|
||||
final float yPositionThreshold = (mOverlap) ? 1.0f : (mDividerHeight + 1.0f); // [px]
|
||||
final float zPositionThreshold = 1.0f; // [px]
|
||||
|
||||
if (childCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int savedCount = c.save(Canvas.CLIP_SAVE_FLAG);
|
||||
|
||||
c.clipRect(
|
||||
parent.getLeft() + parent.getPaddingLeft(),
|
||||
parent.getTop() + parent.getPaddingTop(),
|
||||
parent.getRight() - parent.getPaddingRight(),
|
||||
parent.getBottom() + parent.getPaddingBottom());
|
||||
|
||||
for (int i = 0; i < childCount - 1; i++) {
|
||||
final View child = parent.getChildAt(i);
|
||||
final View nextChild = parent.getChildAt(i + 1);
|
||||
|
||||
if ((child.getVisibility() != View.VISIBLE) ||
|
||||
(nextChild.getVisibility() != View.VISIBLE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if the next item is placed at the bottom
|
||||
final float childBottom = child.getBottom() + ViewCompat.getTranslationY(child);
|
||||
final float nextChildTop = nextChild.getTop() + ViewCompat.getTranslationY(nextChild);
|
||||
|
||||
if (!(Math.abs(nextChildTop - childBottom) < yPositionThreshold)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if the next item is placed on the same plane
|
||||
final float childZ = ViewCompat.getTranslationZ(child) + ViewCompat.getElevation(child);
|
||||
final float nextChildZ = ViewCompat.getTranslationZ(nextChild) + ViewCompat.getElevation(nextChild);
|
||||
|
||||
if (!(Math.abs(nextChildZ - childZ) < zPositionThreshold)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final float childAlpha = ViewCompat.getAlpha(child);
|
||||
final float nextChildAlpha = ViewCompat.getAlpha(nextChild);
|
||||
|
||||
final int tx = (int) (ViewCompat.getTranslationX(child) + 0.5f);
|
||||
final int ty = (int) (ViewCompat.getTranslationY(child) + 0.5f);
|
||||
final int left = child.getLeft();
|
||||
final int right = child.getRight();
|
||||
final int top = child.getBottom();
|
||||
final int bottom = top + mDividerHeight;
|
||||
|
||||
mDividerDrawable.setAlpha((int) ((0.5f * 255) * (childAlpha + nextChildAlpha) + 0.5f));
|
||||
mDividerDrawable.setBounds(left + tx, top + ty, right + tx, bottom + ty);
|
||||
mDividerDrawable.draw(c);
|
||||
}
|
||||
|
||||
c.restoreToCount(savedCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||
if (mOverlap) {
|
||||
outRect.set(0, 0, 0, 0);
|
||||
} else {
|
||||
outRect.set(0, 0, 0, mDividerHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.draggable;
|
||||
|
||||
import android.os.Build;
|
||||
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.Interpolator;
|
||||
|
||||
abstract class BaseDraggableItemDecorator extends RecyclerView.ItemDecoration {
|
||||
|
||||
private static final int RETURN_TO_DEFAULT_POS_ANIMATE_THRESHOLD_DP = 2;
|
||||
private static final int RETURN_TO_DEFAULT_POS_ANIMATE_THRESHOLD_MSEC = 20;
|
||||
|
||||
private int mReturnToDefaultPositionDuration = 200;
|
||||
private int mReturnToDefaultPositionAnimateThreshold;
|
||||
private Interpolator mReturnToDefaultPositionInterpolator;
|
||||
|
||||
protected RecyclerView mRecyclerView;
|
||||
protected RecyclerView.ViewHolder mDraggingItem;
|
||||
|
||||
public BaseDraggableItemDecorator(RecyclerView recyclerView, RecyclerView.ViewHolder draggingItem) {
|
||||
mRecyclerView = recyclerView;
|
||||
mDraggingItem = draggingItem;
|
||||
|
||||
final float displayDensity = recyclerView.getResources().getDisplayMetrics().density;
|
||||
mReturnToDefaultPositionAnimateThreshold = (int) (RETURN_TO_DEFAULT_POS_ANIMATE_THRESHOLD_DP * displayDensity + 0.5f);
|
||||
}
|
||||
|
||||
|
||||
public void setReturnToDefaultPositionAnimationDuration(int duration) {
|
||||
mReturnToDefaultPositionDuration = duration;
|
||||
}
|
||||
|
||||
public void setReturnToDefaultPositionAnimationInterpolator(Interpolator interpolator) {
|
||||
mReturnToDefaultPositionInterpolator = interpolator;
|
||||
}
|
||||
|
||||
protected void moveToDefaultPosition(View targetView, boolean animate) {
|
||||
final int curTranslationY = (int) (ViewCompat.getTranslationY(targetView));
|
||||
final int halfItemHeight = targetView.getHeight() / 2;
|
||||
final float translationProportion = (halfItemHeight > 0) ? Math.abs((float) curTranslationY / halfItemHeight) : 0;
|
||||
final float t = 1.0f - Math.min(translationProportion, 1.0f);
|
||||
final int animDuration = (int) (mReturnToDefaultPositionDuration * (1.0f - (t * t)) + 0.5f);
|
||||
|
||||
if (supportsViewPropertyAnimation() && animate &&
|
||||
(animDuration > RETURN_TO_DEFAULT_POS_ANIMATE_THRESHOLD_MSEC) &&
|
||||
(Math.abs(curTranslationY) > mReturnToDefaultPositionAnimateThreshold)) {
|
||||
final ViewPropertyAnimatorCompat animator = ViewCompat.animate(targetView);
|
||||
animator.cancel();
|
||||
animator.setDuration(animDuration);
|
||||
animator.setInterpolator(mReturnToDefaultPositionInterpolator);
|
||||
animator.translationY(0.0f);
|
||||
animator.setListener(new ViewPropertyAnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
animator.setListener(null);
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
|
||||
// invalidate explicitly to refresh other decorations
|
||||
if (view.getParent() instanceof RecyclerView) {
|
||||
ViewCompat.postInvalidateOnAnimation((RecyclerView) view.getParent());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
}
|
||||
});
|
||||
animator.start();
|
||||
} else {
|
||||
ViewCompat.setTranslationY(targetView, 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void setItemTranslationY(RecyclerView rv, RecyclerView.ViewHolder holder, float y) {
|
||||
final RecyclerView.ItemAnimator itemAnimator = rv.getItemAnimator();
|
||||
if (itemAnimator != null) {
|
||||
itemAnimator.endAnimation(holder);
|
||||
}
|
||||
ViewCompat.setTranslationY(holder.itemView, y);
|
||||
}
|
||||
|
||||
private static boolean supportsViewPropertyAnimation() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.draggable;
|
||||
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
public class BasicSwapTargetTranslationInterpolator implements Interpolator {
|
||||
private final float mThreshold;
|
||||
private final float mHalfValidRange;
|
||||
private final float mInvValidRange;
|
||||
|
||||
public BasicSwapTargetTranslationInterpolator() {
|
||||
this(0.3f);
|
||||
}
|
||||
|
||||
public BasicSwapTargetTranslationInterpolator(float threshold) {
|
||||
if (!(threshold >= 0 && threshold < 0.5f)) {
|
||||
throw new IllegalArgumentException("Invalid threshold range: " + threshold);
|
||||
}
|
||||
final float validRange = 1.0f - 2 * threshold;
|
||||
|
||||
mThreshold = threshold;
|
||||
mHalfValidRange = validRange * 0.5f;
|
||||
mInvValidRange = 1.0f / validRange;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getInterpolation(float input) {
|
||||
if (Math.abs(input - 0.5f) < mHalfValidRange) {
|
||||
return (input - mThreshold) * mInvValidRange;
|
||||
} else {
|
||||
return (input < 0.5f) ? 0.0f : 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.draggable;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
public interface DraggableItemAdapter<T extends RecyclerView.ViewHolder> {
|
||||
|
||||
/**
|
||||
* Called when user is attempt to drag the item.
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to item user is attempt to start dragging.
|
||||
* @param position The position of the item within the adapter's data set.
|
||||
* @param x Touched X position. Relative from the itemView's top-left.
|
||||
* @param y Touched Y position. Relative from the itemView's top-left.
|
||||
|
||||
* @return Whether can start dragging.
|
||||
*/
|
||||
boolean onCheckCanStartDrag(T holder, int position, int x, int y);
|
||||
|
||||
/**
|
||||
* Called after the {@link #onCheckCanStartDrag(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int)} method returned true.
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to item user is attempt to start dragging.
|
||||
* @param position The position of the item within the adapter's data set.
|
||||
*
|
||||
* @return null: no constraints (= new ItemDraggableRange(0, getItemCount() - 1)),
|
||||
* otherwise: the range specified item can be drag-sortable.
|
||||
*/
|
||||
ItemDraggableRange onGetItemDraggableRange(T holder, int position);
|
||||
|
||||
/**
|
||||
* Called when item is moved. Should apply the move operation result to data set.
|
||||
*
|
||||
* @param fromPosition Previous position of the item.
|
||||
* @param toPosition New position of the item.
|
||||
*/
|
||||
void onMoveItem(int fromPosition, int toPosition);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.draggable;
|
||||
|
||||
/**
|
||||
* Interface which provides required information for dragging item.
|
||||
*
|
||||
* Implement this interface on your sub-class of the {@link android.support.v7.widget.RecyclerView.ViewHolder}.
|
||||
*/
|
||||
public interface DraggableItemViewHolder {
|
||||
/**
|
||||
* Sets the state flags value for dragging item
|
||||
*
|
||||
* @param flags Bitwise OR of these flags;
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager#STATE_FLAG_DRAGGING}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager#STATE_FLAG_IS_ACTIVE}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager#STATE_FLAG_IS_IN_RANGE}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager#STATE_FLAG_IS_UPDATED}
|
||||
*/
|
||||
void setDragStateFlags(int flags);
|
||||
|
||||
/**
|
||||
* Gets the state flags value for dragging item
|
||||
*
|
||||
* @return Bitwise OR of these flags;
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager#STATE_FLAG_DRAGGING}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager#STATE_FLAG_IS_ACTIVE}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager#STATE_FLAG_IS_IN_RANGE}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager#STATE_FLAG_IS_UPDATED}
|
||||
*/
|
||||
int getDragStateFlags();
|
||||
}
|
||||
+448
@@ -0,0 +1,448 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.draggable;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.BaseWrapperAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
|
||||
|
||||
class DraggableItemWrapperAdapter<VH extends RecyclerView.ViewHolder> extends BaseWrapperAdapter<VH> implements SwipeableItemAdapter<VH> {
|
||||
private static final String TAG = "ARVDraggableWrapper";
|
||||
|
||||
private static final int STATE_FLAG_INITIAL_VALUE = -1;
|
||||
|
||||
private static final boolean LOCAL_LOGV = false;
|
||||
private static final boolean LOCAL_LOGD = false;
|
||||
private static final boolean LOCAL_LOGI = true;
|
||||
private static final boolean DEBUG_BYPASS_MOVE_OPERATION_MODE = false;
|
||||
|
||||
private RecyclerViewDragDropManager mDragDropManager;
|
||||
private DraggableItemAdapter mDraggableItemAdapter;
|
||||
private RecyclerView.ViewHolder mDraggingItem;
|
||||
private ItemDraggableRange mDraggableRange;
|
||||
private long mDraggingItemId = RecyclerView.NO_ID;
|
||||
private int mDraggingItemInitialPosition = RecyclerView.NO_POSITION;
|
||||
private int mDraggingItemCurrentPosition = RecyclerView.NO_POSITION;
|
||||
|
||||
public DraggableItemWrapperAdapter(RecyclerViewDragDropManager manager, RecyclerView.Adapter<VH> adapter) {
|
||||
super(adapter);
|
||||
|
||||
mDraggableItemAdapter = getDraggableItemAdapter(adapter);
|
||||
if (getDraggableItemAdapter(adapter) == null) {
|
||||
throw new IllegalArgumentException("adapter does not implement DraggableItemAdapter");
|
||||
}
|
||||
|
||||
if (manager == null) {
|
||||
throw new IllegalArgumentException("manager cannot be null");
|
||||
}
|
||||
|
||||
mDragDropManager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRelease() {
|
||||
super.onRelease();
|
||||
mDraggingItem = null;
|
||||
mDraggableItemAdapter = null;
|
||||
mDragDropManager = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
final VH holder = super.onCreateViewHolder(parent, viewType);
|
||||
|
||||
if (holder instanceof DraggableItemViewHolder) {
|
||||
((DraggableItemViewHolder) holder).setDragStateFlags(STATE_FLAG_INITIAL_VALUE);
|
||||
}
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(VH holder, int position) {
|
||||
if (isDragging()) {
|
||||
final long itemId = holder.getItemId();
|
||||
|
||||
final int origPosition = convertToOriginalPosition(
|
||||
position, mDraggingItemInitialPosition, mDraggingItemCurrentPosition);
|
||||
|
||||
if (itemId == mDraggingItemId && holder != mDraggingItem) {
|
||||
if (mDraggingItem == null) {
|
||||
if (LOCAL_LOGI) {
|
||||
Log.i(TAG, "a new view holder object for the currently dragging item is assigned");
|
||||
}
|
||||
mDraggingItem = holder;
|
||||
mDragDropManager.onNewDraggingItemViewBinded(holder);
|
||||
} else {
|
||||
Log.e(TAG, "an another view holder object for the currently dragging item is assigned");
|
||||
}
|
||||
}
|
||||
|
||||
int flags = RecyclerViewDragDropManager.STATE_FLAG_DRAGGING;
|
||||
|
||||
if (itemId == mDraggingItemId) {
|
||||
flags |= RecyclerViewDragDropManager.STATE_FLAG_IS_ACTIVE;
|
||||
}
|
||||
if (mDraggableRange.checkInRange(position)) {
|
||||
flags |= RecyclerViewDragDropManager.STATE_FLAG_IS_IN_RANGE;
|
||||
}
|
||||
|
||||
safeUpdateFlags(holder, flags);
|
||||
super.onBindViewHolder(holder, origPosition);
|
||||
} else {
|
||||
safeUpdateFlags(holder, 0);
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (isDragging()) {
|
||||
final int origPosition = convertToOriginalPosition(
|
||||
position, mDraggingItemInitialPosition, mDraggingItemCurrentPosition);
|
||||
return super.getItemId(origPosition);
|
||||
} else {
|
||||
return super.getItemId(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (isDragging()) {
|
||||
final int origPosition = convertToOriginalPosition(
|
||||
position, mDraggingItemInitialPosition, mDraggingItemCurrentPosition);
|
||||
return super.getItemViewType(origPosition);
|
||||
} else {
|
||||
return super.getItemViewType(position);
|
||||
}
|
||||
}
|
||||
|
||||
protected static int convertToOriginalPosition(int position, int dragInitial, int dragCurrent) {
|
||||
if (dragInitial < 0 || dragCurrent < 0) {
|
||||
// not dragging
|
||||
return position;
|
||||
} else {
|
||||
if ((dragInitial == dragCurrent) ||
|
||||
((position < dragInitial) && (position < dragCurrent)) ||
|
||||
(position > dragInitial) && (position > dragCurrent)) {
|
||||
return position;
|
||||
} else if (dragCurrent < dragInitial) {
|
||||
if (position == dragCurrent) {
|
||||
return dragInitial;
|
||||
} else {
|
||||
return position - 1;
|
||||
}
|
||||
} else { // if (dragCurrent > dragInitial)
|
||||
if (position == dragCurrent) {
|
||||
return dragInitial;
|
||||
} else {
|
||||
return position + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterChanged() {
|
||||
if (shouldCancelDragOnDataUpdated()) {
|
||||
cancelDrag();
|
||||
} else {
|
||||
super.onHandleWrappedAdapterChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterItemRangeChanged(int positionStart, int itemCount) {
|
||||
if (shouldCancelDragOnDataUpdated()) {
|
||||
cancelDrag();
|
||||
} else {
|
||||
super.onHandleWrappedAdapterItemRangeChanged(positionStart, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterItemRangeInserted(int positionStart, int itemCount) {
|
||||
if (shouldCancelDragOnDataUpdated()) {
|
||||
cancelDrag();
|
||||
} else {
|
||||
super.onHandleWrappedAdapterItemRangeInserted(positionStart, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterItemRangeRemoved(int positionStart, int itemCount) {
|
||||
if (shouldCancelDragOnDataUpdated()) {
|
||||
cancelDrag();
|
||||
} else {
|
||||
super.onHandleWrappedAdapterItemRangeRemoved(positionStart, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||
if (shouldCancelDragOnDataUpdated()) {
|
||||
cancelDrag();
|
||||
} else {
|
||||
super.onHandleWrappedAdapterRangeMoved(fromPosition, toPosition, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldCancelDragOnDataUpdated() {
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (DEBUG_BYPASS_MOVE_OPERATION_MODE) {
|
||||
return false;
|
||||
}
|
||||
return isDragging();
|
||||
}
|
||||
|
||||
private void cancelDrag() {
|
||||
if (mDragDropManager != null) {
|
||||
mDragDropManager.cancelDrag();
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This method is called from RecyclerViewDragDropManager
|
||||
/*package*/ void onDragItemStarted(RecyclerView.ViewHolder holder, ItemDraggableRange range) {
|
||||
if (LOCAL_LOGD) {
|
||||
Log.d(TAG, "onDragItemStarted(holder = " + holder + ")");
|
||||
}
|
||||
|
||||
if (DEBUG_BYPASS_MOVE_OPERATION_MODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (holder.getItemId() == RecyclerView.NO_ID) {
|
||||
throw new IllegalStateException("dragging target must provides valid ID");
|
||||
}
|
||||
|
||||
mDraggingItemInitialPosition = holder.getAdapterPosition();
|
||||
mDraggingItemCurrentPosition = mDraggingItemInitialPosition;
|
||||
mDraggingItem = holder;
|
||||
mDraggingItemId = holder.getItemId();
|
||||
mDraggableRange = range;
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// NOTE: This method is called from RecyclerViewDragDropManager
|
||||
/*package*/ void onDragItemFinished(RecyclerView.ViewHolder holder, boolean result) {
|
||||
if (LOCAL_LOGD) {
|
||||
Log.d(TAG, "onDragItemFinished(holder = " + holder + ", result = " + result + ")");
|
||||
}
|
||||
|
||||
if (holder != null && holder != mDraggingItem) {
|
||||
throw new IllegalStateException("onDragItemFinished() - may be a bug (mDraggingItem != holder)");
|
||||
}
|
||||
|
||||
if (DEBUG_BYPASS_MOVE_OPERATION_MODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result && (mDraggingItemCurrentPosition != mDraggingItemInitialPosition)) {
|
||||
// apply to wrapped adapter
|
||||
DraggableItemAdapter adapter = WrapperAdapterUtils.findWrappedAdapter(
|
||||
getWrappedAdapter(), DraggableItemAdapter.class);
|
||||
adapter.onMoveItem(mDraggingItemInitialPosition, mDraggingItemCurrentPosition);
|
||||
}
|
||||
|
||||
mDraggingItemInitialPosition = RecyclerView.NO_POSITION;
|
||||
mDraggingItemCurrentPosition = RecyclerView.NO_POSITION;
|
||||
mDraggableRange = null;
|
||||
mDraggingItemId = RecyclerView.NO_ID;
|
||||
mDraggingItem = null;
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRecycled(VH holder) {
|
||||
if (isDragging()) {
|
||||
if (holder == mDraggingItem) {
|
||||
if (LOCAL_LOGI) {
|
||||
Log.i(TAG, "a view holder object which is bound to currently dragging item is recycled");
|
||||
}
|
||||
mDraggingItem = null;
|
||||
mDragDropManager.onDraggingItemViewRecycled();
|
||||
}
|
||||
}
|
||||
|
||||
super.onViewRecycled(holder);
|
||||
}
|
||||
|
||||
// NOTE: This method is called from RecyclerViewDragDropManager
|
||||
/*package*/
|
||||
@SuppressWarnings("unchecked")
|
||||
boolean canStartDrag(RecyclerView.ViewHolder holder, int position, int x, int y) {
|
||||
if (LOCAL_LOGV) {
|
||||
Log.v(TAG, "canStartDrag(holder = " + holder + ", position = " + position + ", x = " + x + ", y = " + y + ")");
|
||||
}
|
||||
return mDraggableItemAdapter.onCheckCanStartDrag(holder, position, x, y);
|
||||
}
|
||||
|
||||
// NOTE: This method is called from RecyclerViewDragDropManager
|
||||
/*package*/
|
||||
@SuppressWarnings("unchecked")
|
||||
ItemDraggableRange getItemDraggableRange(RecyclerView.ViewHolder holder, int position) {
|
||||
if (LOCAL_LOGV) {
|
||||
Log.v(TAG, "getItemDraggableRange(holder = " + holder + ", position = " + position + ")");
|
||||
}
|
||||
return mDraggableItemAdapter.onGetItemDraggableRange(holder, position);
|
||||
}
|
||||
|
||||
// NOTE: This method is called from RecyclerViewDragDropManager
|
||||
/*package*/ void moveItem(int fromPosition, int toPosition) {
|
||||
if (LOCAL_LOGD) {
|
||||
Log.d(TAG, "onMoveItem(fromPosition = " + fromPosition + ", toPosition = " + toPosition + ")");
|
||||
}
|
||||
|
||||
if (DEBUG_BYPASS_MOVE_OPERATION_MODE) {
|
||||
mDraggableItemAdapter.onMoveItem(fromPosition, toPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
final int origFromPosition = convertToOriginalPosition(
|
||||
fromPosition, mDraggingItemInitialPosition, mDraggingItemCurrentPosition);
|
||||
|
||||
if (origFromPosition != mDraggingItemInitialPosition) {
|
||||
throw new IllegalStateException(
|
||||
"onMoveItem() - may be a bug or has duplicate IDs --- " +
|
||||
"mDraggingItemInitialPosition = " + mDraggingItemInitialPosition + ", " +
|
||||
"mDraggingItemCurrentPosition = " + mDraggingItemCurrentPosition + ", " +
|
||||
"origFromPosition = " + origFromPosition + ", " +
|
||||
"fromPosition = " + fromPosition + ", " +
|
||||
"toPosition = " + toPosition);
|
||||
}
|
||||
|
||||
mDraggingItemCurrentPosition = toPosition;
|
||||
|
||||
// NOTE:
|
||||
// Don't move items in wrapped adapter here.
|
||||
|
||||
// notify to observers
|
||||
notifyItemMoved(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
protected boolean isDragging() {
|
||||
return mDragDropManager.isDragging();
|
||||
}
|
||||
|
||||
/*package*/ int getDraggingItemInitialPosition() {
|
||||
return mDraggingItemInitialPosition;
|
||||
}
|
||||
|
||||
/*package*/ int getDraggingItemCurrentPosition() {
|
||||
return mDraggingItemCurrentPosition;
|
||||
}
|
||||
|
||||
private static void safeUpdateFlags(RecyclerView.ViewHolder holder, int flags) {
|
||||
if (!(holder instanceof DraggableItemViewHolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final DraggableItemViewHolder holder2 = (DraggableItemViewHolder) holder;
|
||||
|
||||
final int curFlags = holder2.getDragStateFlags();
|
||||
final int mask = ~RecyclerViewDragDropManager.STATE_FLAG_IS_UPDATED;
|
||||
|
||||
// append UPDATED flag
|
||||
if ((curFlags == STATE_FLAG_INITIAL_VALUE) || (((curFlags ^ flags) & mask) != 0)) {
|
||||
flags |= RecyclerViewDragDropManager.STATE_FLAG_IS_UPDATED;
|
||||
}
|
||||
|
||||
((DraggableItemViewHolder) holder).setDragStateFlags(flags);
|
||||
}
|
||||
|
||||
private static DraggableItemAdapter getDraggableItemAdapter(RecyclerView.Adapter adapter) {
|
||||
return WrapperAdapterUtils.findWrappedAdapter(adapter, DraggableItemAdapter.class);
|
||||
}
|
||||
|
||||
private int getOriginalPosition(int position) {
|
||||
int correctedPosition;
|
||||
|
||||
if (isDragging()) {
|
||||
correctedPosition = convertToOriginalPosition(
|
||||
position, mDraggingItemInitialPosition, mDraggingItemCurrentPosition);
|
||||
} else {
|
||||
correctedPosition = position;
|
||||
}
|
||||
return correctedPosition;
|
||||
}
|
||||
|
||||
//
|
||||
// SwipeableItemAdapter implementations
|
||||
//
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int onGetSwipeReactionType(VH holder, int position, int x, int y) {
|
||||
RecyclerView.Adapter adapter = getWrappedAdapter();
|
||||
if (!(adapter instanceof SwipeableItemAdapter)) {
|
||||
return RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_DEFAULT;
|
||||
}
|
||||
|
||||
int correctedPosition = getOriginalPosition(position);
|
||||
|
||||
SwipeableItemAdapter<VH> swipeableItemAdapter = (SwipeableItemAdapter<VH>) adapter;
|
||||
return swipeableItemAdapter.onGetSwipeReactionType(holder, correctedPosition, x, y);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onSetSwipeBackground(VH holder, int position, int type) {
|
||||
RecyclerView.Adapter adapter = getWrappedAdapter();
|
||||
if (!(adapter instanceof SwipeableItemAdapter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int correctedPosition = getOriginalPosition(position);
|
||||
|
||||
SwipeableItemAdapter<VH> swipeableItemAdapter = (SwipeableItemAdapter<VH>) adapter;
|
||||
swipeableItemAdapter.onSetSwipeBackground(holder, correctedPosition, type);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int onSwipeItem(VH holder, int position, int result) {
|
||||
RecyclerView.Adapter adapter = getWrappedAdapter();
|
||||
if (!(adapter instanceof SwipeableItemAdapter)) {
|
||||
return RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_DEFAULT;
|
||||
}
|
||||
|
||||
int correctedPosition = getOriginalPosition(position);
|
||||
|
||||
SwipeableItemAdapter<VH> swipeableItemAdapter = (SwipeableItemAdapter<VH>) adapter;
|
||||
return swipeableItemAdapter.onSwipeItem(holder, correctedPosition, result);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onPerformAfterSwipeReaction(VH holder, int position, int result, int reaction) {
|
||||
RecyclerView.Adapter adapter = getWrappedAdapter();
|
||||
if (!(adapter instanceof SwipeableItemAdapter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int correctedPosition = getOriginalPosition(position);
|
||||
|
||||
SwipeableItemAdapter<VH> swipeableItemAdapter = (SwipeableItemAdapter<VH>) adapter;
|
||||
swipeableItemAdapter.onPerformAfterSwipeReaction(holder, correctedPosition, result, reaction);
|
||||
}
|
||||
}
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.draggable;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.NinePatchDrawable;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.CustomRecyclerViewUtils;
|
||||
|
||||
class DraggingItemDecorator extends BaseDraggableItemDecorator {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "DraggingItemDecorator";
|
||||
|
||||
private int mGrabbedPositionY;
|
||||
private int mTranslationY;
|
||||
private int mRecyclerViewPaddingLeft;
|
||||
private Bitmap mDraggingItemImage;
|
||||
private int mTranslationTopLimit;
|
||||
private int mTranslationBottomLimit;
|
||||
private int mGrabbedItemHeight;
|
||||
private int mTouchPositionY;
|
||||
private NinePatchDrawable mShadowDrawable;
|
||||
private Rect mShadowPadding = new Rect();
|
||||
private Rect mDraggingItemMargins = new Rect();
|
||||
private boolean mStarted;
|
||||
private boolean mIsScrolling;
|
||||
private ItemDraggableRange mRange;
|
||||
|
||||
public DraggingItemDecorator(RecyclerView recyclerView, RecyclerView.ViewHolder draggingItem, ItemDraggableRange range) {
|
||||
super(recyclerView, draggingItem);
|
||||
mRange = range;
|
||||
CustomRecyclerViewUtils.getLayoutMargins(mDraggingItem.itemView, mDraggingItemMargins);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
// NOTE:
|
||||
// On lollipop or later, View has Z-axis property and no needed to draw the dragging view manually.
|
||||
// However, if the RecyclerView has any other decorations or RecyclerView is in scrolling state,
|
||||
// need to draw it to avoid visual corruptions.
|
||||
if (mDraggingItemImage != null) {
|
||||
final float left = mRecyclerViewPaddingLeft + mDraggingItemMargins.left - mShadowPadding.left;
|
||||
final float top = /*mDraggingItemMargins.top +*/ mTranslationY - mShadowPadding.top;
|
||||
c.drawBitmap(mDraggingItemImage, left, top, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void start(MotionEvent e, float grabbedPositionY) {
|
||||
if (mStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final View itemView = mDraggingItem.itemView;
|
||||
|
||||
mGrabbedPositionY = (int) (grabbedPositionY + 0.5f);
|
||||
|
||||
// draw the grabbed item on bitmap
|
||||
mDraggingItemImage = createDraggingItemImage(itemView, mShadowDrawable);
|
||||
|
||||
mGrabbedItemHeight = itemView.getHeight();
|
||||
mTranslationTopLimit = mRecyclerView.getPaddingTop();
|
||||
mRecyclerViewPaddingLeft = mRecyclerView.getPaddingLeft();
|
||||
|
||||
// hide
|
||||
itemView.setVisibility(View.INVISIBLE);
|
||||
|
||||
update(e);
|
||||
|
||||
mRecyclerView.addItemDecoration(this);
|
||||
|
||||
mStarted = true;
|
||||
}
|
||||
|
||||
public void finish(boolean animate) {
|
||||
if (mStarted) {
|
||||
mRecyclerView.removeItemDecoration(this);
|
||||
}
|
||||
|
||||
final RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
|
||||
if (itemAnimator != null) {
|
||||
itemAnimator.endAnimations();
|
||||
}
|
||||
mRecyclerView.stopScroll();
|
||||
|
||||
// return to default position
|
||||
updateDraggingItemPosition(mTranslationY);
|
||||
if (mDraggingItem != null) {
|
||||
moveToDefaultPosition(mDraggingItem.itemView, animate);
|
||||
}
|
||||
|
||||
// show
|
||||
if (mDraggingItem != null) {
|
||||
mDraggingItem.itemView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
mDraggingItem = null;
|
||||
|
||||
if (mDraggingItemImage != null) {
|
||||
mDraggingItemImage.recycle();
|
||||
mDraggingItemImage = null;
|
||||
}
|
||||
|
||||
mRange = null;
|
||||
mGrabbedPositionY = 0;
|
||||
mTranslationY = 0;
|
||||
mTranslationTopLimit = 0;
|
||||
mTranslationBottomLimit = 0;
|
||||
mRecyclerViewPaddingLeft = 0;
|
||||
mGrabbedItemHeight = 0;
|
||||
mTouchPositionY = 0;
|
||||
mStarted = false;
|
||||
}
|
||||
|
||||
public void update(MotionEvent e) {
|
||||
mTouchPositionY = (int) (e.getY() + 0.5f);
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
updateTranslationOffset();
|
||||
updateDraggingItemPosition(mTranslationY);
|
||||
|
||||
ViewCompat.postInvalidateOnAnimation(mRecyclerView);
|
||||
}
|
||||
|
||||
public void setShadowDrawable(NinePatchDrawable shadowDrawable) {
|
||||
mShadowDrawable = shadowDrawable;
|
||||
|
||||
if (mShadowDrawable != null) {
|
||||
mShadowDrawable.getPadding(mShadowPadding);
|
||||
}
|
||||
}
|
||||
|
||||
public int getDraggingItemTranslationY() {
|
||||
return mTranslationY;
|
||||
}
|
||||
|
||||
private void updateTranslationOffset() {
|
||||
final int childCount = mRecyclerView.getChildCount();
|
||||
if (childCount > 0) {
|
||||
mTranslationTopLimit = mRecyclerView.getPaddingTop();
|
||||
mTranslationBottomLimit = Math.max(0, (mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom() - mGrabbedItemHeight));
|
||||
|
||||
if (!mIsScrolling) {
|
||||
final int firstVisiblePosition = CustomRecyclerViewUtils.findFirstVisibleItemPosition(mRecyclerView);
|
||||
final int lastVisiblePosition = CustomRecyclerViewUtils.findLastVisibleItemPosition(mRecyclerView);
|
||||
final View topChild = findRangeFirstItem(mRecyclerView, mRange, firstVisiblePosition, lastVisiblePosition);
|
||||
final View bottomChild = findRangeLastItem(mRecyclerView, mRange, firstVisiblePosition, lastVisiblePosition);
|
||||
|
||||
if (topChild != null) {
|
||||
mTranslationTopLimit = Math.min(mTranslationBottomLimit, topChild.getTop());
|
||||
}
|
||||
|
||||
if (bottomChild != null) {
|
||||
mTranslationBottomLimit = Math.min(mTranslationBottomLimit, bottomChild.getTop());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mTranslationBottomLimit = mTranslationTopLimit = mRecyclerView.getPaddingTop();
|
||||
}
|
||||
|
||||
mTranslationY = mTouchPositionY - mGrabbedPositionY;
|
||||
mTranslationY = Math.min(Math.max(mTranslationY, mTranslationTopLimit), mTranslationBottomLimit);
|
||||
}
|
||||
|
||||
public boolean isReachedToTopLimit() {
|
||||
return (mTranslationY == mTranslationTopLimit);
|
||||
}
|
||||
|
||||
public boolean isReachedToBottomLimit() {
|
||||
return (mTranslationY == mTranslationBottomLimit);
|
||||
}
|
||||
|
||||
private Bitmap createDraggingItemImage(View v, NinePatchDrawable shadow) {
|
||||
int width = v.getWidth() + mShadowPadding.left + mShadowPadding.right;
|
||||
int height = v.getHeight() + mShadowPadding.top + mShadowPadding.bottom;
|
||||
|
||||
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
|
||||
final Canvas canvas = new Canvas(bitmap);
|
||||
|
||||
if (shadow != null) {
|
||||
shadow.setBounds(0, 0, width, height);
|
||||
shadow.draw(canvas);
|
||||
}
|
||||
|
||||
final int savedCount = canvas.save(Canvas.CLIP_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG);
|
||||
// NOTE: Explicitly set clipping rect. This is required on Gingerbread.
|
||||
canvas.clipRect(mShadowPadding.left, mShadowPadding.top, width - mShadowPadding.right, height - mShadowPadding.bottom);
|
||||
canvas.translate(mShadowPadding.left, mShadowPadding.top);
|
||||
v.draw(canvas);
|
||||
canvas.restoreToCount(savedCount);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private void updateDraggingItemPosition(int translationY) {
|
||||
// NOTE: Need to update the view position to make other decorations work properly while dragging
|
||||
if (mDraggingItem != null) {
|
||||
setItemTranslationY(mRecyclerView, mDraggingItem, translationY - mDraggingItem.itemView.getTop());
|
||||
}
|
||||
}
|
||||
|
||||
public void setIsScrolling(boolean isScrolling) {
|
||||
if (mIsScrolling == isScrolling) {
|
||||
return;
|
||||
}
|
||||
|
||||
mIsScrolling = isScrolling;
|
||||
|
||||
}
|
||||
|
||||
public int getTranslatedItemPositionTop() {
|
||||
return mTranslationY;
|
||||
}
|
||||
|
||||
public int getTranslatedItemPositionBottom() {
|
||||
return mTranslationY + mGrabbedItemHeight;
|
||||
}
|
||||
|
||||
|
||||
private static View findRangeFirstItem(RecyclerView rv, ItemDraggableRange range, int firstVisiblePosition, int lastVisiblePosition) {
|
||||
if (firstVisiblePosition == RecyclerView.NO_POSITION || lastVisiblePosition == RecyclerView.NO_POSITION) {
|
||||
return null;
|
||||
}
|
||||
|
||||
View v = null;
|
||||
|
||||
final int childCount = rv.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final View v2 = rv.getChildAt(i);
|
||||
final RecyclerView.ViewHolder vh = rv.getChildViewHolder(v2);
|
||||
|
||||
if (vh != null) {
|
||||
final int position = vh.getLayoutPosition();
|
||||
|
||||
if ((position >= firstVisiblePosition) &&
|
||||
(position <= lastVisiblePosition) &&
|
||||
range.checkInRange(position)) {
|
||||
v = v2;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
private static View findRangeLastItem(RecyclerView rv, ItemDraggableRange range, int firstVisiblePosition, int lastVisiblePosition) {
|
||||
if (firstVisiblePosition == RecyclerView.NO_POSITION || lastVisiblePosition == RecyclerView.NO_POSITION) {
|
||||
return null;
|
||||
}
|
||||
|
||||
View v = null;
|
||||
|
||||
final int childCount = rv.getChildCount();
|
||||
for (int i = childCount - 1; i >= 0; i--) {
|
||||
final View v2 = rv.getChildAt(i);
|
||||
final RecyclerView.ViewHolder vh = rv.getChildViewHolder(v2);
|
||||
|
||||
if (vh != null) {
|
||||
final int position = vh.getLayoutPosition();
|
||||
|
||||
if ((position >= firstVisiblePosition) &&
|
||||
(position <= lastVisiblePosition) &&
|
||||
range.checkInRange(position)) {
|
||||
v = v2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
public void invalidateDraggingItem() {
|
||||
if (mDraggingItem != null) {
|
||||
mDraggingItem.itemView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
mDraggingItem = null;
|
||||
}
|
||||
|
||||
public void setDraggingItemViewHolder(RecyclerView.ViewHolder holder) {
|
||||
if (mDraggingItem != null) {
|
||||
throw new IllegalStateException("A new view holder is attempt to be assigned before invalidating the older one");
|
||||
}
|
||||
|
||||
mDraggingItem = holder;
|
||||
|
||||
holder.itemView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.draggable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.widget.EdgeEffectCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
public class EdgeEffectDecorator extends RecyclerView.ItemDecoration {
|
||||
private RecyclerView mRecyclerView;
|
||||
private EdgeEffectCompat mTopGlow;
|
||||
private EdgeEffectCompat mBottomGlow;
|
||||
private boolean mStarted;
|
||||
|
||||
public EdgeEffectDecorator(RecyclerView recyclerView) {
|
||||
mRecyclerView = recyclerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
boolean needsInvalidate = false;
|
||||
|
||||
if (mTopGlow != null && !mTopGlow.isFinished()) {
|
||||
final int restore = c.save();
|
||||
if (getClipToPadding(parent)) {
|
||||
c.translate(parent.getPaddingLeft(), parent.getPaddingTop());
|
||||
}
|
||||
//noinspection ConstantConditions
|
||||
needsInvalidate |= mTopGlow.draw(c);
|
||||
c.restoreToCount(restore);
|
||||
}
|
||||
|
||||
if (mBottomGlow != null && !mBottomGlow.isFinished()) {
|
||||
final int restore = c.save();
|
||||
c.rotate(180);
|
||||
if (getClipToPadding(parent)) {
|
||||
c.translate(-parent.getWidth() + parent.getPaddingRight(), -parent.getHeight() + parent.getPaddingBottom());
|
||||
} else {
|
||||
c.translate(-parent.getWidth(), -parent.getHeight());
|
||||
}
|
||||
needsInvalidate |= mBottomGlow.draw(c);
|
||||
c.restoreToCount(restore);
|
||||
}
|
||||
|
||||
if (needsInvalidate) {
|
||||
ViewCompat.postInvalidateOnAnimation(parent);
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (mStarted) {
|
||||
return;
|
||||
}
|
||||
mRecyclerView.addItemDecoration(this);
|
||||
mStarted = true;
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
if (mStarted) {
|
||||
mRecyclerView.removeItemDecoration(this);
|
||||
}
|
||||
releaseBothGlows();
|
||||
mRecyclerView = null;
|
||||
mStarted = false;
|
||||
}
|
||||
|
||||
public void pullTopGlow(float deltaDistance) {
|
||||
ensureTopGlow(mRecyclerView);
|
||||
|
||||
if (mTopGlow.onPull(deltaDistance, 0.5f)) {
|
||||
ViewCompat.postInvalidateOnAnimation(mRecyclerView);
|
||||
}
|
||||
}
|
||||
|
||||
public void pullBottom(float deltaDistance) {
|
||||
ensureBottomGlow(mRecyclerView);
|
||||
|
||||
if (mBottomGlow.onPull(deltaDistance, 0.5f)) {
|
||||
ViewCompat.postInvalidateOnAnimation(mRecyclerView);
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseBothGlows() {
|
||||
boolean needsInvalidate = false;
|
||||
|
||||
if (mTopGlow != null) {
|
||||
//noinspection ConstantConditions
|
||||
needsInvalidate |= mTopGlow.onRelease();
|
||||
}
|
||||
|
||||
if (mBottomGlow != null) {
|
||||
needsInvalidate |= mBottomGlow.onRelease();
|
||||
}
|
||||
|
||||
if (needsInvalidate) {
|
||||
ViewCompat.postInvalidateOnAnimation(mRecyclerView);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureTopGlow(RecyclerView rv) {
|
||||
if (mTopGlow == null) {
|
||||
mTopGlow = new EdgeEffectCompat(rv.getContext());
|
||||
}
|
||||
|
||||
updateGlowSize(rv, mTopGlow);
|
||||
}
|
||||
|
||||
private void ensureBottomGlow(RecyclerView rv) {
|
||||
if (mBottomGlow == null) {
|
||||
mBottomGlow = new EdgeEffectCompat(rv.getContext());
|
||||
}
|
||||
updateGlowSize(rv, mBottomGlow);
|
||||
}
|
||||
|
||||
private static void updateGlowSize(RecyclerView rv, EdgeEffectCompat topGlow) {
|
||||
int width = rv.getMeasuredWidth();
|
||||
int height = rv.getMeasuredHeight();
|
||||
|
||||
if (getClipToPadding(rv)) {
|
||||
width -= rv.getPaddingLeft() - rv.getPaddingRight();
|
||||
height -= rv.getPaddingTop() - rv.getPaddingBottom();
|
||||
}
|
||||
|
||||
width = Math.max(0, width);
|
||||
height = Math.max(0, height);
|
||||
|
||||
topGlow.setSize(width, height);
|
||||
}
|
||||
|
||||
private static boolean getClipToPadding(RecyclerView rv) {
|
||||
return rv.getLayoutManager().getClipToPadding();
|
||||
}
|
||||
|
||||
public void reorderToTop() {
|
||||
if (mStarted) {
|
||||
mRecyclerView.removeItemDecoration(this);
|
||||
mRecyclerView.addItemDecoration(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.draggable;
|
||||
|
||||
public class ItemDraggableRange {
|
||||
private final int mStart;
|
||||
private final int mEnd;
|
||||
|
||||
public ItemDraggableRange(int start, int end) {
|
||||
if (!(start <= end)) {
|
||||
throw new IllegalArgumentException("end position (= " + end + ") is smaller than start position (=" + start + ")");
|
||||
}
|
||||
|
||||
mStart = start;
|
||||
mEnd = end;
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return mStart;
|
||||
}
|
||||
|
||||
public int getEnd() {
|
||||
return mEnd;
|
||||
}
|
||||
|
||||
public boolean checkInRange(int position) {
|
||||
return ((position >= mStart) && (position <= mEnd));
|
||||
}
|
||||
|
||||
protected String getClassName() {
|
||||
return "ItemDraggableRange";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClassName() + "{" +
|
||||
"mStart=" + mStart +
|
||||
", mEnd=" + mEnd +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
+1352
File diff suppressed because it is too large.
Load diff
+197
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.draggable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.CustomRecyclerViewUtils;
|
||||
|
||||
class SwapTargetItemOperator extends BaseDraggableItemDecorator {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "SwapTargetItemOperator";
|
||||
|
||||
private RecyclerView.ViewHolder mSwapTargetItem;
|
||||
private Interpolator mSwapTargetTranslationInterpolator;
|
||||
private int mTranslationY;
|
||||
private int mDraggingItemHeight;
|
||||
private Rect mSwapTargetDecorationOffsets = new Rect();
|
||||
private Rect mSwapTargetItemMargins = new Rect();
|
||||
private Rect mDraggingItemMargins = new Rect();
|
||||
private Rect mDraggingItemDecorationOffsets = new Rect();
|
||||
private boolean mStarted;
|
||||
private float mReqTranslationPhase;
|
||||
private float mCurTranslationPhase;
|
||||
private long mDraggingItemId;
|
||||
private ItemDraggableRange mRange;
|
||||
|
||||
public SwapTargetItemOperator(RecyclerView recyclerView, RecyclerView.ViewHolder draggingItem, ItemDraggableRange range) {
|
||||
super(recyclerView, draggingItem);
|
||||
|
||||
mDraggingItemId = mDraggingItem.getItemId();
|
||||
mRange = range;
|
||||
|
||||
CustomRecyclerViewUtils.getLayoutMargins(mDraggingItem.itemView, mDraggingItemMargins);
|
||||
CustomRecyclerViewUtils.getDecorationOffsets(
|
||||
mRecyclerView.getLayoutManager(), mDraggingItem.itemView, mDraggingItemDecorationOffsets);
|
||||
}
|
||||
|
||||
public void setSwapTargetTranslationInterpolator(Interpolator interpolator) {
|
||||
mSwapTargetTranslationInterpolator = interpolator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
final RecyclerView.ViewHolder draggingItem = mDraggingItem;
|
||||
|
||||
if (draggingItem == null || draggingItem.getItemId() != mDraggingItemId) {
|
||||
return;
|
||||
}
|
||||
|
||||
final RecyclerView.ViewHolder swapTargetItem =
|
||||
RecyclerViewDragDropManager.findSwapTargetItem(
|
||||
mRecyclerView, draggingItem, mDraggingItemId, mTranslationY, mRange);
|
||||
|
||||
// reset Y-translation if the swap target has changed
|
||||
if ((mSwapTargetItem != swapTargetItem) && (mSwapTargetItem != null)) {
|
||||
setItemTranslationY(mRecyclerView, mSwapTargetItem, 0.0f);
|
||||
}
|
||||
|
||||
if (swapTargetItem != null) {
|
||||
mReqTranslationPhase = calculateTranslationPhase(draggingItem, swapTargetItem);
|
||||
|
||||
if (mSwapTargetItem != swapTargetItem) {
|
||||
mCurTranslationPhase = mReqTranslationPhase;
|
||||
} else {
|
||||
// interpolate to make it moves smoothly
|
||||
mCurTranslationPhase = calculateCurrentTranslationPhase(mCurTranslationPhase, mReqTranslationPhase);
|
||||
}
|
||||
|
||||
updateSwapTargetTranslation(draggingItem, swapTargetItem, mCurTranslationPhase);
|
||||
}
|
||||
|
||||
mSwapTargetItem = swapTargetItem;
|
||||
}
|
||||
|
||||
private float calculateTranslationPhase(RecyclerView.ViewHolder draggingItem, RecyclerView.ViewHolder swapTargetItem) {
|
||||
final View swapItemView = swapTargetItem.itemView;
|
||||
|
||||
final int pos1 = draggingItem.getLayoutPosition();
|
||||
final int pos2 = swapTargetItem.getLayoutPosition();
|
||||
|
||||
CustomRecyclerViewUtils.getDecorationOffsets(
|
||||
mRecyclerView.getLayoutManager(), swapItemView, mSwapTargetDecorationOffsets);
|
||||
CustomRecyclerViewUtils.getLayoutMargins(swapItemView, mSwapTargetItemMargins);
|
||||
|
||||
final Rect m2 = mSwapTargetItemMargins;
|
||||
final Rect d2 = mSwapTargetDecorationOffsets;
|
||||
final int h2 = swapItemView.getHeight() + m2.top + m2.bottom + d2.top + d2.bottom;
|
||||
|
||||
final float offsetPx = draggingItem.itemView.getTop() - mTranslationY; // == -(ViewCompat.getTranslationY(draggingItem.itemView)
|
||||
final float phase = (h2 != 0) ? (offsetPx / h2) : 0.0f;
|
||||
|
||||
float translationPhase;
|
||||
|
||||
if (pos1 > pos2) {
|
||||
// dragging item moving to upward
|
||||
translationPhase = phase;
|
||||
} else {
|
||||
// dragging item moving to downward
|
||||
translationPhase = 1.0f + phase;
|
||||
}
|
||||
|
||||
return Math.min(Math.max(translationPhase, 0.0f), 1.0f);
|
||||
}
|
||||
|
||||
private static float calculateCurrentTranslationPhase(float cur, float req) {
|
||||
final float A = 0.3f;
|
||||
final float B = 0.01f;
|
||||
final float tmp = (cur * (1.0f - A)) + (req * A);
|
||||
|
||||
return (Math.abs(tmp - req) < B) ? req : tmp;
|
||||
}
|
||||
|
||||
private void updateSwapTargetTranslation(RecyclerView.ViewHolder draggingItem, RecyclerView.ViewHolder swapTargetItem, float translationPhase) {
|
||||
final View swapItemView = swapTargetItem.itemView;
|
||||
|
||||
final int pos1 = draggingItem.getLayoutPosition();
|
||||
final int pos2 = swapTargetItem.getLayoutPosition();
|
||||
|
||||
final Rect m1 = mDraggingItemMargins;
|
||||
final Rect d1 = mDraggingItemDecorationOffsets;
|
||||
final int h1 = mDraggingItemHeight + m1.top + m1.bottom + d1.top + d1.bottom;
|
||||
|
||||
if (mSwapTargetTranslationInterpolator != null) {
|
||||
translationPhase = mSwapTargetTranslationInterpolator.getInterpolation(translationPhase);
|
||||
}
|
||||
|
||||
if (pos1 > pos2) {
|
||||
// dragging item moving to upward
|
||||
ViewCompat.setTranslationY(swapItemView, translationPhase * h1);
|
||||
} else {
|
||||
// dragging item moving to downward
|
||||
ViewCompat.setTranslationY(swapItemView, (translationPhase - 1.0f) * h1);
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (mStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
mDraggingItemHeight = mDraggingItem.itemView.getHeight();
|
||||
|
||||
mRecyclerView.addItemDecoration(this, 0);
|
||||
|
||||
mStarted = true;
|
||||
}
|
||||
|
||||
public void finish(boolean animate) {
|
||||
if (mStarted) {
|
||||
mRecyclerView.removeItemDecoration(this);
|
||||
}
|
||||
|
||||
final RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
|
||||
if (itemAnimator != null) {
|
||||
itemAnimator.endAnimations();
|
||||
}
|
||||
mRecyclerView.stopScroll();
|
||||
|
||||
if (mSwapTargetItem != null) {
|
||||
// return to default position
|
||||
updateSwapTargetTranslation(mDraggingItem, mSwapTargetItem, mCurTranslationPhase);
|
||||
moveToDefaultPosition(mSwapTargetItem.itemView, animate);
|
||||
mSwapTargetItem = null;
|
||||
}
|
||||
|
||||
mRange = null;
|
||||
mDraggingItem = null;
|
||||
mTranslationY = 0;
|
||||
mDraggingItemHeight = 0;
|
||||
mCurTranslationPhase = 0.0f;
|
||||
mReqTranslationPhase = 0.0f;
|
||||
mStarted = false;
|
||||
}
|
||||
|
||||
public void update(int translationY) {
|
||||
mTranslationY = translationY;
|
||||
}
|
||||
}
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.event;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class BaseRecyclerViewEventDistributor<T> {
|
||||
protected boolean mReleased;
|
||||
protected RecyclerView mRecyclerView;
|
||||
protected List<T> mListeners;
|
||||
protected boolean mPerformingClearMethod;
|
||||
|
||||
public BaseRecyclerViewEventDistributor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets attached {@link android.support.v7.widget.RecyclerView}
|
||||
*
|
||||
* @return The {@link android.support.v7.widget.RecyclerView} instance
|
||||
*/
|
||||
public RecyclerView getRecyclerView() {
|
||||
return mRecyclerView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release all references.
|
||||
* This method should be called to avoid memory leaks.
|
||||
*/
|
||||
public void release() {
|
||||
if (mReleased) {
|
||||
return;
|
||||
}
|
||||
mReleased = true;
|
||||
clear(true);
|
||||
onRelease();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this distributor has been released
|
||||
*
|
||||
* @return True for this distributor has been released, otherwise false
|
||||
*/
|
||||
public boolean isReleased() {
|
||||
return mReleased;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches {@link android.support.v7.widget.RecyclerView} instance.
|
||||
*
|
||||
* @param rv The {@link android.support.v7.widget.RecyclerView} instance
|
||||
*/
|
||||
public void attachRecyclerView(RecyclerView rv) {
|
||||
final String METHOD_NAME = "attachRecyclerView()";
|
||||
|
||||
if (rv == null) {
|
||||
throw new IllegalArgumentException("RecyclerView cannot be null");
|
||||
}
|
||||
|
||||
verifyIsNotReleased(METHOD_NAME);
|
||||
verifyIsNotPerformingClearMethod(METHOD_NAME);
|
||||
|
||||
onRecyclerViewAttached(rv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link T} listener to the chain.
|
||||
*
|
||||
* @param listener The {@link T} instance
|
||||
*
|
||||
* @return True if the listener object successfully added, otherwise false. Also returns true if have already been added.
|
||||
*/
|
||||
public boolean add(T listener) {
|
||||
return add(listener, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link T} listener to the chain at the specified position.
|
||||
*
|
||||
* @param listener The {@link T} instance
|
||||
* @param index Position in the listener chain to insert this listener at. (< 0: tail of the chain)
|
||||
*
|
||||
* @return True if the listener object successfully added, otherwise false. Also returns true if have already been added.
|
||||
*/
|
||||
public boolean add(T listener, int index) {
|
||||
final String METHOD_NAME = "add()";
|
||||
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("can not specify null for the listener");
|
||||
}
|
||||
|
||||
verifyIsNotReleased(METHOD_NAME);
|
||||
verifyIsNotPerformingClearMethod(METHOD_NAME);
|
||||
|
||||
if (mListeners == null) {
|
||||
mListeners = new ArrayList<>();
|
||||
}
|
||||
|
||||
if (!mListeners.contains(listener)) {
|
||||
if (index < 0) {
|
||||
// append to the tail of the list
|
||||
mListeners.add(listener);
|
||||
} else {
|
||||
// insert to the specified position
|
||||
mListeners.add(index, listener);
|
||||
}
|
||||
|
||||
// raise onAddedToEventDistributor() event
|
||||
if (listener instanceof RecyclerViewEventDistributorListener) {
|
||||
((RecyclerViewEventDistributorListener) listener).onAddedToEventDistributor(this);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a {@link T} listener from the chain.
|
||||
*
|
||||
* @param listener Listener to remove
|
||||
*
|
||||
* @return True for successfully removed the listener object, otherwise false
|
||||
*/
|
||||
public boolean remove(T listener) {
|
||||
final String METHOD_NAME = "remove()";
|
||||
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("can not specify null for the listener");
|
||||
}
|
||||
|
||||
verifyIsNotPerformingClearMethod(METHOD_NAME);
|
||||
verifyIsNotReleased(METHOD_NAME);
|
||||
|
||||
if (mListeners == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean removed = mListeners.remove(listener);
|
||||
|
||||
if (removed) {
|
||||
// raise onRemovedFromEventDistributor() event
|
||||
if (listener instanceof RecyclerViewEventDistributorListener) {
|
||||
((RecyclerViewEventDistributorListener) listener).onRemovedFromEventDistributor(this);
|
||||
}
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all listeners from the chain.
|
||||
*/
|
||||
public void clear() {
|
||||
clear(false);
|
||||
}
|
||||
|
||||
protected void clear(boolean calledFromRelease) {
|
||||
final String METHOD_NAME = "clear()";
|
||||
|
||||
if (!calledFromRelease) {
|
||||
verifyIsNotReleased(METHOD_NAME);
|
||||
}
|
||||
verifyIsNotPerformingClearMethod(METHOD_NAME);
|
||||
|
||||
if (mListeners == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mPerformingClearMethod = true;
|
||||
|
||||
final int n = mListeners.size();
|
||||
|
||||
for (int i = n - 1; i >= 0; i--) {
|
||||
final T listener = mListeners.remove(i);
|
||||
|
||||
// raise onRemovedFromEventDistributor() event
|
||||
if (listener instanceof RecyclerViewEventDistributorListener) {
|
||||
((RecyclerViewEventDistributorListener) listener).onRemovedFromEventDistributor(this);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
mPerformingClearMethod = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of underlying listener objects.
|
||||
*
|
||||
* @return Number of underlying listener objects in the chain.
|
||||
*/
|
||||
public int size() {
|
||||
if (mListeners != null) {
|
||||
return mListeners.size();
|
||||
} else {
|
||||
return mListeners.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the specified listener object is contained in the chain.
|
||||
*
|
||||
* @param listener Listener to check
|
||||
*
|
||||
* @return True for the listener contains in the chain, otherwise false
|
||||
*/
|
||||
public boolean contains(T listener) {
|
||||
if (mListeners != null) {
|
||||
return mListeners.contains(listener);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void onRelease() {
|
||||
mRecyclerView = null;
|
||||
mListeners = null;
|
||||
mPerformingClearMethod = false;
|
||||
}
|
||||
|
||||
protected void onRecyclerViewAttached(RecyclerView rv) {
|
||||
mRecyclerView = rv;
|
||||
}
|
||||
|
||||
protected void verifyIsNotPerformingClearMethod(String methodName) {
|
||||
if (mPerformingClearMethod) {
|
||||
throw new IllegalStateException(methodName + " can not be called while performing the clear() method");
|
||||
}
|
||||
}
|
||||
|
||||
protected void verifyIsNotReleased(String methodName) {
|
||||
if (mReleased) {
|
||||
throw new IllegalStateException(methodName + " can not be called after release() method called");
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.event;
|
||||
|
||||
public interface RecyclerViewEventDistributorListener {
|
||||
void onAddedToEventDistributor(BaseRecyclerViewEventDistributor distributor);
|
||||
void onRemovedFromEventDistributor(BaseRecyclerViewEventDistributor distributor);
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.event;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Deprecated.
|
||||
*
|
||||
* Please use {@link RecyclerView#addOnScrollListener(RecyclerView.OnScrollListener)} and {@link RecyclerView#removeOnScrollListener(RecyclerView.OnScrollListener)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class RecyclerViewOnScrollEventDistributor extends BaseRecyclerViewEventDistributor<RecyclerView.OnScrollListener> {
|
||||
|
||||
private InternalOnScrollListener mInternalOnScrollListener;
|
||||
|
||||
public RecyclerViewOnScrollEventDistributor() {
|
||||
super();
|
||||
|
||||
mInternalOnScrollListener = new InternalOnScrollListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRecyclerViewAttached(RecyclerView rv) {
|
||||
super.onRecyclerViewAttached(rv);
|
||||
|
||||
rv.addOnScrollListener(mInternalOnScrollListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRelease() {
|
||||
if (mInternalOnScrollListener != null) {
|
||||
if (mRecyclerView != null) {
|
||||
mRecyclerView.removeOnScrollListener(mInternalOnScrollListener);
|
||||
}
|
||||
mInternalOnScrollListener.release();
|
||||
mInternalOnScrollListener = null;
|
||||
}
|
||||
|
||||
super.onRelease();
|
||||
}
|
||||
|
||||
/*package*/ void handleOnScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||
if (mListeners == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (RecyclerView.OnScrollListener listener : mListeners) {
|
||||
listener.onScrollStateChanged(recyclerView, newState);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void handleOnScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
if (mListeners == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (RecyclerView.OnScrollListener listener : mListeners) {
|
||||
listener.onScrolled(recyclerView, dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static class InternalOnScrollListener extends RecyclerView.OnScrollListener {
|
||||
private WeakReference<RecyclerViewOnScrollEventDistributor> mRefDistributor;
|
||||
|
||||
public InternalOnScrollListener(RecyclerViewOnScrollEventDistributor distributor) {
|
||||
super();
|
||||
mRefDistributor = new WeakReference<>(distributor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||
final RecyclerViewOnScrollEventDistributor distributor = mRefDistributor.get();
|
||||
if (distributor != null) {
|
||||
distributor.handleOnScrollStateChanged(recyclerView, newState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
final RecyclerViewOnScrollEventDistributor holder = mRefDistributor.get();
|
||||
if (holder != null) {
|
||||
holder.handleOnScrolled(recyclerView, dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
mRefDistributor.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.event;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class RecyclerViewRecyclerEventDistributor extends BaseRecyclerViewEventDistributor<RecyclerView.RecyclerListener> {
|
||||
|
||||
private InternalRecyclerListener mInternalRecyclerListener;
|
||||
|
||||
public RecyclerViewRecyclerEventDistributor() {
|
||||
super();
|
||||
|
||||
mInternalRecyclerListener = new InternalRecyclerListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRecyclerViewAttached(RecyclerView rv) {
|
||||
super.onRecyclerViewAttached(rv);
|
||||
|
||||
rv.setRecyclerListener(mInternalRecyclerListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRelease() {
|
||||
super.onRelease();
|
||||
|
||||
if (mInternalRecyclerListener != null) {
|
||||
mInternalRecyclerListener.release();
|
||||
mInternalRecyclerListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void handleOnViewRecycled(RecyclerView.ViewHolder holder) {
|
||||
if (mListeners == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (RecyclerView.RecyclerListener listener : mListeners) {
|
||||
listener.onViewRecycled(holder);
|
||||
}
|
||||
}
|
||||
|
||||
private static class InternalRecyclerListener implements RecyclerView.RecyclerListener {
|
||||
private WeakReference<RecyclerViewRecyclerEventDistributor> mRefDistributor;
|
||||
|
||||
public InternalRecyclerListener(RecyclerViewRecyclerEventDistributor distributor) {
|
||||
super();
|
||||
mRefDistributor = new WeakReference<>(distributor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRecycled(RecyclerView.ViewHolder holder) {
|
||||
final RecyclerViewRecyclerEventDistributor distributor = mRefDistributor.get();
|
||||
|
||||
if (distributor != null) {
|
||||
distributor.handleOnViewRecycled(holder);
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
mRefDistributor.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.expandable;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
|
||||
|
||||
public class ChildPositionItemDraggableRange extends ItemDraggableRange {
|
||||
public ChildPositionItemDraggableRange(int start, int end) {
|
||||
super(start, end);
|
||||
}
|
||||
|
||||
protected String getClassName() {
|
||||
return "ChildPositionItemDraggableRange";
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.expandable;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
class ExpandableAdapterHelper {
|
||||
public static final long NO_EXPANDABLE_POSITION = 0xffffffffffffffffl;
|
||||
|
||||
private static final long LOWER_32BIT_MASK = 0x00000000ffffffffl;
|
||||
private static final long LOWER_31BIT_MASK = 0x000000007fffffffl;
|
||||
|
||||
/*package*/ static final int VIEW_TYPE_FLAG_IS_GROUP = 0x80000000;
|
||||
|
||||
public static long getPackedPositionForChild(int groupPosition, int childPosition) {
|
||||
return ((long) childPosition << 32) | (groupPosition & LOWER_32BIT_MASK);
|
||||
}
|
||||
|
||||
public static long getPackedPositionForGroup(int groupPosition) {
|
||||
return ((long) RecyclerView.NO_POSITION << 32) | (groupPosition & LOWER_32BIT_MASK);
|
||||
}
|
||||
|
||||
public static int getPackedPositionChild(long packedPosition) {
|
||||
return (int) (packedPosition >>> 32);
|
||||
}
|
||||
|
||||
public static int getPackedPositionGroup(long packedPosition) {
|
||||
return (int) (packedPosition & LOWER_32BIT_MASK);
|
||||
}
|
||||
|
||||
public static long getCombinedChildId(long groupId, long childId) {
|
||||
return ((groupId & LOWER_31BIT_MASK) << 32) | (childId & LOWER_32BIT_MASK);
|
||||
}
|
||||
|
||||
public static long getCombinedGroupId(long groupId) {
|
||||
//noinspection PointlessBitwiseExpression
|
||||
return ((groupId & LOWER_31BIT_MASK) << 32) | (RecyclerView.NO_ID & LOWER_32BIT_MASK);
|
||||
}
|
||||
|
||||
public static boolean isGroupViewType(int rawViewType) {
|
||||
return ((rawViewType & VIEW_TYPE_FLAG_IS_GROUP) != 0);
|
||||
}
|
||||
|
||||
public static int getGroupViewType(int rawViewType) {
|
||||
return (rawViewType & (~VIEW_TYPE_FLAG_IS_GROUP));
|
||||
}
|
||||
|
||||
public static int getChildViewType(int rawViewType) {
|
||||
return (rawViewType & (~VIEW_TYPE_FLAG_IS_GROUP));
|
||||
}
|
||||
|
||||
private ExpandableAdapterHelper() {
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.expandable;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
|
||||
|
||||
public interface ExpandableDraggableItemAdapter<GVH extends RecyclerView.ViewHolder, CVH extends RecyclerView.ViewHolder> {
|
||||
/**
|
||||
* Called when user is attempt to drag the group item.
|
||||
*
|
||||
* @param holder The group ViewHolder which is associated to item user is attempt to start dragging.
|
||||
* @param groupPosition Group position.
|
||||
* @param x Touched X position. Relative from the itemView's top-left.
|
||||
* @param y Touched Y position. Relative from the itemView's top-left.
|
||||
*
|
||||
* @return Whether can start dragging.
|
||||
*/
|
||||
boolean onCheckGroupCanStartDrag(GVH holder, int groupPosition, int x, int y);
|
||||
|
||||
/**
|
||||
* Called when user is attempt to drag the child item.
|
||||
*
|
||||
* @param holder The child ViewHolder which is associated to item user is attempt to start dragging.
|
||||
* @param groupPosition Group position.
|
||||
* @param childPosition Child position.
|
||||
* @param x Touched X position. Relative from the itemView's top-left.
|
||||
* @param y Touched Y position. Relative from the itemView's top-left.
|
||||
*
|
||||
* @return Whether can start dragging.
|
||||
*/
|
||||
boolean onCheckChildCanStartDrag(CVH holder, int groupPosition, int childPosition, int x, int y);
|
||||
|
||||
/**
|
||||
* Called after the {@link #onCheckGroupCanStartDrag(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int)} method returned true.
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to item user is attempt to start dragging.
|
||||
* @param groupPosition Group position.
|
||||
*
|
||||
* @return null: no constraints (= new ItemDraggableRange(0, getGroupCount() - 1)),
|
||||
* otherwise: the range specified item can be drag-sortable.
|
||||
*/
|
||||
ItemDraggableRange onGetGroupItemDraggableRange(GVH holder, int groupPosition);
|
||||
|
||||
/**
|
||||
* Called after the {@link #onCheckChildCanStartDrag(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int, int)} method returned true.
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to item user is attempt to start dragging.
|
||||
* @param groupPosition Group position.
|
||||
* @param childPosition Child position.
|
||||
*
|
||||
* @return null: no constraints (= new ItemDraggableRange(0, getGroupCount() - 1)),
|
||||
* otherwise: the range specified item can be drag-sortable.
|
||||
*/
|
||||
ItemDraggableRange onGetChildItemDraggableRange(CVH holder, int groupPosition, int childPosition);
|
||||
|
||||
/**
|
||||
* Called when group item is moved. Should apply the move operation result to data set.
|
||||
*
|
||||
* @param fromGroupPosition Previous group position of the item.
|
||||
* @param toGroupPosition New group position of the item.
|
||||
*/
|
||||
void onMoveGroupItem(int fromGroupPosition, int toGroupPosition);
|
||||
|
||||
/**
|
||||
* Called when child item is moved. Should apply the move operation result to data set.
|
||||
*
|
||||
* @param fromGroupPosition Previous group position of the item.
|
||||
* @param fromChildPosition Previous child position of the item.
|
||||
* @param toGroupPosition New group position of the item.
|
||||
* @param toChildPosition New child position of the item.
|
||||
*/
|
||||
void onMoveChildItem(int fromGroupPosition, int fromChildPosition, int toGroupPosition, int toChildPosition);
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.expandable;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public interface ExpandableItemAdapter<GVH extends RecyclerView.ViewHolder, CVH extends RecyclerView.ViewHolder> {
|
||||
|
||||
/**
|
||||
* Gets the number of groups.
|
||||
*
|
||||
* @return the number of groups
|
||||
*/
|
||||
int getGroupCount();
|
||||
|
||||
/**
|
||||
* Gets the number of children in a specified group.
|
||||
*
|
||||
* @param groupPosition the position of the group for which the children count should be returned
|
||||
*
|
||||
* @return the number of children
|
||||
*/
|
||||
int getChildCount(int groupPosition);
|
||||
|
||||
/**
|
||||
* Gets the ID for the group at the given position. This group ID must be unique across groups.
|
||||
*
|
||||
* The combined ID (see {@link RecyclerViewExpandableItemManager#getCombinedGroupId(long)})
|
||||
* must be unique across ALL items (groups and all children).
|
||||
*
|
||||
* @param groupPosition the position of the group for which the ID is wanted
|
||||
* @return the ID associated with the group
|
||||
*/
|
||||
long getGroupId(int groupPosition);
|
||||
|
||||
/**
|
||||
* Gets the ID for the given child within the given group.
|
||||
*
|
||||
* This ID must be unique across all children within the group.
|
||||
* The combined ID (see {@link RecyclerViewExpandableItemManager#getCombinedChildId(long, long)})
|
||||
* must be unique across ALL items (groups and all children).
|
||||
*
|
||||
* @param groupPosition the position of the group that contains the child
|
||||
* @param childPosition the position of the child within the group for which the ID is wanted
|
||||
|
||||
* @return the ID associated with the child
|
||||
*/
|
||||
long getChildId(int groupPosition, int childPosition);
|
||||
|
||||
/**
|
||||
* Gets the view type of the specified group.
|
||||
*
|
||||
* @param groupPosition the position of the group for which the view type is wanted
|
||||
*
|
||||
* @return integer value identifying the type of the view needed to represent the group item at position. Type codes need positive number but not be contiguous.
|
||||
*/
|
||||
int getGroupItemViewType(int groupPosition);
|
||||
|
||||
/**
|
||||
* Gets the view type of the specified child.
|
||||
*
|
||||
* @param groupPosition the position of the group that contains the child
|
||||
* @param childPosition the position of the child within the group for which the view type is wanted
|
||||
*
|
||||
* @return integer value identifying the type of the view needed to represent the group item at position. Type codes need positive number but not be contiguous.
|
||||
*/
|
||||
int getChildItemViewType(int groupPosition, int childPosition);
|
||||
|
||||
/**
|
||||
* Called when RecyclerView needs a new {@link GVH} of the given type to represent a group item.
|
||||
*
|
||||
* @param parent The ViewGroup into which the new View will be added after it is bound to an adapter position
|
||||
* @param viewType The view type of the new View
|
||||
*
|
||||
* @return A new group ViewHolder that holds a View of the given view type
|
||||
*/
|
||||
GVH onCreateGroupViewHolder(ViewGroup parent, int viewType);
|
||||
|
||||
/**
|
||||
* Called when RecyclerView needs a new {@link CVH} of the given type to represent a child item.
|
||||
*
|
||||
* @param parent The ViewGroup into which the new View will be added after it is bound to an adapter position
|
||||
* @param viewType The view type of the new View
|
||||
*
|
||||
* @return A new child ViewHolder that holds a View of the given view type
|
||||
*/
|
||||
CVH onCreateChildViewHolder(ViewGroup parent, int viewType);
|
||||
|
||||
/**
|
||||
* Called by RecyclerView to display the group data at the specified position.
|
||||
* This method should update the contents of the {@link android.support.v7.widget.RecyclerView.ViewHolder#itemView}
|
||||
* to reflect the item at the given position.
|
||||
*
|
||||
* @param holder The ViewHolder which should be updated to represent the contents of the item at the given position in the data set
|
||||
* @param groupPosition The position of the group item within the adapter's data set
|
||||
* @param viewType The view type code
|
||||
*/
|
||||
void onBindGroupViewHolder(GVH holder, int groupPosition, int viewType);
|
||||
|
||||
/**
|
||||
* Called by RecyclerView to display the child data at the specified position.
|
||||
* This method should update the contents of the {@link android.support.v7.widget.RecyclerView.ViewHolder#itemView}
|
||||
* to reflect the item at the given position.
|
||||
*
|
||||
* @param holder The ViewHolder which should be updated to represent the contents of the item at the given position in the data set
|
||||
* @param groupPosition The position of the group item within the adapter's data set
|
||||
* @param childPosition The position of the child item within the group
|
||||
* @param viewType The view type code
|
||||
*/
|
||||
void onBindChildViewHolder(CVH holder, int groupPosition, int childPosition, int viewType);
|
||||
|
||||
/**
|
||||
* Called when a user attempt to expand/collapse a group item by tapping.
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to group item user is attempt to expand/collapse
|
||||
* @param groupPosition Group position
|
||||
* @param x Touched X position. Relative from the itemView's top-left
|
||||
* @param y Touched Y position. Relative from the itemView's top-left
|
||||
* @param expand true: expand, false: collapse
|
||||
*
|
||||
* @return Whether to perform expand/collapse operation.
|
||||
*/
|
||||
boolean onCheckCanExpandOrCollapseGroup(GVH holder, int groupPosition, int x, int y, boolean expand);
|
||||
|
||||
/**
|
||||
* Called when a group attempt to expand by user operation or by
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.expandable.RecyclerViewExpandableItemManager#expandGroup(int)} method.
|
||||
*
|
||||
* @param groupPosition The position of the group item within the adapter's data set
|
||||
* @param fromUser Whether the expand request is issued by a user operation
|
||||
*
|
||||
* @return Whether the group can be expanded. If returns false, the group keeps collapsed.
|
||||
*/
|
||||
boolean onHookGroupExpand(int groupPosition, boolean fromUser);
|
||||
|
||||
/**
|
||||
* Called when a group attempt to expand by user operation or by
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.expandable.RecyclerViewExpandableItemManager#collapseGroup(int)} method.
|
||||
*
|
||||
* @param groupPosition The position of the group item within the adapter's data set
|
||||
* @param fromUser Whether the collapse request is issued by a user operation
|
||||
*
|
||||
* @return Whether the group can be collapsed. If returns false, the group keeps expanded.
|
||||
*/
|
||||
boolean onHookGroupCollapse(int groupPosition, boolean fromUser);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.expandable;
|
||||
|
||||
/**
|
||||
* Interface which provides required information for expanding item.
|
||||
*
|
||||
* Implement this interface on your sub-class of the {@link android.support.v7.widget.RecyclerView.ViewHolder}.
|
||||
*/
|
||||
public interface ExpandableItemViewHolder {
|
||||
/**
|
||||
* Sets the state flags value for expanding item
|
||||
*
|
||||
* @param flags Bitwise OR of these flags;
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.expandable.RecyclerViewExpandableItemManager#STATE_FLAG_IS_GROUP}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.expandable.RecyclerViewExpandableItemManager#STATE_FLAG_IS_CHILD}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.expandable.RecyclerViewExpandableItemManager#STATE_FLAG_IS_EXPANDED}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.expandable.RecyclerViewExpandableItemManager#STATE_FLAG_IS_UPDATED}
|
||||
*/
|
||||
void setExpandStateFlags(int flags);
|
||||
|
||||
/**
|
||||
* Gets the state flags value for expanding item
|
||||
*
|
||||
* @return Bitwise OR of these flags;
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.expandable.RecyclerViewExpandableItemManager#STATE_FLAG_IS_GROUP}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.expandable.RecyclerViewExpandableItemManager#STATE_FLAG_IS_CHILD}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.expandable.RecyclerViewExpandableItemManager#STATE_FLAG_IS_EXPANDED}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.expandable.RecyclerViewExpandableItemManager#STATE_FLAG_IS_UPDATED}
|
||||
*/
|
||||
int getExpandStateFlags();
|
||||
}
|
||||
+582
@@ -0,0 +1,582 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.expandable;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
class ExpandablePositionTranslator {
|
||||
private final int ALLOCATE_UNIT = 256;
|
||||
|
||||
private final static long FLAG_EXPANDED = 0x0000000080000000l;
|
||||
private final static long LOWER_31BIT_MASK = 0x000000007fffffffl;
|
||||
private final static long LOWER_32BIT_MASK = 0x00000000ffffffffl;
|
||||
private final static long UPPER_32BIT_MASK = 0xffffffff00000000l;
|
||||
|
||||
/*
|
||||
* bit 64-32: offset (use for caching purpose)
|
||||
* bit 31: expanded or not
|
||||
* bit 30-0: child count
|
||||
*/
|
||||
private long[] mCachedGroupPosInfo;
|
||||
|
||||
/*
|
||||
* bit 31: reserved
|
||||
* bit 30-0: group id
|
||||
*/
|
||||
private int[] mCachedGroupId;
|
||||
private int mGroupCount;
|
||||
private int mExpandedGroupCount;
|
||||
private int mExpandedChildCount;
|
||||
private int mEndOfCalculatedOffsetGroupPosition = RecyclerView.NO_POSITION;
|
||||
private ExpandableItemAdapter mAdapter;
|
||||
|
||||
public ExpandablePositionTranslator() {
|
||||
}
|
||||
|
||||
public void build(ExpandableItemAdapter adapter) {
|
||||
final int groupCount = adapter.getGroupCount();
|
||||
|
||||
enlargeArraysIfNeeded(groupCount, false);
|
||||
|
||||
final long[] info = mCachedGroupPosInfo;
|
||||
final int[] ids = mCachedGroupId;
|
||||
for (int i = 0; i < groupCount; i++) {
|
||||
final long groupId = adapter.getGroupId(i);
|
||||
final int childCount = adapter.getChildCount(i);
|
||||
|
||||
info[i] = (((long) i << 32) | childCount);
|
||||
ids[i] = (int) (groupId & LOWER_32BIT_MASK);
|
||||
}
|
||||
|
||||
mAdapter = adapter;
|
||||
mGroupCount = groupCount;
|
||||
mExpandedGroupCount = 0;
|
||||
mExpandedChildCount = 0;
|
||||
mEndOfCalculatedOffsetGroupPosition = Math.max(0, groupCount - 1);
|
||||
}
|
||||
|
||||
public void restoreExpandedGroupItems(
|
||||
int[] restoreGroupIds,
|
||||
ExpandableItemAdapter adapter,
|
||||
RecyclerViewExpandableItemManager.OnGroupExpandListener expandListener,
|
||||
RecyclerViewExpandableItemManager.OnGroupCollapseListener collapseListener) {
|
||||
if (restoreGroupIds == null || restoreGroupIds.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCachedGroupPosInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make ID + position packed array
|
||||
final long[] idAndPos = new long[mGroupCount];
|
||||
|
||||
for (int i = 0; i < mGroupCount; i++) {
|
||||
idAndPos[i] = ((long) mCachedGroupId[i] << 32) | i;
|
||||
}
|
||||
|
||||
// sort both arrays
|
||||
Arrays.sort(idAndPos);
|
||||
|
||||
final boolean fromUser = false;
|
||||
|
||||
// find matched items & apply
|
||||
int index = 0;
|
||||
//noinspection ForLoopReplaceableByForEach
|
||||
for (int i = 0; i < restoreGroupIds.length; i++) {
|
||||
final int id1 = restoreGroupIds[i];
|
||||
|
||||
for (int j = index; j < idAndPos.length; j++) {
|
||||
final int id2 = (int) (idAndPos[j] >> 32);
|
||||
final int position = (int) (idAndPos[j] & LOWER_31BIT_MASK);
|
||||
|
||||
if (id2 < id1) {
|
||||
index = j;
|
||||
|
||||
if (adapter == null || adapter.onHookGroupCollapse(position, fromUser)) {
|
||||
if (collapseGroup(position)) {
|
||||
if (collapseListener != null) {
|
||||
collapseListener.onGroupCollapse(position, fromUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (id2 == id1) {
|
||||
// matched
|
||||
index = j + 1;
|
||||
|
||||
if (adapter == null || adapter.onHookGroupExpand(position, fromUser)) {
|
||||
if (expandGroup(position)) {
|
||||
if (expandListener != null) {
|
||||
expandListener.onGroupExpand(position, fromUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // id2 > id1
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (adapter != null || collapseListener != null) {
|
||||
for (int i = index; i < idAndPos.length; i++) {
|
||||
final int id2 = (int) (idAndPos[i] >> 32);
|
||||
final int position = (int) (idAndPos[i] & LOWER_31BIT_MASK);
|
||||
|
||||
if (adapter == null || adapter.onHookGroupCollapse(position, fromUser)) {
|
||||
if (collapseGroup(position)) {
|
||||
if (collapseListener != null) {
|
||||
collapseListener.onGroupCollapse(position, fromUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int[] getSavedStateArray() {
|
||||
int[] expandedGroups = new int[mExpandedGroupCount];
|
||||
|
||||
int index = 0;
|
||||
for (int i = 0; i < mGroupCount; i++) {
|
||||
final long t = mCachedGroupPosInfo[i];
|
||||
if ((t & FLAG_EXPANDED) != 0) {
|
||||
expandedGroups[index] = mCachedGroupId[i];
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (index != mExpandedGroupCount) {
|
||||
throw new IllegalStateException("may be a bug (index = " + index + ", mExpandedGroupCount = " + mExpandedGroupCount + ")");
|
||||
}
|
||||
|
||||
Arrays.sort(expandedGroups);
|
||||
|
||||
return expandedGroups;
|
||||
}
|
||||
|
||||
public int getItemCount() {
|
||||
return mGroupCount + mExpandedChildCount;
|
||||
}
|
||||
|
||||
public boolean isGroupExpanded(int groupPosition) {
|
||||
return ((mCachedGroupPosInfo[groupPosition] & FLAG_EXPANDED) != 0);
|
||||
}
|
||||
|
||||
public int getChildCount(int groupPosition) {
|
||||
return (int) (mCachedGroupPosInfo[groupPosition] & LOWER_31BIT_MASK);
|
||||
}
|
||||
|
||||
public int getVisibleChildCount(int groupPosition) {
|
||||
if (isGroupExpanded(groupPosition)) {
|
||||
return getChildCount(groupPosition);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean collapseGroup(int groupPosition) {
|
||||
if ((mCachedGroupPosInfo[groupPosition] & FLAG_EXPANDED) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int childCount = (int) (mCachedGroupPosInfo[groupPosition] & LOWER_31BIT_MASK);
|
||||
|
||||
mCachedGroupPosInfo[groupPosition] &= (~FLAG_EXPANDED);
|
||||
mExpandedGroupCount -= 1;
|
||||
|
||||
mExpandedChildCount -= childCount;
|
||||
mEndOfCalculatedOffsetGroupPosition = Math.min(mEndOfCalculatedOffsetGroupPosition, groupPosition);
|
||||
|
||||
// requires notifyItemRangeRemoved()
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean expandGroup(int groupPosition) {
|
||||
if ((mCachedGroupPosInfo[groupPosition] & FLAG_EXPANDED) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int childCount = (int) (mCachedGroupPosInfo[groupPosition] & LOWER_31BIT_MASK);
|
||||
|
||||
mCachedGroupPosInfo[groupPosition] |= FLAG_EXPANDED;
|
||||
mExpandedGroupCount += 1;
|
||||
|
||||
mExpandedChildCount += childCount;
|
||||
mEndOfCalculatedOffsetGroupPosition = Math.min(mEndOfCalculatedOffsetGroupPosition, groupPosition);
|
||||
|
||||
// requires notifyItemRangeInserted()
|
||||
return true;
|
||||
}
|
||||
|
||||
public void moveGroupItem(int fromGroupPosition, int toGroupPosition) {
|
||||
if (fromGroupPosition == toGroupPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
final long tmp1 = mCachedGroupPosInfo[fromGroupPosition];
|
||||
final int tmp2 = mCachedGroupId[fromGroupPosition];
|
||||
|
||||
if (toGroupPosition < fromGroupPosition) {
|
||||
// shift to backward
|
||||
for (int i = fromGroupPosition; i > toGroupPosition; i--) {
|
||||
mCachedGroupPosInfo[i] = mCachedGroupPosInfo[i - 1];
|
||||
mCachedGroupId[i] = mCachedGroupId[i - 1];
|
||||
}
|
||||
} else {
|
||||
// shift to forward
|
||||
for (int i = fromGroupPosition; i < toGroupPosition; i++) {
|
||||
mCachedGroupPosInfo[i] = mCachedGroupPosInfo[i + 1];
|
||||
mCachedGroupId[i] = mCachedGroupId[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mCachedGroupPosInfo[toGroupPosition] = tmp1;
|
||||
mCachedGroupId[toGroupPosition] = tmp2;
|
||||
|
||||
final int minPosition = Math.min(fromGroupPosition, toGroupPosition);
|
||||
|
||||
if (minPosition > 0) {
|
||||
mEndOfCalculatedOffsetGroupPosition = Math.min(mEndOfCalculatedOffsetGroupPosition, minPosition - 1);
|
||||
} else {
|
||||
mEndOfCalculatedOffsetGroupPosition = RecyclerView.NO_POSITION;
|
||||
}
|
||||
}
|
||||
|
||||
public void moveChildItem(int fromGroupPosition, int fromChildPosition, int toGroupPosition, int toChildPosition) {
|
||||
if (fromGroupPosition == toGroupPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int fromChildCount = (int) (mCachedGroupPosInfo[fromGroupPosition] & LOWER_31BIT_MASK);
|
||||
final int toChildCount = (int) (mCachedGroupPosInfo[toGroupPosition] & LOWER_31BIT_MASK);
|
||||
|
||||
if (fromChildCount == 0) {
|
||||
throw new IllegalStateException("moveChildItem(" +
|
||||
"fromGroupPosition = " + fromGroupPosition +
|
||||
", fromChildPosition = " + fromChildPosition +
|
||||
", toGroupPosition = " + toGroupPosition +
|
||||
", toChildPosition = " + toChildPosition + ") --- may be a bug.");
|
||||
}
|
||||
|
||||
mCachedGroupPosInfo[fromGroupPosition] = (mCachedGroupPosInfo[fromGroupPosition] & (UPPER_32BIT_MASK | FLAG_EXPANDED)) | (fromChildCount - 1);
|
||||
mCachedGroupPosInfo[toGroupPosition] = (mCachedGroupPosInfo[toGroupPosition] & (UPPER_32BIT_MASK | FLAG_EXPANDED)) | (toChildCount + 1);
|
||||
|
||||
if ((mCachedGroupPosInfo[fromGroupPosition] & FLAG_EXPANDED) != 0) {
|
||||
mExpandedChildCount -= 1;
|
||||
}
|
||||
if ((mCachedGroupPosInfo[toGroupPosition] & FLAG_EXPANDED) != 0) {
|
||||
mExpandedChildCount += 1;
|
||||
}
|
||||
|
||||
final int minPosition = Math.min(fromGroupPosition, toGroupPosition);
|
||||
|
||||
if (minPosition > 0) {
|
||||
mEndOfCalculatedOffsetGroupPosition = Math.min(mEndOfCalculatedOffsetGroupPosition, minPosition - 1);
|
||||
} else {
|
||||
mEndOfCalculatedOffsetGroupPosition = RecyclerView.NO_POSITION;
|
||||
}
|
||||
}
|
||||
|
||||
public long getExpandablePosition(int flatPosition) {
|
||||
if (flatPosition == RecyclerView.NO_POSITION) {
|
||||
return ExpandableAdapterHelper.NO_EXPANDABLE_POSITION;
|
||||
}
|
||||
|
||||
final int groupCount = mGroupCount;
|
||||
|
||||
// final int startIndex = 0;
|
||||
final int startIndex = binarySearchGroupPositionByFlatPosition(mCachedGroupPosInfo, mEndOfCalculatedOffsetGroupPosition, flatPosition);
|
||||
long expandablePosition = ExpandableAdapterHelper.NO_EXPANDABLE_POSITION;
|
||||
int endOfCalculatedOffsetGroupPosition = mEndOfCalculatedOffsetGroupPosition;
|
||||
int offset = (startIndex == 0) ? 0 : (int) (mCachedGroupPosInfo[startIndex] >>> 32);
|
||||
|
||||
for (int i = startIndex; i < groupCount; i++) {
|
||||
final long t = mCachedGroupPosInfo[i];
|
||||
|
||||
// update offset info
|
||||
mCachedGroupPosInfo[i] = (((long) offset << 32) | (t & LOWER_32BIT_MASK));
|
||||
endOfCalculatedOffsetGroupPosition = i;
|
||||
|
||||
if (offset >= flatPosition) {
|
||||
// found (group item)
|
||||
expandablePosition = ExpandableAdapterHelper.getPackedPositionForGroup(i);
|
||||
break;
|
||||
} else {
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
if ((t & FLAG_EXPANDED) != 0) {
|
||||
final int childCount = (int) (t & LOWER_31BIT_MASK);
|
||||
|
||||
if ((childCount > 0) && (offset + childCount - 1) >= flatPosition) {
|
||||
// found (child item)
|
||||
expandablePosition = ExpandableAdapterHelper.getPackedPositionForChild(i, (flatPosition - offset));
|
||||
break;
|
||||
} else {
|
||||
offset += childCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mEndOfCalculatedOffsetGroupPosition = Math.max(mEndOfCalculatedOffsetGroupPosition, endOfCalculatedOffsetGroupPosition);
|
||||
|
||||
return expandablePosition;
|
||||
}
|
||||
|
||||
public int getFlatPosition(long packedPosition) {
|
||||
if (packedPosition == ExpandableAdapterHelper.NO_EXPANDABLE_POSITION) {
|
||||
return RecyclerView.NO_POSITION;
|
||||
}
|
||||
|
||||
final int groupPosition = ExpandableAdapterHelper.getPackedPositionGroup(packedPosition);
|
||||
final int childPosition = ExpandableAdapterHelper.getPackedPositionChild(packedPosition);
|
||||
final int groupCount = mGroupCount;
|
||||
|
||||
if (!(groupPosition >= 0 && groupPosition < groupCount)) {
|
||||
return RecyclerView.NO_POSITION;
|
||||
}
|
||||
|
||||
if (childPosition != RecyclerView.NO_POSITION) {
|
||||
if (!isGroupExpanded(groupPosition)) {
|
||||
return RecyclerView.NO_POSITION;
|
||||
}
|
||||
}
|
||||
|
||||
// final int startIndex = 0;
|
||||
final int startIndex = Math.max(0, Math.min(groupPosition, mEndOfCalculatedOffsetGroupPosition));
|
||||
int endOfCalculatedOffsetGroupPosition = mEndOfCalculatedOffsetGroupPosition;
|
||||
int offset = (int) (mCachedGroupPosInfo[startIndex] >>> 32);
|
||||
int flatPosition = RecyclerView.NO_POSITION;
|
||||
|
||||
for (int i = startIndex; i < groupCount; i++) {
|
||||
final long t = mCachedGroupPosInfo[i];
|
||||
|
||||
// update offset info
|
||||
mCachedGroupPosInfo[i] = (((long) offset << 32) | (t & LOWER_32BIT_MASK));
|
||||
endOfCalculatedOffsetGroupPosition = i;
|
||||
|
||||
final int childCount = (int) (t & LOWER_31BIT_MASK);
|
||||
|
||||
if (i == groupPosition) {
|
||||
if (childPosition == RecyclerView.NO_POSITION) {
|
||||
flatPosition = offset;
|
||||
} else if (childPosition < childCount) {
|
||||
flatPosition = (offset + 1) + childPosition;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
offset += 1;
|
||||
|
||||
if ((t & FLAG_EXPANDED) != 0) {
|
||||
offset += childCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mEndOfCalculatedOffsetGroupPosition = Math.max(mEndOfCalculatedOffsetGroupPosition, endOfCalculatedOffsetGroupPosition);
|
||||
|
||||
return flatPosition;
|
||||
}
|
||||
|
||||
|
||||
private static int binarySearchGroupPositionByFlatPosition(long[] array, int endArrayPosition, int flatPosition) {
|
||||
if (endArrayPosition <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final int v1 = (int) (array[0] >>> 32);
|
||||
final int v2 = (int) (array[endArrayPosition] >>> 32);
|
||||
|
||||
if (flatPosition <= v1) {
|
||||
return 0;
|
||||
} else if (flatPosition >= v2) {
|
||||
return endArrayPosition;
|
||||
}
|
||||
|
||||
int lastS = 0;
|
||||
int s = 0;
|
||||
int e = endArrayPosition;
|
||||
|
||||
while (s < e) {
|
||||
final int mid = (s + e) >>> 1;
|
||||
final int v = (int) (array[mid] >>> 32);
|
||||
|
||||
if (v < flatPosition) {
|
||||
lastS = s;
|
||||
s = mid + 1;
|
||||
} else {
|
||||
e = mid;
|
||||
}
|
||||
}
|
||||
|
||||
return lastS;
|
||||
}
|
||||
|
||||
public void removeChildItem(int groupPosition, int childPosition) {
|
||||
removeChildItems(groupPosition, childPosition, 1);
|
||||
}
|
||||
|
||||
public void removeChildItems(int groupPosition, int childPositionStart, int count) {
|
||||
final long t = mCachedGroupPosInfo[groupPosition];
|
||||
final int curCount = (int) (t & LOWER_31BIT_MASK);
|
||||
|
||||
if (!((childPositionStart >= 0) && ((childPositionStart + count) <= curCount))) {
|
||||
throw new IllegalStateException(
|
||||
"Invalid child position " +
|
||||
"removeChildItems(groupPosition = " + groupPosition + ", childPosition = " + childPositionStart + ", count = " + count + ")");
|
||||
}
|
||||
|
||||
if ((t & FLAG_EXPANDED) != 0) {
|
||||
mExpandedChildCount -= count;
|
||||
}
|
||||
|
||||
mCachedGroupPosInfo[groupPosition] = (t & (UPPER_32BIT_MASK | FLAG_EXPANDED)) | (curCount - count);
|
||||
mEndOfCalculatedOffsetGroupPosition = Math.min(mEndOfCalculatedOffsetGroupPosition, groupPosition - 1);
|
||||
}
|
||||
|
||||
public void insertChildItem(int groupPosition, int childPosition) {
|
||||
insertChildItems(groupPosition, childPosition, 1);
|
||||
}
|
||||
|
||||
public void insertChildItems(int groupPosition, int childPositionStart, int count) {
|
||||
final long t = mCachedGroupPosInfo[groupPosition];
|
||||
final int curCount = (int) (t & LOWER_31BIT_MASK);
|
||||
|
||||
if (!((childPositionStart >= 0) && (childPositionStart <= curCount))) {
|
||||
throw new IllegalStateException(
|
||||
"Invalid child position " +
|
||||
"insertChildItems(groupPosition = " + groupPosition + ", childPositionStart = " + childPositionStart + ", count = " + count + ")");
|
||||
}
|
||||
|
||||
if ((t & FLAG_EXPANDED) != 0) {
|
||||
mExpandedChildCount += count;
|
||||
}
|
||||
|
||||
mCachedGroupPosInfo[groupPosition] = (t & (UPPER_32BIT_MASK | FLAG_EXPANDED)) | (curCount + count);
|
||||
mEndOfCalculatedOffsetGroupPosition = Math.min(mEndOfCalculatedOffsetGroupPosition, groupPosition);
|
||||
}
|
||||
|
||||
public int insertGroupItems(int groupPosition, int count) {
|
||||
if (count <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final int n = count;
|
||||
|
||||
enlargeArraysIfNeeded(mGroupCount + n, true);
|
||||
|
||||
// shift to backward
|
||||
final ExpandableItemAdapter adapter = mAdapter;
|
||||
final long[] info = mCachedGroupPosInfo;
|
||||
final int[] ids = mCachedGroupId;
|
||||
|
||||
int start = mGroupCount - 1 + n;
|
||||
int end = groupPosition - 1 + n;
|
||||
for (int i = start; i > end; i--) {
|
||||
info[i] = info[i - n];
|
||||
ids[i] = ids[i - n];
|
||||
}
|
||||
|
||||
int end2 = groupPosition + n;
|
||||
for (int i = groupPosition; i < end2; i++) {
|
||||
final long groupId = adapter.getGroupId(i);
|
||||
final int childCount = adapter.getChildCount(i);
|
||||
|
||||
info[i] = (((long) i << 32) | childCount);
|
||||
ids[i] = (int) (groupId & LOWER_32BIT_MASK);
|
||||
}
|
||||
|
||||
mGroupCount += n;
|
||||
|
||||
int calculatedOffset = (mGroupCount == 0) ? RecyclerView.NO_POSITION : (groupPosition - 1);
|
||||
mEndOfCalculatedOffsetGroupPosition = Math.min(mEndOfCalculatedOffsetGroupPosition, calculatedOffset);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
public int insertGroupItem(int groupPosition) {
|
||||
return insertGroupItems(groupPosition, 1);
|
||||
}
|
||||
|
||||
public int removeGroupItems(int groupPosition, int count) {
|
||||
if (count <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final int n = count;
|
||||
int removedVisibleItemCount = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
final long t = mCachedGroupPosInfo[groupPosition + i];
|
||||
|
||||
if ((t & FLAG_EXPANDED) != 0) {
|
||||
int visibleChildCount = (int) (t & LOWER_31BIT_MASK);
|
||||
removedVisibleItemCount += visibleChildCount;
|
||||
mExpandedChildCount -= visibleChildCount;
|
||||
mExpandedGroupCount -= 1;
|
||||
}
|
||||
}
|
||||
removedVisibleItemCount += n;
|
||||
mGroupCount -= n;
|
||||
|
||||
// shift to forward
|
||||
for (int i = groupPosition; i < mGroupCount; i++) {
|
||||
mCachedGroupPosInfo[i] = mCachedGroupPosInfo[i + n];
|
||||
mCachedGroupId[i] = mCachedGroupId[i + n];
|
||||
}
|
||||
|
||||
int calculatedOffset = (mGroupCount == 0) ? RecyclerView.NO_POSITION : (groupPosition - 1);
|
||||
mEndOfCalculatedOffsetGroupPosition = Math.min(mEndOfCalculatedOffsetGroupPosition, calculatedOffset);
|
||||
|
||||
return removedVisibleItemCount;
|
||||
}
|
||||
|
||||
public int removeGroupItem(int groupPosition) {
|
||||
return removeGroupItems(groupPosition, 1);
|
||||
}
|
||||
|
||||
|
||||
private void enlargeArraysIfNeeded(int size, boolean preserveData) {
|
||||
int allocSize = (size + (2 * ALLOCATE_UNIT - 1)) & ~(ALLOCATE_UNIT - 1);
|
||||
|
||||
long[] curInfo = mCachedGroupPosInfo;
|
||||
int[] curId = mCachedGroupId;
|
||||
long[] newInfo = curInfo;
|
||||
int[] newId = curId;
|
||||
|
||||
if (curInfo == null || curInfo.length < size) {
|
||||
newInfo = new long[allocSize];
|
||||
}
|
||||
if (curId == null || curId.length < size) {
|
||||
newId = new int[allocSize];
|
||||
}
|
||||
|
||||
if (preserveData) {
|
||||
if (curInfo != null && curInfo != newInfo) {
|
||||
System.arraycopy(curInfo, 0, newInfo, 0, curInfo.length);
|
||||
}
|
||||
if (curId != null && curId != newId) {
|
||||
System.arraycopy(curId, 0, newId, 0, curId.length);
|
||||
}
|
||||
}
|
||||
|
||||
mCachedGroupPosInfo = newInfo;
|
||||
mCachedGroupId = newId;
|
||||
}
|
||||
}
|
||||
+912
@@ -0,0 +1,912 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.expandable;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.BaseWrapperAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
|
||||
|
||||
class ExpandableRecyclerViewWrapperAdapter
|
||||
extends BaseWrapperAdapter<RecyclerView.ViewHolder>
|
||||
implements DraggableItemAdapter<RecyclerView.ViewHolder>,
|
||||
SwipeableItemAdapter<RecyclerView.ViewHolder> {
|
||||
private static final String TAG = "ARVExpandableWrapper";
|
||||
|
||||
private static final int VIEW_TYPE_FLAG_IS_GROUP = ExpandableAdapterHelper.VIEW_TYPE_FLAG_IS_GROUP;
|
||||
|
||||
private static final int STATE_FLAG_INITIAL_VALUE = -1;
|
||||
|
||||
private ExpandableItemAdapter mExpandableItemAdapter;
|
||||
private RecyclerViewExpandableItemManager mExpandableListManager;
|
||||
private ExpandablePositionTranslator mPositionTranslator;
|
||||
private int mDraggingItemGroupRangeStart = RecyclerView.NO_POSITION;
|
||||
private int mDraggingItemGroupRangeEnd = RecyclerView.NO_POSITION;
|
||||
private int mDraggingItemChildRangeStart = RecyclerView.NO_POSITION;
|
||||
private int mDraggingItemChildRangeEnd = RecyclerView.NO_POSITION;
|
||||
|
||||
private RecyclerViewExpandableItemManager.OnGroupExpandListener mOnGroupExpandListener;
|
||||
private RecyclerViewExpandableItemManager.OnGroupCollapseListener mOnGroupCollapseListener;
|
||||
|
||||
public ExpandableRecyclerViewWrapperAdapter(RecyclerViewExpandableItemManager manager, RecyclerView.Adapter<RecyclerView.ViewHolder> adapter, int[] expandedItemsSavedState) {
|
||||
super(adapter);
|
||||
|
||||
mExpandableItemAdapter = getExpandableItemAdapter(adapter);
|
||||
if (mExpandableItemAdapter == null) {
|
||||
throw new IllegalArgumentException("adapter does not implement RecyclerViewExpandableListManager");
|
||||
}
|
||||
|
||||
if (manager == null) {
|
||||
throw new IllegalArgumentException("manager cannot be null");
|
||||
}
|
||||
|
||||
mExpandableListManager = manager;
|
||||
|
||||
mPositionTranslator = new ExpandablePositionTranslator();
|
||||
mPositionTranslator.build(mExpandableItemAdapter);
|
||||
|
||||
if (expandedItemsSavedState != null) {
|
||||
// NOTE: do not call hook routines and listener methods
|
||||
mPositionTranslator.restoreExpandedGroupItems(expandedItemsSavedState, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRelease() {
|
||||
super.onRelease();
|
||||
|
||||
mExpandableItemAdapter = null;
|
||||
mExpandableListManager = null;
|
||||
mOnGroupExpandListener = null;
|
||||
mOnGroupCollapseListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mPositionTranslator.getItemCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (mExpandableItemAdapter == null) {
|
||||
return RecyclerView.NO_ID;
|
||||
}
|
||||
|
||||
final long expandablePosition = mPositionTranslator.getExpandablePosition(position);
|
||||
final int groupPosition = ExpandableAdapterHelper.getPackedPositionGroup(expandablePosition);
|
||||
final int childPosition = ExpandableAdapterHelper.getPackedPositionChild(expandablePosition);
|
||||
|
||||
if (childPosition == RecyclerView.NO_POSITION) {
|
||||
final long groupId = mExpandableItemAdapter.getGroupId(groupPosition);
|
||||
return ExpandableAdapterHelper.getCombinedGroupId(groupId);
|
||||
} else {
|
||||
final long groupId = mExpandableItemAdapter.getGroupId(groupPosition);
|
||||
final long childId = mExpandableItemAdapter.getChildId(groupPosition, childPosition);
|
||||
return ExpandableAdapterHelper.getCombinedChildId(groupId, childId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (mExpandableItemAdapter == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final long expandablePosition = mPositionTranslator.getExpandablePosition(position);
|
||||
final int groupPosition = ExpandableAdapterHelper.getPackedPositionGroup(expandablePosition);
|
||||
final int childPosition = ExpandableAdapterHelper.getPackedPositionChild(expandablePosition);
|
||||
|
||||
final int type;
|
||||
|
||||
if (childPosition == RecyclerView.NO_POSITION) {
|
||||
type = mExpandableItemAdapter.getGroupItemViewType(groupPosition);
|
||||
} else {
|
||||
type = mExpandableItemAdapter.getChildItemViewType(groupPosition, childPosition);
|
||||
}
|
||||
|
||||
if ((type & VIEW_TYPE_FLAG_IS_GROUP) != 0) {
|
||||
throw new IllegalStateException("Illegal view type (type = " + Integer.toHexString(type) + ")");
|
||||
}
|
||||
|
||||
return (childPosition == RecyclerView.NO_POSITION) ? (type | VIEW_TYPE_FLAG_IS_GROUP) : (type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
if (mExpandableItemAdapter == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int maskedViewType = (viewType & (~VIEW_TYPE_FLAG_IS_GROUP));
|
||||
|
||||
final RecyclerView.ViewHolder holder;
|
||||
|
||||
if ((viewType & VIEW_TYPE_FLAG_IS_GROUP) != 0) {
|
||||
holder = mExpandableItemAdapter.onCreateGroupViewHolder(parent, maskedViewType);
|
||||
} else {
|
||||
holder = mExpandableItemAdapter.onCreateChildViewHolder(parent, maskedViewType);
|
||||
}
|
||||
|
||||
if (holder instanceof ExpandableItemViewHolder) {
|
||||
((ExpandableItemViewHolder) holder).setExpandStateFlags(STATE_FLAG_INITIAL_VALUE);
|
||||
}
|
||||
|
||||
return holder;
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||
if (mExpandableItemAdapter == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final long expandablePosition = mPositionTranslator.getExpandablePosition(position);
|
||||
final int groupPosition = ExpandableAdapterHelper.getPackedPositionGroup(expandablePosition);
|
||||
final int childPosition = ExpandableAdapterHelper.getPackedPositionChild(expandablePosition);
|
||||
final int viewType = (holder.getItemViewType() & (~VIEW_TYPE_FLAG_IS_GROUP));
|
||||
|
||||
// update flags
|
||||
int flags = 0;
|
||||
|
||||
if (childPosition == RecyclerView.NO_POSITION) {
|
||||
flags |= RecyclerViewExpandableItemManager.STATE_FLAG_IS_GROUP;
|
||||
} else {
|
||||
flags |= RecyclerViewExpandableItemManager.STATE_FLAG_IS_CHILD;
|
||||
}
|
||||
|
||||
if (mPositionTranslator.isGroupExpanded(groupPosition)) {
|
||||
flags |= RecyclerViewExpandableItemManager.STATE_FLAG_IS_EXPANDED;
|
||||
}
|
||||
|
||||
safeUpdateExpandStateFlags(holder, flags);
|
||||
|
||||
correctItemDragStateFlags(holder, groupPosition, childPosition);
|
||||
|
||||
if (childPosition == RecyclerView.NO_POSITION) {
|
||||
mExpandableItemAdapter.onBindGroupViewHolder(holder, groupPosition, viewType);
|
||||
} else {
|
||||
mExpandableItemAdapter.onBindChildViewHolder(holder, groupPosition, childPosition, viewType);
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuildPositionTranslator() {
|
||||
if (mPositionTranslator != null) {
|
||||
int [] savedState = mPositionTranslator.getSavedStateArray();
|
||||
mPositionTranslator.build(mExpandableItemAdapter);
|
||||
|
||||
// NOTE: do not call hook routines and listener methods
|
||||
mPositionTranslator.restoreExpandedGroupItems(savedState, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterChanged() {
|
||||
rebuildPositionTranslator();
|
||||
super.onHandleWrappedAdapterChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterItemRangeChanged(int positionStart, int itemCount) {
|
||||
super.onHandleWrappedAdapterItemRangeChanged(positionStart, itemCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterItemRangeInserted(int positionStart, int itemCount) {
|
||||
rebuildPositionTranslator();
|
||||
super.onHandleWrappedAdapterItemRangeInserted(positionStart, itemCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterItemRangeRemoved(int positionStart, int itemCount) {
|
||||
if (itemCount == 1) {
|
||||
final long expandablePosition = mPositionTranslator.getExpandablePosition(positionStart);
|
||||
final int groupPosition = ExpandableAdapterHelper.getPackedPositionGroup(expandablePosition);
|
||||
final int childPosition = ExpandableAdapterHelper.getPackedPositionChild(expandablePosition);
|
||||
|
||||
if (childPosition == RecyclerView.NO_POSITION) {
|
||||
mPositionTranslator.removeGroupItem(groupPosition);
|
||||
} else {
|
||||
mPositionTranslator.removeChildItem(groupPosition, childPosition);
|
||||
}
|
||||
} else {
|
||||
rebuildPositionTranslator();
|
||||
}
|
||||
|
||||
super.onHandleWrappedAdapterItemRangeRemoved(positionStart, itemCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||
rebuildPositionTranslator();
|
||||
super.onHandleWrappedAdapterRangeMoved(fromPosition, toPosition, itemCount);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean onCheckCanStartDrag(RecyclerView.ViewHolder holder, int position, int x, int y) {
|
||||
if (!(mExpandableItemAdapter instanceof ExpandableDraggableItemAdapter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ExpandableDraggableItemAdapter adapter = (ExpandableDraggableItemAdapter) mExpandableItemAdapter;
|
||||
|
||||
final int flatPosition = position;
|
||||
final long expandablePosition = mPositionTranslator.getExpandablePosition(flatPosition);
|
||||
final int groupPosition = ExpandableAdapterHelper.getPackedPositionGroup(expandablePosition);
|
||||
final int childPosition = ExpandableAdapterHelper.getPackedPositionChild(expandablePosition);
|
||||
|
||||
boolean canStart;
|
||||
if (childPosition == RecyclerView.NO_POSITION) {
|
||||
canStart = adapter.onCheckGroupCanStartDrag(holder, groupPosition, x, y);
|
||||
} else {
|
||||
canStart = adapter.onCheckChildCanStartDrag(holder, groupPosition, childPosition, x, y);
|
||||
}
|
||||
|
||||
mDraggingItemGroupRangeStart = RecyclerView.NO_POSITION;
|
||||
mDraggingItemGroupRangeEnd = RecyclerView.NO_POSITION;
|
||||
mDraggingItemChildRangeStart = RecyclerView.NO_POSITION;
|
||||
mDraggingItemChildRangeEnd = RecyclerView.NO_POSITION;
|
||||
|
||||
return canStart;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public ItemDraggableRange onGetItemDraggableRange(RecyclerView.ViewHolder holder, int position) {
|
||||
if (!(mExpandableItemAdapter instanceof ExpandableDraggableItemAdapter)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mExpandableItemAdapter.getGroupCount() < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ExpandableDraggableItemAdapter adapter = (ExpandableDraggableItemAdapter) mExpandableItemAdapter;
|
||||
|
||||
final int flatPosition = position;
|
||||
final long expandablePosition = mPositionTranslator.getExpandablePosition(flatPosition);
|
||||
final int groupPosition = ExpandableAdapterHelper.getPackedPositionGroup(expandablePosition);
|
||||
final int childPosition = ExpandableAdapterHelper.getPackedPositionChild(expandablePosition);
|
||||
|
||||
if (childPosition == RecyclerView.NO_POSITION) {
|
||||
// group
|
||||
final ItemDraggableRange range = adapter.onGetGroupItemDraggableRange(holder, groupPosition);
|
||||
|
||||
if (range == null) {
|
||||
final int lastGroup = Math.max(0, mExpandableItemAdapter.getGroupCount() - 1);
|
||||
final int start = 0;
|
||||
final int end = Math.max(start, mPositionTranslator.getItemCount() - mPositionTranslator.getVisibleChildCount(lastGroup) - 1);
|
||||
|
||||
return new ItemDraggableRange(start, end);
|
||||
} else if (isGroupPositionRange(range)) {
|
||||
final long startPackedGroupPosition = ExpandableAdapterHelper.getPackedPositionForGroup(range.getStart());
|
||||
final long endPackedGroupPosition = ExpandableAdapterHelper.getPackedPositionForGroup(range.getEnd());
|
||||
final int start = mPositionTranslator.getFlatPosition(startPackedGroupPosition);
|
||||
int end = mPositionTranslator.getFlatPosition(endPackedGroupPosition);
|
||||
|
||||
if (range.getEnd() > groupPosition) {
|
||||
end += mPositionTranslator.getVisibleChildCount(range.getEnd());
|
||||
}
|
||||
|
||||
mDraggingItemGroupRangeStart = range.getStart();
|
||||
mDraggingItemGroupRangeEnd = range.getEnd();
|
||||
|
||||
return new ItemDraggableRange(start, end);
|
||||
} else {
|
||||
throw new IllegalStateException("Invalid range specified: " + range);
|
||||
}
|
||||
} else {
|
||||
// child
|
||||
final ItemDraggableRange range = adapter.onGetChildItemDraggableRange(holder, groupPosition, childPosition);
|
||||
|
||||
// NOTE:
|
||||
// This method returns actual drag-sortable range, but the visual drag-sortable range would be different.
|
||||
// Thus appending the STATE_FLAG_IS_IN_RANGE flag at correctItemDragStateFlags() to avoid visual corruption.
|
||||
if (range == null) {
|
||||
final int start = 1; // 1 --- to avoid swapping with the first group item
|
||||
|
||||
return new ItemDraggableRange(start, Math.max(start, mPositionTranslator.getItemCount() - 1));
|
||||
} else if (isGroupPositionRange(range)) {
|
||||
final long startPackedGroupPosition = ExpandableAdapterHelper.getPackedPositionForGroup(range.getStart());
|
||||
final long endPackedGroupPosition = ExpandableAdapterHelper.getPackedPositionForGroup(range.getEnd());
|
||||
final int end = mPositionTranslator.getFlatPosition(endPackedGroupPosition)
|
||||
+ mPositionTranslator.getVisibleChildCount(range.getEnd());
|
||||
|
||||
int start = mPositionTranslator.getFlatPosition(startPackedGroupPosition) + 1;
|
||||
|
||||
start = Math.min(start, end);
|
||||
|
||||
mDraggingItemGroupRangeStart = range.getStart();
|
||||
mDraggingItemGroupRangeEnd = range.getEnd();
|
||||
|
||||
return new ItemDraggableRange(start, end);
|
||||
} else if (isChildPositionRange(range)) {
|
||||
final int maxChildrenPos = Math.max(mPositionTranslator.getVisibleChildCount(groupPosition) - 1, 0);
|
||||
final int childStart = Math.min(range.getStart(), maxChildrenPos);
|
||||
final int childEnd = Math.min(range.getEnd(), maxChildrenPos);
|
||||
final long startPackedChildPosition = ExpandableAdapterHelper.getPackedPositionForChild(groupPosition, childStart);
|
||||
final long endPackedChildPosition = ExpandableAdapterHelper.getPackedPositionForChild(groupPosition, childEnd);
|
||||
final int start = mPositionTranslator.getFlatPosition(startPackedChildPosition);
|
||||
final int end = mPositionTranslator.getFlatPosition(endPackedChildPosition);
|
||||
|
||||
mDraggingItemChildRangeStart = childStart;
|
||||
mDraggingItemChildRangeEnd = childEnd;
|
||||
|
||||
return new ItemDraggableRange(start, end);
|
||||
} else {
|
||||
throw new IllegalStateException("Invalid range specified: " + range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isGroupPositionRange(ItemDraggableRange range) {
|
||||
if (range.getClass().equals(GroupPositionItemDraggableRange.class)) {
|
||||
return true;
|
||||
} else if (range.getClass().equals(ItemDraggableRange.class)) {
|
||||
// NOTE: ItemDraggableRange is regarded as group position
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isChildPositionRange(ItemDraggableRange range) {
|
||||
return range.getClass().equals(ChildPositionItemDraggableRange.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoveItem(int fromPosition, int toPosition) {
|
||||
if (!(mExpandableItemAdapter instanceof ExpandableDraggableItemAdapter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mDraggingItemGroupRangeStart = RecyclerView.NO_POSITION;
|
||||
mDraggingItemGroupRangeEnd = RecyclerView.NO_POSITION;
|
||||
mDraggingItemChildRangeStart = RecyclerView.NO_POSITION;
|
||||
mDraggingItemChildRangeEnd = RecyclerView.NO_POSITION;
|
||||
|
||||
if (fromPosition == toPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ExpandableDraggableItemAdapter adapter = (ExpandableDraggableItemAdapter) mExpandableItemAdapter;
|
||||
|
||||
final long expandableFromPosition = mPositionTranslator.getExpandablePosition(fromPosition);
|
||||
final int fromGroupPosition = ExpandableAdapterHelper.getPackedPositionGroup(expandableFromPosition);
|
||||
final int fromChildPosition = ExpandableAdapterHelper.getPackedPositionChild(expandableFromPosition);
|
||||
|
||||
final long expandableToPosition = mPositionTranslator.getExpandablePosition(toPosition);
|
||||
final int toGroupPosition = ExpandableAdapterHelper.getPackedPositionGroup(expandableToPosition);
|
||||
final int toChildPosition = ExpandableAdapterHelper.getPackedPositionChild(expandableToPosition);
|
||||
|
||||
final boolean fromIsGroup = (fromChildPosition == RecyclerView.NO_POSITION);
|
||||
final boolean toIsGroup = (toChildPosition == RecyclerView.NO_POSITION);
|
||||
|
||||
int actualToFlatPosition = fromPosition;
|
||||
|
||||
if (fromIsGroup && toIsGroup) {
|
||||
adapter.onMoveGroupItem(fromGroupPosition, toGroupPosition);
|
||||
mPositionTranslator.moveGroupItem(fromGroupPosition, toGroupPosition);
|
||||
actualToFlatPosition = toPosition;
|
||||
} else if (!fromIsGroup && !toIsGroup) {
|
||||
int modToChildPosition;
|
||||
|
||||
// correct child position
|
||||
if (fromGroupPosition == toGroupPosition) {
|
||||
modToChildPosition = toChildPosition;
|
||||
} else {
|
||||
if (fromPosition < toPosition) {
|
||||
modToChildPosition = toChildPosition + 1;
|
||||
} else {
|
||||
modToChildPosition = toChildPosition;
|
||||
}
|
||||
}
|
||||
|
||||
actualToFlatPosition = mPositionTranslator.getFlatPosition(
|
||||
ExpandableAdapterHelper.getPackedPositionForChild(fromGroupPosition, modToChildPosition));
|
||||
|
||||
adapter.onMoveChildItem(fromGroupPosition, fromChildPosition, toGroupPosition, modToChildPosition);
|
||||
mPositionTranslator.moveChildItem(fromGroupPosition, fromChildPosition, toGroupPosition, modToChildPosition);
|
||||
} else if (!fromIsGroup /*&& toIsGroup NOTE: toIsGroup is always true here*/) {
|
||||
int modToGroupPosition;
|
||||
int modToChildPosition;
|
||||
|
||||
if (toPosition < fromPosition) {
|
||||
if (toGroupPosition == 0) {
|
||||
// insert at the top
|
||||
modToGroupPosition = toGroupPosition;
|
||||
modToChildPosition = 0;
|
||||
} else {
|
||||
// insert at the end
|
||||
modToGroupPosition = toGroupPosition - 1;
|
||||
modToChildPosition = mPositionTranslator.getChildCount(modToGroupPosition);
|
||||
}
|
||||
} else {
|
||||
if (mPositionTranslator.isGroupExpanded(toGroupPosition)) {
|
||||
// insert at the top
|
||||
modToGroupPosition = toGroupPosition;
|
||||
modToChildPosition = 0;
|
||||
} else {
|
||||
// insert at the end
|
||||
modToGroupPosition = toGroupPosition;
|
||||
modToChildPosition = mPositionTranslator.getChildCount(modToGroupPosition);
|
||||
}
|
||||
}
|
||||
|
||||
if (fromGroupPosition == modToGroupPosition) {
|
||||
final int lastIndex = Math.max(0, mPositionTranslator.getChildCount(modToGroupPosition) - 1);
|
||||
modToChildPosition = Math.min(modToChildPosition, lastIndex);
|
||||
}
|
||||
|
||||
if (!((fromGroupPosition == modToGroupPosition) && (fromChildPosition == modToChildPosition))) {
|
||||
if (mPositionTranslator.isGroupExpanded(toGroupPosition)) {
|
||||
actualToFlatPosition = toPosition;
|
||||
} else {
|
||||
actualToFlatPosition = RecyclerView.NO_POSITION;
|
||||
}
|
||||
|
||||
adapter.onMoveChildItem(fromGroupPosition, fromChildPosition, modToGroupPosition, modToChildPosition);
|
||||
mPositionTranslator.moveChildItem(fromGroupPosition, fromChildPosition, modToGroupPosition, modToChildPosition);
|
||||
}
|
||||
} else { // if (fromIsGroup && !toIsGroup)
|
||||
if (fromGroupPosition != toGroupPosition) {
|
||||
actualToFlatPosition = mPositionTranslator.getFlatPosition(ExpandableAdapterHelper.getPackedPositionForGroup(toGroupPosition));
|
||||
|
||||
adapter.onMoveGroupItem(fromGroupPosition, toGroupPosition);
|
||||
mPositionTranslator.moveGroupItem(fromGroupPosition, toGroupPosition);
|
||||
}
|
||||
}
|
||||
|
||||
if (actualToFlatPosition != fromPosition) {
|
||||
if (actualToFlatPosition != RecyclerView.NO_POSITION) {
|
||||
notifyItemMoved(fromPosition, actualToFlatPosition);
|
||||
} else {
|
||||
notifyItemRemoved(fromPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int onGetSwipeReactionType(RecyclerView.ViewHolder holder, int position, int x, int y) {
|
||||
if (!(mExpandableItemAdapter instanceof ExpandableSwipeableItemAdapter)) {
|
||||
return RecyclerViewSwipeManager.REACTION_CAN_NOT_SWIPE_BOTH;
|
||||
}
|
||||
|
||||
final ExpandableSwipeableItemAdapter adapter = (ExpandableSwipeableItemAdapter) mExpandableItemAdapter;
|
||||
|
||||
final int flatPosition = position;
|
||||
final long expandablePosition = mPositionTranslator.getExpandablePosition(flatPosition);
|
||||
final int groupPosition = ExpandableAdapterHelper.getPackedPositionGroup(expandablePosition);
|
||||
final int childPosition = ExpandableAdapterHelper.getPackedPositionChild(expandablePosition);
|
||||
|
||||
if (childPosition == RecyclerView.NO_POSITION) {
|
||||
return adapter.onGetGroupItemSwipeReactionType(holder, groupPosition, x, y);
|
||||
} else {
|
||||
return adapter.onGetChildItemSwipeReactionType(holder, groupPosition, childPosition, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onSetSwipeBackground(RecyclerView.ViewHolder holder, int position, int type) {
|
||||
if (!(mExpandableItemAdapter instanceof ExpandableSwipeableItemAdapter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ExpandableSwipeableItemAdapter adapter = (ExpandableSwipeableItemAdapter) mExpandableItemAdapter;
|
||||
|
||||
final int flatPosition = position;
|
||||
final long expandablePosition = mPositionTranslator.getExpandablePosition(flatPosition);
|
||||
final int groupPosition = ExpandableAdapterHelper.getPackedPositionGroup(expandablePosition);
|
||||
final int childPosition = ExpandableAdapterHelper.getPackedPositionChild(expandablePosition);
|
||||
|
||||
if (childPosition == RecyclerView.NO_POSITION) {
|
||||
adapter.onSetGroupItemSwipeBackground(holder, groupPosition, type);
|
||||
} else {
|
||||
adapter.onSetChildItemSwipeBackground(holder, groupPosition, childPosition, type);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int onSwipeItem(RecyclerView.ViewHolder holder, int position, int result) {
|
||||
if (!(mExpandableItemAdapter instanceof ExpandableSwipeableItemAdapter)) {
|
||||
return RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_DEFAULT;
|
||||
}
|
||||
|
||||
final ExpandableSwipeableItemAdapter adapter = (ExpandableSwipeableItemAdapter) mExpandableItemAdapter;
|
||||
|
||||
final int flatPosition = position;
|
||||
final long expandablePosition = mPositionTranslator.getExpandablePosition(flatPosition);
|
||||
final int groupPosition = ExpandableAdapterHelper.getPackedPositionGroup(expandablePosition);
|
||||
final int childPosition = ExpandableAdapterHelper.getPackedPositionChild(expandablePosition);
|
||||
|
||||
if (childPosition == RecyclerView.NO_POSITION) {
|
||||
return adapter.onSwipeGroupItem(holder, groupPosition, result);
|
||||
} else {
|
||||
return adapter.onSwipeChildItem(holder, groupPosition, childPosition, result);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onPerformAfterSwipeReaction(RecyclerView.ViewHolder holder, int position, int result, int reaction) {
|
||||
if (!(mExpandableItemAdapter instanceof ExpandableSwipeableItemAdapter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ExpandableSwipeableItemAdapter adapter = (ExpandableSwipeableItemAdapter) mExpandableItemAdapter;
|
||||
|
||||
final int flatPosition = position;
|
||||
final long expandablePosition = mPositionTranslator.getExpandablePosition(flatPosition);
|
||||
final int groupPosition = ExpandableAdapterHelper.getPackedPositionGroup(expandablePosition);
|
||||
final int childPosition = ExpandableAdapterHelper.getPackedPositionChild(expandablePosition);
|
||||
|
||||
if (childPosition == RecyclerView.NO_POSITION) {
|
||||
adapter.onPerformAfterSwipeGroupReaction(holder, groupPosition, result, reaction);
|
||||
} else {
|
||||
adapter.onPerformAfterSwipeChildReaction(holder, groupPosition, childPosition, result, reaction);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This method is called from RecyclerViewExpandableItemManager
|
||||
/*package*/
|
||||
@SuppressWarnings("unchecked")
|
||||
boolean onTapItem(RecyclerView.ViewHolder holder, int position, int x, int y) {
|
||||
if (mExpandableItemAdapter == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int flatPosition = position;
|
||||
final long expandablePosition = mPositionTranslator.getExpandablePosition(flatPosition);
|
||||
final int groupPosition = ExpandableAdapterHelper.getPackedPositionGroup(expandablePosition);
|
||||
final int childPosition = ExpandableAdapterHelper.getPackedPositionChild(expandablePosition);
|
||||
|
||||
if (childPosition != RecyclerView.NO_POSITION) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean expand = !(mPositionTranslator.isGroupExpanded(groupPosition));
|
||||
|
||||
boolean result = mExpandableItemAdapter.onCheckCanExpandOrCollapseGroup(holder, groupPosition, x, y, expand);
|
||||
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expand) {
|
||||
expandGroup(groupPosition, true);
|
||||
} else {
|
||||
collapseGroup(groupPosition, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*package*/ boolean collapseGroup(int groupPosition, boolean fromUser) {
|
||||
if (!mPositionTranslator.isGroupExpanded(groupPosition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// call hook method
|
||||
if (!mExpandableItemAdapter.onHookGroupCollapse(groupPosition, fromUser)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mPositionTranslator.collapseGroup(groupPosition)) {
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForGroup(groupPosition);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
final int childCount = mPositionTranslator.getChildCount(groupPosition);
|
||||
|
||||
notifyItemRangeRemoved(flatPosition + 1, childCount);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForGroup(groupPosition);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
|
||||
notifyItemChanged(flatPosition);
|
||||
}
|
||||
|
||||
// raise onGroupCollapse() event
|
||||
if (mOnGroupCollapseListener != null) {
|
||||
mOnGroupCollapseListener.onGroupCollapse(groupPosition, fromUser);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*package*/ boolean expandGroup(int groupPosition, boolean fromUser) {
|
||||
if (mPositionTranslator.isGroupExpanded(groupPosition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// call hook method
|
||||
if (!mExpandableItemAdapter.onHookGroupExpand(groupPosition, fromUser)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mPositionTranslator.expandGroup(groupPosition)) {
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForGroup(groupPosition);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
final int childCount = mPositionTranslator.getChildCount(groupPosition);
|
||||
|
||||
notifyItemRangeInserted(flatPosition + 1, childCount);
|
||||
}
|
||||
|
||||
{
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForGroup(groupPosition);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
|
||||
notifyItemChanged(flatPosition);
|
||||
}
|
||||
|
||||
// raise onGroupExpand() event
|
||||
if (mOnGroupExpandListener != null) {
|
||||
mOnGroupExpandListener.onGroupExpand(groupPosition, fromUser);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*package*/ boolean isGroupExpanded(int groupPosition) {
|
||||
return mPositionTranslator.isGroupExpanded(groupPosition);
|
||||
}
|
||||
|
||||
/*package*/ long getExpandablePosition(int flatPosition) {
|
||||
return mPositionTranslator.getExpandablePosition(flatPosition);
|
||||
}
|
||||
|
||||
/*package*/ int getFlatPosition(long packedPosition) {
|
||||
return mPositionTranslator.getFlatPosition(packedPosition);
|
||||
}
|
||||
|
||||
/*package*/ int[] getExpandedItemsSavedStateArray() {
|
||||
if (mPositionTranslator != null) {
|
||||
return mPositionTranslator.getSavedStateArray();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void setOnGroupExpandListener(RecyclerViewExpandableItemManager.OnGroupExpandListener listener) {
|
||||
mOnGroupExpandListener = listener;
|
||||
}
|
||||
|
||||
/*package*/ void setOnGroupCollapseListener(RecyclerViewExpandableItemManager.OnGroupCollapseListener listener) {
|
||||
mOnGroupCollapseListener = listener;
|
||||
}
|
||||
|
||||
/*package*/ void restoreState(int[] adapterSavedState, boolean callHook, boolean callListeners) {
|
||||
mPositionTranslator.restoreExpandedGroupItems(
|
||||
adapterSavedState,
|
||||
(callHook ? mExpandableItemAdapter : null),
|
||||
(callListeners ? mOnGroupExpandListener : null),
|
||||
(callListeners ? mOnGroupCollapseListener : null));
|
||||
}
|
||||
|
||||
/*package*/ void notifyGroupItemChanged(int groupPosition) {
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForGroup(groupPosition);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
|
||||
if (flatPosition != RecyclerView.NO_POSITION) {
|
||||
notifyItemChanged(flatPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void notifyGroupAndChildrenItemsChanged(int groupPosition) {
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForGroup(groupPosition);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
final int visibleChildCount = mPositionTranslator.getVisibleChildCount(groupPosition);
|
||||
|
||||
if (flatPosition != RecyclerView.NO_POSITION) {
|
||||
notifyItemRangeChanged(flatPosition, 1 + visibleChildCount);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void notifyChildrenOfGroupItemChanged(int groupPosition) {
|
||||
final int visibleChildCount = mPositionTranslator.getVisibleChildCount(groupPosition);
|
||||
|
||||
// notify if the group is expanded
|
||||
if (visibleChildCount > 0) {
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForChild(groupPosition, 0);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
|
||||
if (flatPosition != RecyclerView.NO_POSITION) {
|
||||
notifyItemRangeChanged(flatPosition, visibleChildCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void notifyChildItemChanged(int groupPosition, int childPosition) {
|
||||
notifyChildItemRangeChanged(groupPosition, childPosition, 1);
|
||||
}
|
||||
|
||||
/*package*/ void notifyChildItemRangeChanged(int groupPosition, int childPositionStart, int itemCount) {
|
||||
final int visibleChildCount = mPositionTranslator.getVisibleChildCount(groupPosition);
|
||||
|
||||
// notify if the group is expanded
|
||||
if ((visibleChildCount > 0) && (childPositionStart < visibleChildCount)) {
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForChild(groupPosition, 0);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
|
||||
if (flatPosition != RecyclerView.NO_POSITION) {
|
||||
final int startPosition = flatPosition + childPositionStart;
|
||||
final int count = Math.min(itemCount, (visibleChildCount - childPositionStart));
|
||||
|
||||
notifyItemRangeChanged(startPosition, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void notifyChildItemInserted(int groupPosition, int childPosition) {
|
||||
mPositionTranslator.insertChildItem(groupPosition, childPosition);
|
||||
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForChild(groupPosition, childPosition);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
|
||||
if (flatPosition != RecyclerView.NO_POSITION) {
|
||||
notifyItemInserted(flatPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void notifyChildItemRangeInserted(int groupPosition, int childPositionStart, int itemCount) {
|
||||
mPositionTranslator.insertChildItems(groupPosition, childPositionStart, itemCount);
|
||||
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForChild(groupPosition, childPositionStart);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
|
||||
if (flatPosition != RecyclerView.NO_POSITION) {
|
||||
notifyItemRangeInserted(flatPosition, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void notifyChildItemRemoved(int groupPosition, int childPosition) {
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForChild(groupPosition, childPosition);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
|
||||
mPositionTranslator.removeChildItem(groupPosition, childPosition);
|
||||
|
||||
if (flatPosition != RecyclerView.NO_POSITION) {
|
||||
notifyItemRemoved(flatPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void notifyChildItemRangeRemoved(int groupPosition, int childPositionStart, int itemCount) {
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForChild(groupPosition, childPositionStart);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
|
||||
mPositionTranslator.removeChildItems(groupPosition, childPositionStart, itemCount);
|
||||
|
||||
if (flatPosition != RecyclerView.NO_POSITION) {
|
||||
notifyItemRangeRemoved(flatPosition, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void notifyGroupItemInserted(int groupPosition) {
|
||||
int insertedCount = mPositionTranslator.insertGroupItem(groupPosition);
|
||||
if (insertedCount > 0) {
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForGroup(groupPosition);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
|
||||
notifyItemInserted(flatPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void notifyGroupItemRangeInserted(int groupPositionStart, int count) {
|
||||
int insertedCount = mPositionTranslator.insertGroupItems(groupPositionStart, count);
|
||||
if (insertedCount > 0) {
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForGroup(groupPositionStart);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
|
||||
notifyItemRangeInserted(flatPosition, insertedCount);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void notifyGroupItemRemoved(int groupPosition) {
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForGroup(groupPosition);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
|
||||
int removedCount = mPositionTranslator.removeGroupItem(groupPosition);
|
||||
if (removedCount > 0) {
|
||||
notifyItemRangeRemoved(flatPosition, removedCount);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void notifyGroupItemRangeRemoved(int groupPositionStart, int count) {
|
||||
final long packedPosition = ExpandableAdapterHelper.getPackedPositionForGroup(groupPositionStart);
|
||||
final int flatPosition = mPositionTranslator.getFlatPosition(packedPosition);
|
||||
|
||||
int removedCount = mPositionTranslator.removeGroupItems(groupPositionStart, count);
|
||||
if (removedCount > 0) {
|
||||
notifyItemRangeRemoved(flatPosition, removedCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static ExpandableItemAdapter getExpandableItemAdapter(RecyclerView.Adapter adapter) {
|
||||
return WrapperAdapterUtils.findWrappedAdapter(adapter, ExpandableItemAdapter.class);
|
||||
}
|
||||
|
||||
private static void safeUpdateExpandStateFlags(RecyclerView.ViewHolder holder, int flags) {
|
||||
if (!(holder instanceof ExpandableItemViewHolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ExpandableItemViewHolder holder2 = (ExpandableItemViewHolder) holder;
|
||||
|
||||
final int curFlags = holder2.getExpandStateFlags();
|
||||
final int mask = ~RecyclerViewExpandableItemManager.STATE_FLAG_IS_UPDATED;
|
||||
|
||||
// append UPDATED flag
|
||||
if ((curFlags == STATE_FLAG_INITIAL_VALUE) || (((curFlags ^ flags) & mask) != 0)) {
|
||||
flags |= RecyclerViewExpandableItemManager.STATE_FLAG_IS_UPDATED;
|
||||
}
|
||||
|
||||
holder2.setExpandStateFlags(flags);
|
||||
}
|
||||
|
||||
private void correctItemDragStateFlags(RecyclerView.ViewHolder holder, int groupPosition, int childPosition) {
|
||||
if (!(holder instanceof DraggableItemViewHolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final DraggableItemViewHolder holder2 = (DraggableItemViewHolder) holder;
|
||||
|
||||
final boolean groupRangeSpecified =
|
||||
(mDraggingItemGroupRangeStart != RecyclerView.NO_POSITION) &&
|
||||
(mDraggingItemGroupRangeEnd != RecyclerView.NO_POSITION);
|
||||
final boolean childRangeSpecified =
|
||||
(mDraggingItemChildRangeStart != RecyclerView.NO_POSITION) &&
|
||||
(mDraggingItemChildRangeEnd != RecyclerView.NO_POSITION);
|
||||
final boolean isInGroupRange =
|
||||
(groupPosition >= mDraggingItemGroupRangeStart) &&
|
||||
(groupPosition <= mDraggingItemGroupRangeEnd);
|
||||
final boolean isInChildRange =
|
||||
(groupPosition != RecyclerView.NO_POSITION) &&
|
||||
(childPosition >= mDraggingItemChildRangeStart) &&
|
||||
(childPosition <= mDraggingItemChildRangeEnd);
|
||||
|
||||
final int flags = holder2.getDragStateFlags();
|
||||
boolean needCorrection = false;
|
||||
|
||||
if (((flags & RecyclerViewDragDropManager.STATE_FLAG_DRAGGING) != 0) &&
|
||||
((flags & RecyclerViewDragDropManager.STATE_FLAG_IS_IN_RANGE) == 0)) {
|
||||
if (!groupRangeSpecified || isInGroupRange) {
|
||||
if (!childRangeSpecified || (childRangeSpecified && isInChildRange)) {
|
||||
needCorrection = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needCorrection) {
|
||||
holder2.setDragStateFlags(
|
||||
flags | RecyclerViewDragDropManager.STATE_FLAG_IS_IN_RANGE |
|
||||
RecyclerViewDragDropManager.STATE_FLAG_IS_UPDATED);
|
||||
}
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.expandable;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
public interface ExpandableSwipeableItemAdapter<GVH extends RecyclerView.ViewHolder, CVH extends RecyclerView.ViewHolder> {
|
||||
/**
|
||||
* Called when user is attempt to swipe the group item.
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to item user is attempt to start swiping.
|
||||
* @param groupPosition Group position.
|
||||
* @param x Touched X position. Relative from the itemView's top-left.
|
||||
* @param y Touched Y position. Relative from the itemView's top-left.
|
||||
|
||||
* @return Reaction type. Bitwise OR of these flags;
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#REACTION_CAN_NOT_SWIPE_LEFT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#REACTION_CAN_NOT_SWIPE_LEFT_WITH_RUBBER_BAND_EFFECT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#REACTION_CAN_SWIPE_LEFT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#REACTION_CAN_NOT_SWIPE_RIGHT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#REACTION_CAN_NOT_SWIPE_RIGHT_WITH_RUBBER_BAND_EFFECT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#REACTION_CAN_SWIPE_RIGHT}
|
||||
*/
|
||||
int onGetGroupItemSwipeReactionType(GVH holder, int groupPosition, int x, int y);
|
||||
|
||||
/**
|
||||
* Called when user is attempt to swipe the child item.
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to item user is attempt to start swiping.
|
||||
* @param groupPosition Group position.
|
||||
* @param childPosition Child position.
|
||||
* @param x Touched X position. Relative from the itemView's top-left.
|
||||
* @param y Touched Y position. Relative from the itemView's top-left.
|
||||
|
||||
* @return Reaction type. Bitwise OR of these flags;
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#REACTION_CAN_NOT_SWIPE_LEFT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#REACTION_CAN_NOT_SWIPE_LEFT_WITH_RUBBER_BAND_EFFECT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#REACTION_CAN_SWIPE_LEFT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#REACTION_CAN_NOT_SWIPE_RIGHT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#REACTION_CAN_NOT_SWIPE_RIGHT_WITH_RUBBER_BAND_EFFECT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#REACTION_CAN_SWIPE_RIGHT}
|
||||
*/
|
||||
int onGetChildItemSwipeReactionType(CVH holder, int groupPosition, int childPosition, int x, int y);
|
||||
|
||||
/**
|
||||
* Called when sets background of the swiping group item.
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to the swiping item.
|
||||
* @param groupPosition Group position.
|
||||
* @param type Background type. One of the
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#DRAWABLE_SWIPE_NEUTRAL_BACKGROUND},
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#DRAWABLE_SWIPE_LEFT_BACKGROUND} or
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#DRAWABLE_SWIPE_RIGHT_BACKGROUND}.
|
||||
*/
|
||||
void onSetGroupItemSwipeBackground(GVH holder, int groupPosition, int type);
|
||||
|
||||
|
||||
/**
|
||||
* Called when sets background of the swiping child item.
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to the swiping item.
|
||||
* @param groupPosition Group position.
|
||||
* @param childPosition Child position.
|
||||
* @param type Background type. One of the
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#DRAWABLE_SWIPE_NEUTRAL_BACKGROUND},
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#DRAWABLE_SWIPE_LEFT_BACKGROUND} or
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#DRAWABLE_SWIPE_RIGHT_BACKGROUND}.
|
||||
*/
|
||||
void onSetChildItemSwipeBackground(CVH holder, int groupPosition, int childPosition, int type);
|
||||
|
||||
/**
|
||||
* Called when group item is swiped.
|
||||
*
|
||||
* *Note that do not change data set and do not call notifyDataXXX() methods inside of this method.*
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to the swiped item.
|
||||
* @param groupPosition Group position.
|
||||
* @param result The result code of user's swipe operation.
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_CANCELED},
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_SWIPED_LEFT} or
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_SWIPED_RIGHT}
|
||||
*
|
||||
* @return Reaction type of after swiping.
|
||||
* One of the {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_DEFAULT},
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION} or
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_REMOVE_ITEM}.
|
||||
*/
|
||||
int onSwipeGroupItem(GVH holder, int groupPosition, int result);
|
||||
|
||||
/**
|
||||
* Called when child item is swiped.
|
||||
*
|
||||
* *Note that do not change data set and do not call notifyDataXXX() methods inside of this method.*
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to the swiped item.
|
||||
* @param groupPosition Group position.
|
||||
* @param childPosition Child position.
|
||||
* @param result The result code of user's swipe operation.
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_CANCELED},
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_SWIPED_LEFT} or
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_SWIPED_RIGHT}
|
||||
*
|
||||
* @return Reaction type of after swiping.
|
||||
* One of the {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_DEFAULT},
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION} or
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_REMOVE_ITEM}.
|
||||
*/
|
||||
int onSwipeChildItem(CVH holder, int groupPosition, int childPosition, int result);
|
||||
|
||||
/**
|
||||
* Called after {@link #onSwipeGroupItem(android.support.v7.widget.RecyclerView.ViewHolder, int, int)} method.
|
||||
*
|
||||
* You can update data set and call notifyDataXXX() methods inside of this method.
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to the swiped item.
|
||||
* @param groupPosition Group position.
|
||||
* @param result The result code of user's swipe operation.
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_CANCELED},
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_SWIPED_LEFT} or
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_SWIPED_RIGHT}
|
||||
* @param reaction Reaction type. One of the {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_DEFAULT},
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION} or
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_REMOVE_ITEM}.
|
||||
*/
|
||||
void onPerformAfterSwipeGroupReaction(GVH holder, int groupPosition, int result, int reaction);
|
||||
|
||||
|
||||
/**
|
||||
* Called after {@link #onSwipeChildItem(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int)} method.
|
||||
*
|
||||
* You can update data set and call notifyDataXXX() methods inside of this method.
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to the swiped item.
|
||||
* @param groupPosition Group position.
|
||||
* @param childPosition Child position.
|
||||
* @param result The result code of user's swipe operation.
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_CANCELED},
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_SWIPED_LEFT} or
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_SWIPED_RIGHT}
|
||||
* @param reaction Reaction type. One of the {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_DEFAULT},
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION} or
|
||||
* {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_REMOVE_ITEM}.
|
||||
*/
|
||||
void onPerformAfterSwipeChildReaction(CVH holder, int groupPosition, int childPosition, int result, int reaction);
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.expandable;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
|
||||
|
||||
public class GroupPositionItemDraggableRange extends ItemDraggableRange {
|
||||
public GroupPositionItemDraggableRange(int start, int end) {
|
||||
super(start, end);
|
||||
}
|
||||
|
||||
protected String getClassName() {
|
||||
return "GroupPositionItemDraggableRange";
|
||||
}
|
||||
}
|
||||
+820
@@ -0,0 +1,820 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.expandable;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.view.MotionEventCompat;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.CustomRecyclerViewUtils;
|
||||
|
||||
/**
|
||||
* Provides item swipe operation for {@link android.support.v7.widget.RecyclerView}
|
||||
*/
|
||||
public class RecyclerViewExpandableItemManager {
|
||||
private static final String TAG = "ARVExpandableItemMgr";
|
||||
|
||||
/**
|
||||
* Packed position version of {@link android.support.v7.widget.RecyclerView#NO_POSITION}
|
||||
*/
|
||||
public static final long NO_EXPANDABLE_POSITION = ExpandableAdapterHelper.NO_EXPANDABLE_POSITION;
|
||||
|
||||
|
||||
/**
|
||||
* State flag for the {@link ExpandableItemViewHolder#setExpandStateFlags(int)} and {@link ExpandableItemViewHolder#getExpandStateFlags()} methods.
|
||||
* Indicates that this ViewHolder is associated to group item.
|
||||
*/
|
||||
@SuppressWarnings("PointlessBitwiseExpression")
|
||||
public static final int STATE_FLAG_IS_GROUP = (1 << 0);
|
||||
|
||||
/**
|
||||
* State flag for the {@link ExpandableItemViewHolder#setExpandStateFlags(int)} and {@link ExpandableItemViewHolder#getExpandStateFlags()} methods.
|
||||
* Indicates that this ViewHolder is associated to child item.
|
||||
*/
|
||||
public static final int STATE_FLAG_IS_CHILD = (1 << 1);
|
||||
|
||||
/**
|
||||
* State flag for the {@link ExpandableItemViewHolder#setExpandStateFlags(int)} and {@link ExpandableItemViewHolder#getExpandStateFlags()} methods.
|
||||
* Indicates that this is a group item.
|
||||
*/
|
||||
public static final int STATE_FLAG_IS_EXPANDED = (1 << 2);
|
||||
|
||||
/**
|
||||
* State flag for the {@link ExpandableItemViewHolder#setExpandStateFlags(int)} and {@link ExpandableItemViewHolder#getExpandStateFlags()} methods.
|
||||
* If this flag is set, some other flags are changed and require to apply.
|
||||
*/
|
||||
public static final int STATE_FLAG_IS_UPDATED = (1 << 31);
|
||||
|
||||
|
||||
// ---
|
||||
|
||||
/**
|
||||
* Used for being notified when a group is expanded
|
||||
*/
|
||||
public interface OnGroupExpandListener {
|
||||
/**
|
||||
* Callback method to be invoked when a group in this expandable list has been expanded.
|
||||
*
|
||||
* @param groupPosition The group position that was expanded
|
||||
* @param fromUser Whether the expand request is issued by a user operation
|
||||
*/
|
||||
void onGroupExpand(int groupPosition, boolean fromUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for being notified when a group is collapsed
|
||||
*/
|
||||
public interface OnGroupCollapseListener {
|
||||
/**
|
||||
* Callback method to be invoked when a group in this expandable list has been collapsed.
|
||||
*
|
||||
* @param groupPosition The group position that was collapsed
|
||||
* @param fromUser Whether the collapse request is issued by a user operation
|
||||
*/
|
||||
void onGroupCollapse(int groupPosition, boolean fromUser);
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
private SavedState mSavedState;
|
||||
|
||||
private RecyclerView mRecyclerView;
|
||||
private ExpandableRecyclerViewWrapperAdapter mAdapter;
|
||||
private RecyclerView.OnItemTouchListener mInternalUseOnItemTouchListener;
|
||||
private OnGroupExpandListener mOnGroupExpandListener;
|
||||
private OnGroupCollapseListener mOnGroupCollapseListener;
|
||||
|
||||
private long mTouchedItemId = RecyclerView.NO_ID;
|
||||
private int mTouchSlop;
|
||||
private int mInitialTouchX;
|
||||
private int mInitialTouchY;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param savedState The saved state object which is obtained from the {@link #getSavedState()} method.
|
||||
*/
|
||||
public RecyclerViewExpandableItemManager(Parcelable savedState) {
|
||||
mInternalUseOnItemTouchListener = new RecyclerView.OnItemTouchListener() {
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
|
||||
return RecyclerViewExpandableItemManager.this.onInterceptTouchEvent(rv, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
}
|
||||
};
|
||||
|
||||
if (savedState instanceof SavedState) {
|
||||
mSavedState = (SavedState) savedState;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates this manager instance has released or not.
|
||||
*
|
||||
* @return True if this manager instance has released
|
||||
*/
|
||||
public boolean isReleased() {
|
||||
return (mInternalUseOnItemTouchListener == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches {@link android.support.v7.widget.RecyclerView} instance.
|
||||
*
|
||||
* Before calling this method, the target {@link android.support.v7.widget.RecyclerView} must set
|
||||
* the wrapped adapter instance which is returned by the
|
||||
* {@link #createWrappedAdapter(android.support.v7.widget.RecyclerView.Adapter)} method.
|
||||
*
|
||||
* @param rv The {@link android.support.v7.widget.RecyclerView} instance
|
||||
*/
|
||||
public void attachRecyclerView(RecyclerView rv) {
|
||||
if (rv == null) {
|
||||
throw new IllegalArgumentException("RecyclerView cannot be null");
|
||||
}
|
||||
|
||||
if (isReleased()) {
|
||||
throw new IllegalStateException("Accessing released object");
|
||||
}
|
||||
|
||||
if (mRecyclerView != null) {
|
||||
throw new IllegalStateException("RecyclerView instance has already been set");
|
||||
}
|
||||
|
||||
mRecyclerView = rv;
|
||||
mRecyclerView.addOnItemTouchListener(mInternalUseOnItemTouchListener);
|
||||
mTouchSlop = ViewConfiguration.get(mRecyclerView.getContext()).getScaledTouchSlop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach the {@link android.support.v7.widget.RecyclerView} instance and release internal field references.
|
||||
*
|
||||
* This method should be called in order to avoid memory leaks.
|
||||
*/
|
||||
public void release() {
|
||||
if (mRecyclerView != null && mInternalUseOnItemTouchListener != null) {
|
||||
mRecyclerView.removeOnItemTouchListener(mInternalUseOnItemTouchListener);
|
||||
}
|
||||
mInternalUseOnItemTouchListener = null;
|
||||
mOnGroupExpandListener = null;
|
||||
mOnGroupCollapseListener = null;
|
||||
mRecyclerView = null;
|
||||
mSavedState = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create wrapped adapter.
|
||||
*
|
||||
* @param adapter The target adapter.
|
||||
*
|
||||
* @return Wrapped adapter which is associated to this {@link RecyclerViewExpandableItemManager} instance.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public RecyclerView.Adapter createWrappedAdapter(RecyclerView.Adapter adapter) {
|
||||
if (mAdapter != null) {
|
||||
throw new IllegalStateException("already have a wrapped adapter");
|
||||
}
|
||||
|
||||
int [] adapterSavedState = (mSavedState != null) ? mSavedState.adapterSavedState : null;
|
||||
mSavedState = null;
|
||||
|
||||
mAdapter = new ExpandableRecyclerViewWrapperAdapter(this, adapter, adapterSavedState);
|
||||
|
||||
// move listeners to wrapper adapter
|
||||
mAdapter.setOnGroupExpandListener(mOnGroupExpandListener);
|
||||
mOnGroupExpandListener = null;
|
||||
|
||||
mAdapter.setOnGroupCollapseListener(mOnGroupCollapseListener);
|
||||
mOnGroupCollapseListener = null;
|
||||
|
||||
return mAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets saved state object in order to restore the internal state.
|
||||
*
|
||||
* Call this method in Activity/Fragment's onSavedInstance() and save to the bundle.
|
||||
*
|
||||
* @return The Parcelable object which stores information need to restore the internal states.
|
||||
*/
|
||||
public Parcelable getSavedState() {
|
||||
int [] adapterSavedState = null;
|
||||
|
||||
if (mAdapter != null) {
|
||||
adapterSavedState = mAdapter.getExpandedItemsSavedStateArray();
|
||||
}
|
||||
|
||||
return new SavedState(adapterSavedState);
|
||||
}
|
||||
|
||||
/*package*/ boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
|
||||
if (mAdapter == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int action = MotionEventCompat.getActionMasked(e);
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
handleActionDown(rv, e);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
if (handleActionUpOrCancel(rv, e)) {
|
||||
// NOTE: it requires to return false to work click effect properly (issue #44)
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleActionDown(RecyclerView rv, MotionEvent e) {
|
||||
final RecyclerView.ViewHolder holder = CustomRecyclerViewUtils.findChildViewHolderUnderWithTranslation(rv, e.getX(), e.getY());
|
||||
|
||||
mInitialTouchX = (int) (e.getX() + 0.5f);
|
||||
mInitialTouchY = (int) (e.getY() + 0.5f);
|
||||
|
||||
if (holder instanceof ExpandableItemViewHolder) {
|
||||
mTouchedItemId = holder.getItemId();
|
||||
} else {
|
||||
mTouchedItemId = RecyclerView.NO_ID;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleActionUpOrCancel(RecyclerView rv, MotionEvent e) {
|
||||
final long touchedItemId = mTouchedItemId;
|
||||
final int initialTouchX = mInitialTouchX;
|
||||
final int initialTouchY = mInitialTouchY;
|
||||
|
||||
mTouchedItemId = RecyclerView.NO_ID;
|
||||
mInitialTouchX = 0;
|
||||
mInitialTouchY = 0;
|
||||
|
||||
if (!((touchedItemId != RecyclerView.NO_ID) && (MotionEventCompat.getActionMasked(e) == MotionEvent.ACTION_UP))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int touchX = (int) (e.getX() + 0.5f);
|
||||
final int touchY = (int) (e.getY() + 0.5f);
|
||||
|
||||
final int diffX = touchX - initialTouchX;
|
||||
final int diffY = touchY - initialTouchY;
|
||||
|
||||
if (!((Math.abs(diffX) < mTouchSlop) && (Math.abs(diffY) < mTouchSlop))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final RecyclerView.ViewHolder holder = CustomRecyclerViewUtils.findChildViewHolderUnderWithTranslation(rv, e.getX(), e.getY());
|
||||
|
||||
if (!((holder != null) && (holder.getItemId() == touchedItemId))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int position = CustomRecyclerViewUtils.getSynchronizedPosition(holder);
|
||||
|
||||
if (position == RecyclerView.NO_POSITION) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final View view = holder.itemView;
|
||||
final int translateX = (int) (ViewCompat.getTranslationX(view) + 0.5f);
|
||||
final int translateY = (int) (ViewCompat.getTranslationY(view) + 0.5f);
|
||||
final int viewX = touchX - (view.getLeft() + translateX);
|
||||
final int viewY = touchY - (view.getTop() + translateY);
|
||||
|
||||
return mAdapter.onTapItem(holder, position, viewX, viewY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand a group.
|
||||
*
|
||||
* @param groupPosition The group position to be expanded
|
||||
*
|
||||
* @return True if the group was expanded, false otherwise (If the group was already expanded, this will return false)
|
||||
*/
|
||||
public boolean expandGroup(int groupPosition) {
|
||||
return (mAdapter != null) && mAdapter.expandGroup(groupPosition, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse a group.
|
||||
*
|
||||
* @param groupPosition The group position to be collapsed
|
||||
*
|
||||
* @return True if the group was collapsed, false otherwise (If the group was already collapsed, this will return false)
|
||||
*/
|
||||
public boolean collapseGroup(int groupPosition) {
|
||||
return (mAdapter != null) && mAdapter.collapseGroup(groupPosition, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a flat position (the raw position of an item in the list) to a group and/or child position
|
||||
* (represented in a packed position). Use {@link #getPackedPositionChild(long)}, {@link #getPackedPositionGroup(long)} to unpack.
|
||||
*
|
||||
* @param flatPosition The flat position to be converted
|
||||
*
|
||||
* @return The group and/or child position for the given flat position in packed position representation.
|
||||
*/
|
||||
public long getExpandablePosition(int flatPosition) {
|
||||
if (mAdapter == null) {
|
||||
return ExpandableAdapterHelper.NO_EXPANDABLE_POSITION;
|
||||
}
|
||||
return mAdapter.getExpandablePosition(flatPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a group and/or child position to a flat position.
|
||||
*
|
||||
* @param packedPosition The group and/or child position to be converted in packed position representation.
|
||||
*
|
||||
* @return The group and/or child position for the given flat position in packed position representation.
|
||||
*/
|
||||
public int getFlatPosition(long packedPosition) {
|
||||
if (mAdapter == null) {
|
||||
return RecyclerView.NO_POSITION;
|
||||
}
|
||||
return mAdapter.getFlatPosition(packedPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the child position from a packed position.
|
||||
* To get the group that this child belongs to, use {@link #getPackedPositionGroup(long)}.
|
||||
* See {@link #getPackedPositionForChild(int, int)}.
|
||||
*
|
||||
* @param packedPosition The packed position from which the child position will be returned.
|
||||
*
|
||||
* @return The child position portion of the packed position. If this does not contain a child, returns {@link android.support.v7.widget.RecyclerView#NO_POSITION}.
|
||||
*/
|
||||
public static int getPackedPositionChild(long packedPosition) {
|
||||
return ExpandableAdapterHelper.getPackedPositionChild(packedPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the packed position representation of a child position.
|
||||
*
|
||||
* In general, a packed position should be used in situations where the position given to/returned from
|
||||
* {@link RecyclerViewExpandableItemManager} method can either be a child or group.
|
||||
* The two positions are packed into a single long which can be unpacked using {@link #getPackedPositionChild(long)} and
|
||||
* {@link #getPackedPositionGroup(long)}.
|
||||
*
|
||||
* @param groupPosition The child's parent group's position
|
||||
* @param childPosition The child position within the group
|
||||
*
|
||||
* @return The packed position representation of the child (and parent group).
|
||||
*/
|
||||
public static long getPackedPositionForChild(int groupPosition, int childPosition) {
|
||||
return ExpandableAdapterHelper.getPackedPositionForChild(groupPosition, childPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the packed position representation of a group's position. See {@link #getPackedPositionForChild(int, int)}.
|
||||
*
|
||||
* @param groupPosition The child's parent group's position.
|
||||
*
|
||||
* @return The packed position representation of the group.
|
||||
*/
|
||||
public static long getPackedPositionForGroup(int groupPosition) {
|
||||
return ExpandableAdapterHelper.getPackedPositionForGroup(groupPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the group position from a packed position. See {@link #getPackedPositionForChild(int, int)}.
|
||||
*
|
||||
* @param packedPosition The packed position from which the group position will be returned.
|
||||
*
|
||||
* @return THe group position of the packed position. If this does not contain a group, returns {@link android.support.v7.widget.RecyclerView#NO_POSITION}.
|
||||
*/
|
||||
public static int getPackedPositionGroup(long packedPosition) {
|
||||
return ExpandableAdapterHelper.getPackedPositionGroup(packedPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given group is currently expanded.
|
||||
*
|
||||
* @param groupPosition The group to check
|
||||
*
|
||||
* @return Whether the group is currently expanded
|
||||
*/
|
||||
public boolean isGroupExpanded(int groupPosition) {
|
||||
return (mAdapter != null) && mAdapter.isGroupExpanded(groupPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets combined ID for child item.
|
||||
*
|
||||
* bit 0-31: Lower 32 bits of the childId
|
||||
* bit 32-62: Lower 31 bits of the groupId
|
||||
* bit 63: reserved
|
||||
*
|
||||
* @param groupId The ID of the group that contains the child.
|
||||
* @param childId The ID of the child.
|
||||
*
|
||||
* @return The unique ID of the child across all groups and children in the list
|
||||
*/
|
||||
public static long getCombinedChildId(long groupId, long childId) {
|
||||
return ExpandableAdapterHelper.getCombinedChildId(groupId, childId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets combined ID for child item.
|
||||
*
|
||||
* bit 0-31: all bits are set to 1
|
||||
* bit 32-62: Lower 31 bits of the groupId
|
||||
* bit 63: reserved
|
||||
*
|
||||
* @param groupId The ID of the group that contains the child.
|
||||
*
|
||||
* @return The unique ID of the child across all groups and children in the list
|
||||
*/
|
||||
public static long getCombinedGroupId(long groupId) {
|
||||
return ExpandableAdapterHelper.getCombinedGroupId(groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the passed view type is a group's one.
|
||||
*
|
||||
* @param rawViewType raw view type value (return value of {@link android.support.v7.widget.RecyclerView.ViewHolder#getItemViewType()})
|
||||
*
|
||||
* @return True for the a group view type, otherwise false
|
||||
*/
|
||||
public static boolean isGroupViewType(int rawViewType) {
|
||||
return ExpandableAdapterHelper.isGroupViewType(rawViewType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets group view type from a raw view type.
|
||||
*
|
||||
* @param rawViewType raw view type value (return value of {@link android.support.v7.widget.RecyclerView.ViewHolder#getItemViewType()})
|
||||
*
|
||||
* @return Group view type for the given raw view type.
|
||||
*/
|
||||
public static int getGroupViewType(int rawViewType) {
|
||||
return ExpandableAdapterHelper.getGroupViewType(rawViewType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets child view type from a raw view type.
|
||||
*
|
||||
* @param rawViewType raw view type value (return value of {@link android.support.v7.widget.RecyclerView.ViewHolder#getItemViewType()})
|
||||
*
|
||||
* @return Child view type for the given raw view type.
|
||||
*/
|
||||
public static int getChildViewType(int rawViewType) {
|
||||
return ExpandableAdapterHelper.getChildViewType(rawViewType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when an group item has been expanded.
|
||||
*
|
||||
* @param listener The callback that will be invoked.
|
||||
*/
|
||||
public void setOnGroupExpandListener(OnGroupExpandListener listener) {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.setOnGroupExpandListener(listener);
|
||||
} else {
|
||||
// pending
|
||||
mOnGroupExpandListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when an group item has been collapsed.
|
||||
*
|
||||
* @param listener The callback that will be invoked.
|
||||
*/
|
||||
public void setOnGroupCollapseListener(OnGroupCollapseListener listener) {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.setOnGroupCollapseListener(listener);
|
||||
} else {
|
||||
// pending
|
||||
mOnGroupCollapseListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore saves state. See {@link #restoreState(android.os.Parcelable, boolean, boolean)}.
|
||||
* (This method does not invoke any hook methods and listener events)
|
||||
*
|
||||
* @param savedState The saved state object
|
||||
*/
|
||||
public void restoreState(Parcelable savedState) {
|
||||
restoreState(savedState, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore saves state.
|
||||
*
|
||||
* This method is useful when the adapter can not be prepared (because data loading may takes time and processed asynchronously)
|
||||
* before creating this manager instance.
|
||||
*
|
||||
* @param savedState The saved state object
|
||||
* @param callHooks Whether to call hook routines
|
||||
* ({@link ExpandableItemAdapter#onHookGroupExpand(int, boolean)},
|
||||
* {@link ExpandableItemAdapter#onHookGroupCollapse(int, boolean)})
|
||||
* @param callListeners Whether to invoke {@link OnGroupExpandListener} and/or {@link OnGroupCollapseListener} listener events
|
||||
*/
|
||||
public void restoreState(Parcelable savedState, boolean callHooks, boolean callListeners) {
|
||||
if (savedState == null) {
|
||||
return; // do nothing
|
||||
}
|
||||
|
||||
if (!(savedState instanceof SavedState)) {
|
||||
throw new IllegalArgumentException("Illegal saved state object passed");
|
||||
}
|
||||
|
||||
if (!((mAdapter != null) && (mRecyclerView != null))) {
|
||||
throw new IllegalStateException("RecyclerView has not been attached");
|
||||
}
|
||||
|
||||
mAdapter.restoreState(((SavedState) savedState).adapterSavedState, callHooks, callListeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify any registered observers that the group item at <code>groupPosition</code> has changed.
|
||||
*
|
||||
* <p>This is an group item change event, not a structural change event. It indicates that any
|
||||
* reflection of the data at <code>groupPosition</code> is out of date and should be updated.
|
||||
* The item at <code>groupPosition</code> retains the same identity.</p>
|
||||
*
|
||||
* <p>This method does not notify for children that are contained in the specified group.
|
||||
* If children have also changed, use {@link #notifyGroupAndChildrenItemsChanged(int)} instead.</p>
|
||||
*
|
||||
* @param groupPosition Position of the group item that has changed
|
||||
*
|
||||
* @see #notifyGroupAndChildrenItemsChanged(int)
|
||||
*/
|
||||
public void notifyGroupItemChanged(int groupPosition) {
|
||||
mAdapter.notifyGroupItemChanged(groupPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify any registered observers that the group and children items at <code>groupPosition</code> have changed.
|
||||
*
|
||||
* <p>This is an group item change event, not a structural change event. It indicates that any
|
||||
* reflection of the data at <code>groupPosition</code> is out of date and should be updated.
|
||||
* The item at <code>groupPosition</code> retains the same identity.</p>
|
||||
*
|
||||
* @param groupPosition Position of the group item which contains changed children
|
||||
*
|
||||
* @see #notifyGroupItemChanged(int)
|
||||
* @see #notifyChildrenOfGroupItemChanged(int)
|
||||
*/
|
||||
public void notifyGroupAndChildrenItemsChanged(int groupPosition) {
|
||||
mAdapter.notifyGroupAndChildrenItemsChanged(groupPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify any registered observers that the children items contained in the group item at <code>groupPosition</code> have changed.
|
||||
*
|
||||
* <p>This is an group item change event, not a structural change event. It indicates that any
|
||||
* reflection of the data at <code>groupPosition</code> is out of date and should be updated.
|
||||
* The item at <code>groupPosition</code> retains the same identity.</p>
|
||||
*
|
||||
* <p>This method does not notify for the group item.
|
||||
* If the group has also changed, use {@link #notifyGroupAndChildrenItemsChanged(int)} instead.</p>
|
||||
*
|
||||
* @param groupPosition Position of the group item which contains changed children
|
||||
*
|
||||
* @see #notifyGroupAndChildrenItemsChanged(int)
|
||||
*/
|
||||
public void notifyChildrenOfGroupItemChanged(int groupPosition) {
|
||||
mAdapter.notifyChildrenOfGroupItemChanged(groupPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify any registered observers that the child item at <code>{groupPosition, childPosition}</code> has changed.
|
||||
*
|
||||
* <p>This is an item change event, not a structural change event. It indicates that any
|
||||
* reflection of the data at <code>{groupPosition, childPosition}</code> is out of date and should be updated.
|
||||
* The item at <code>{groupPosition, childPosition}</code> retains the same identity.</p>
|
||||
*
|
||||
* @param groupPosition Position of the group item which contains the changed child
|
||||
* @param childPosition Position of the child item in the group that has changed
|
||||
*
|
||||
* @see #notifyChildItemRangeChanged(int, int, int)
|
||||
*/
|
||||
public void notifyChildItemChanged(int groupPosition, int childPosition) {
|
||||
mAdapter.notifyChildItemChanged(groupPosition, childPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify any registered observers that the <code>itemCount</code> child items starting at
|
||||
* position <code>{groupPosition, childPosition}</code> have changed.
|
||||
*
|
||||
* <p>This is an item change event, not a structural change event. It indicates that
|
||||
* any reflection of the data in the given position range is out of date and should
|
||||
* be updated. The items in the given range retain the same identity.</p>
|
||||
*
|
||||
* @param groupPosition Position of the group item which contains the changed child
|
||||
* @param childPositionStart Position of the first child item in the group that has changed
|
||||
* @param itemCount Number of items that have changed
|
||||
*
|
||||
* @see #notifyChildItemChanged(int, int)
|
||||
*/
|
||||
public void notifyChildItemRangeChanged(int groupPosition, int childPositionStart, int itemCount) {
|
||||
mAdapter.notifyChildItemRangeChanged(groupPosition, childPositionStart, itemCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify any registered observers that the group item reflected at <code>groupPosition</code>
|
||||
* has been newly inserted. The group item previously at <code>groupPosition</code> is now at
|
||||
* position <code>groupPosition + 1</code>.
|
||||
*
|
||||
* <p>This is a structural change event. Representations of other existing items in the
|
||||
* data set are still considered up to date and will not be rebound, though their
|
||||
* positions may be altered.</p>
|
||||
*
|
||||
* @param groupPosition Position of the newly inserted group item in the data set
|
||||
*
|
||||
* @see #notifyGroupItemRangeInserted(int, int)
|
||||
*/
|
||||
public void notifyGroupItemInserted(int groupPosition) {
|
||||
mAdapter.notifyGroupItemInserted(groupPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify any registered observers that the currently reflected <code>itemCount</code>
|
||||
* group items starting at <code>groupPositionStart</code> have been newly inserted. The group items
|
||||
* previously located at <code>groupPositionStart</code> and beyond can now be found starting
|
||||
* at position <code>groupPositionStart + itemCount</code>.
|
||||
*
|
||||
* <p>This is a structural change event. Representations of other existing items in the
|
||||
* data set are still considered up to date and will not be rebound, though their positions
|
||||
* may be altered.</p>
|
||||
*
|
||||
* @param groupPositionStart Position of the first group item that was inserted
|
||||
* @param itemCount Number of group items inserted
|
||||
*
|
||||
* @see #notifyGroupItemInserted(int)
|
||||
*/
|
||||
public void notifyGroupItemRangeInserted(int groupPositionStart, int itemCount) {
|
||||
mAdapter.notifyGroupItemRangeInserted(groupPositionStart, itemCount);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notify any registered observers that the group item reflected at <code>groupPosition</code>
|
||||
* has been newly inserted. The group item previously at <code>groupPosition</code> is now at
|
||||
* position <code>groupPosition + 1</code>.
|
||||
*
|
||||
* <p>This is a structural change event. Representations of other existing items in the
|
||||
* data set are still considered up to date and will not be rebound, though their
|
||||
* positions may be altered.</p>
|
||||
*
|
||||
* @param groupPosition Position of the group item which contains the inserted child
|
||||
* @param childPosition Position of the newly inserted child item in the data set
|
||||
*
|
||||
* @see #notifyChildItemRangeInserted(int, int, int)
|
||||
*/
|
||||
public void notifyChildItemInserted(int groupPosition, int childPosition) {
|
||||
mAdapter.notifyChildItemInserted(groupPosition, childPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify any registered observers that the currently reflected <code>itemCount</code>
|
||||
* child items starting at <code>childPositionStart</code> have been newly inserted. The child items
|
||||
* previously located at <code>childPositionStart</code> and beyond can now be found starting
|
||||
* at position <code>childPositionStart + itemCount</code>.
|
||||
*
|
||||
* <p>This is a structural change event. Representations of other existing items in the
|
||||
* data set are still considered up to date and will not be rebound, though their positions
|
||||
* may be altered.</p>
|
||||
*
|
||||
* @param groupPosition Position of the group item which contains the inserted child
|
||||
* @param childPositionStart Position of the first child item that was inserted
|
||||
* @param itemCount Number of child items inserted
|
||||
*
|
||||
* @see #notifyChildItemInserted(int, int)
|
||||
*/
|
||||
public void notifyChildItemRangeInserted(int groupPosition, int childPositionStart, int itemCount) {
|
||||
mAdapter.notifyChildItemRangeInserted(groupPosition, childPositionStart, itemCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify any registered observers that the group item previously located at <code>groupPosition</code>
|
||||
* has been removed from the data set. The group items previously located at and after
|
||||
* <code>groupPosition</code> may now be found at <code>oldGroupPosition - 1</code>.
|
||||
*
|
||||
* <p>This is a structural change event. Representations of other existing items in the
|
||||
* data set are still considered up to date and will not be rebound, though their positions
|
||||
* may be altered.</p>
|
||||
*
|
||||
* @param groupPosition Position of the group item that has now been removed
|
||||
*
|
||||
* @see #notifyGroupItemRangeRemoved(int, int)
|
||||
*/
|
||||
public void notifyGroupItemRemoved(int groupPosition) {
|
||||
mAdapter.notifyGroupItemRemoved(groupPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify any registered observers that the <code>itemCount</code> group items previously
|
||||
* located at <code>groupPositionStart</code> have been removed from the data set. The group items
|
||||
* previously located at and after <code>groupPositionStart + itemCount</code> may now be found
|
||||
* at <code>oldPosition - itemCount</code>.
|
||||
*
|
||||
* <p>This is a structural change event. Representations of other existing items in the data
|
||||
* set are still considered up to date and will not be rebound, though their positions
|
||||
* may be altered.</p>
|
||||
*
|
||||
* @param groupPositionStart Previous position of the first group item that was removed
|
||||
* @param itemCount Number of group items removed from the data set
|
||||
*/
|
||||
public void notifyGroupItemRangeRemoved(int groupPositionStart, int itemCount) {
|
||||
mAdapter.notifyGroupItemRangeRemoved(groupPositionStart, itemCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify any registered observers that the child item previously located at <code>childPosition</code>
|
||||
* has been removed from the data set. The child items previously located at and after
|
||||
* <code>childPosition</code> may now be found at <code>oldGroupPosition - 1</code>.
|
||||
*
|
||||
* <p>This is a structural change event. Representations of other existing items in the
|
||||
* data set are still considered up to date and will not be rebound, though their positions
|
||||
* may be altered.</p>
|
||||
*
|
||||
* @param groupPosition Position of the group item which was the parent of the child item that was removed
|
||||
* @param childPosition Position of the child item that has now been removed
|
||||
*
|
||||
* @see #notifyGroupItemRangeRemoved(int, int)
|
||||
*/
|
||||
public void notifyChildItemRemoved(int groupPosition, int childPosition) {
|
||||
mAdapter.notifyChildItemRemoved(groupPosition, childPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify any registered observers that the <code>itemCount</code> child items previously
|
||||
* located at <code>childPositionStart</code> have been removed from the data set. The child items
|
||||
* previously located at and after <code>childPositionStart + itemCount</code> may now be found
|
||||
* at <code>oldPosition - itemCount</code>.
|
||||
*
|
||||
* <p>This is a structural change event. Representations of other existing items in the data
|
||||
* set are still considered up to date and will not be rebound, though their positions
|
||||
* may be altered.</p>
|
||||
*
|
||||
* @param groupPosition Position of the group item which was the parent of the child item that was removed
|
||||
* @param childPositionStart Previous position of the first child item that was removed
|
||||
* @param itemCount Number of child items removed from the data set
|
||||
*/
|
||||
public void notifyChildItemRangeRemoved(int groupPosition, int childPositionStart, int itemCount) {
|
||||
mAdapter.notifyChildItemRangeRemoved(groupPosition, childPositionStart, itemCount);
|
||||
}
|
||||
|
||||
public static class SavedState implements Parcelable {
|
||||
final int [] adapterSavedState;
|
||||
|
||||
public SavedState(int[] adapterSavedState) {
|
||||
this.adapterSavedState = adapterSavedState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeIntArray(this.adapterSavedState);
|
||||
}
|
||||
|
||||
private SavedState(Parcel in) {
|
||||
this.adapterSavedState = in.createIntArray();
|
||||
}
|
||||
|
||||
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
|
||||
public SavedState createFromParcel(Parcel source) {
|
||||
return new SavedState(source);
|
||||
}
|
||||
|
||||
public SavedState[] newArray(int size) {
|
||||
return new SavedState[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.swipeable;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
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.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ItemSlidingAnimator {
|
||||
private static final String TAG = "ItemSlidingAnimator";
|
||||
|
||||
private Interpolator mSlideToDefaultPositionAnimationInterpolator = new AccelerateDecelerateInterpolator();
|
||||
private Interpolator mSlideToOutsideOfWindowAnimationInterpolator = new AccelerateInterpolator(0.8f);
|
||||
private List<RecyclerView.ViewHolder> mActive;
|
||||
private List<WeakReference<ViewHolderDeferredProcess>> mDeferredProcesses;
|
||||
private int[] mTmpLocation = new int[2];
|
||||
private Rect mTmpRect = new Rect();
|
||||
private int mImmediatelySetTranslationThreshold;
|
||||
|
||||
public ItemSlidingAnimator() {
|
||||
mActive = new ArrayList<>();
|
||||
mDeferredProcesses = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void slideToDefaultPosition(RecyclerView.ViewHolder holder, boolean shouldAnimate, long duration) {
|
||||
cancelDeferredProcess(holder);
|
||||
slideToSpecifiedPositionInternal(holder, 0, shouldAnimate, duration);
|
||||
}
|
||||
|
||||
public void slideToOutsideOfWindow(RecyclerView.ViewHolder holder, boolean toLeft, boolean shouldAnimate, long duration) {
|
||||
cancelDeferredProcess(holder);
|
||||
slideToOutsideOfWindowInternal(holder, toLeft, shouldAnimate, duration);
|
||||
}
|
||||
|
||||
public void slideToSpecifiedPosition(RecyclerView.ViewHolder holder, float position) {
|
||||
cancelDeferredProcess(holder);
|
||||
slideToSpecifiedPositionInternal(holder, position, false, 0);
|
||||
}
|
||||
|
||||
private void cancelDeferredProcess(RecyclerView.ViewHolder holder) {
|
||||
int n = mDeferredProcesses.size();
|
||||
for (int i = n - 1; i >= 0; i--) {
|
||||
ViewHolderDeferredProcess dp = mDeferredProcesses.get(i).get();
|
||||
|
||||
if (dp == null || dp.hasTargetViewHolderOrLostReference(holder)) {
|
||||
mDeferredProcesses.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean slideToSpecifiedPositionInternal(final RecyclerView.ViewHolder holder, final float position, boolean shouldAnimate, long duration) {
|
||||
duration = (shouldAnimate) ? duration : 0;
|
||||
|
||||
if (position != 0.0f) {
|
||||
final View containerView = ((SwipeableItemViewHolder) holder).getSwipeableContainerView();
|
||||
final int width = containerView.getWidth();
|
||||
|
||||
if (width != 0) {
|
||||
final int translationX;
|
||||
translationX = (int) (width * position + 0.5f);
|
||||
return animateSlideInternalCompat(holder, translationX, duration, mSlideToDefaultPositionAnimationInterpolator);
|
||||
} else {
|
||||
final DeferredSlideProcess deferredProcess = new DeferredSlideProcess(holder, position);
|
||||
mDeferredProcesses.add(new WeakReference<ViewHolderDeferredProcess>(deferredProcess));
|
||||
containerView.post(deferredProcess);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return animateSlideInternalCompat(holder, 0, duration, mSlideToDefaultPositionAnimationInterpolator);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean slideToOutsideOfWindowInternal(RecyclerView.ViewHolder holder, boolean toLeft, boolean shouldAnimate, long duration) {
|
||||
if (!(holder instanceof SwipeableItemViewHolder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final View containerView = ((SwipeableItemViewHolder) holder).getSwipeableContainerView();
|
||||
final ViewGroup parent = (ViewGroup) containerView.getParent();
|
||||
|
||||
if (parent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int left = containerView.getLeft();
|
||||
final int right = containerView.getRight();
|
||||
final int width = right - left;
|
||||
final boolean parentIsShown = parent.isShown();
|
||||
|
||||
parent.getWindowVisibleDisplayFrame(mTmpRect);
|
||||
|
||||
final int translateX;
|
||||
if ((width == 0) || !parentIsShown) {
|
||||
// not measured yet or not shown
|
||||
translateX = (toLeft) ? (-mTmpRect.width()) : (mTmpRect.width());
|
||||
shouldAnimate = false;
|
||||
} else {
|
||||
parent.getLocationInWindow(mTmpLocation);
|
||||
|
||||
if (toLeft) {
|
||||
translateX = -(mTmpLocation[0] + width);
|
||||
} else {
|
||||
translateX = mTmpRect.width() - (mTmpLocation[0] - left);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldAnimate) {
|
||||
shouldAnimate = containerView.isShown();
|
||||
}
|
||||
|
||||
duration = (shouldAnimate) ? duration : 0;
|
||||
|
||||
return animateSlideInternalCompat(holder, translateX, duration, mSlideToOutsideOfWindowAnimationInterpolator);
|
||||
}
|
||||
|
||||
private boolean animateSlideInternalCompat(RecyclerView.ViewHolder holder, int translationX, long duration, Interpolator interpolator) {
|
||||
if (supportsViewPropertyAnimator()) {
|
||||
return animateSlideInternal(holder, translationX, duration, interpolator);
|
||||
} else {
|
||||
return slideInternalPreHoneycomb(holder, translationX);
|
||||
}
|
||||
}
|
||||
|
||||
static void slideInternalCompat(RecyclerView.ViewHolder holder, int translationX) {
|
||||
if (supportsViewPropertyAnimator()) {
|
||||
slideInternal(holder, translationX);
|
||||
} else {
|
||||
slideInternalPreHoneycomb(holder, translationX);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
private static boolean slideInternalPreHoneycomb(RecyclerView.ViewHolder holder, int translationX) {
|
||||
if (!(holder instanceof SwipeableItemViewHolder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final View containerView = ((SwipeableItemViewHolder) holder).getSwipeableContainerView();
|
||||
|
||||
final ViewGroup.LayoutParams lp = containerView.getLayoutParams();
|
||||
if (lp instanceof ViewGroup.MarginLayoutParams) {
|
||||
final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp;
|
||||
mlp.leftMargin = translationX;
|
||||
mlp.rightMargin = -translationX;
|
||||
|
||||
if (lp instanceof FrameLayout.LayoutParams) {
|
||||
((FrameLayout.LayoutParams) lp).gravity = Gravity.TOP | Gravity.LEFT;
|
||||
}
|
||||
containerView.setLayoutParams(mlp);
|
||||
} else {
|
||||
Log.w(TAG, "should use MarginLayoutParams supported view for compatibility on Android 2.3");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int getTranslationXPreHoneycomb(RecyclerView.ViewHolder holder) {
|
||||
final View containerView = ((SwipeableItemViewHolder) holder).getSwipeableContainerView();
|
||||
|
||||
final ViewGroup.LayoutParams lp = containerView.getLayoutParams();
|
||||
if (lp instanceof ViewGroup.MarginLayoutParams) {
|
||||
final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp;
|
||||
return mlp.leftMargin;
|
||||
} else {
|
||||
Log.w(TAG, "should use MarginLayoutParams supported view for compatibility on Android 2.3");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void slideInternal(final RecyclerView.ViewHolder holder, int translationX) {
|
||||
if (!(holder instanceof SwipeableItemViewHolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final View containerView = ((SwipeableItemViewHolder) holder).getSwipeableContainerView();
|
||||
ViewCompat.animate(containerView).cancel();
|
||||
ViewCompat.setTranslationX(containerView, translationX);
|
||||
}
|
||||
|
||||
private boolean animateSlideInternal(final RecyclerView.ViewHolder holder, int translationX, long duration, Interpolator interpolator) {
|
||||
if (!(holder instanceof SwipeableItemViewHolder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final View containerView = ((SwipeableItemViewHolder) holder).getSwipeableContainerView();
|
||||
|
||||
final int prevTranslationX = (int) (ViewCompat.getTranslationX(containerView) + 0.5f);
|
||||
|
||||
endAnimation(holder);
|
||||
|
||||
final int curTranslationX = (int) (ViewCompat.getTranslationX(containerView) + 0.5f);
|
||||
final int toX = translationX;
|
||||
|
||||
if (curTranslationX == toX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (duration == 0 || Math.abs(toX - prevTranslationX) <= mImmediatelySetTranslationThreshold) {
|
||||
ViewCompat.setTranslationX(containerView, toX);
|
||||
return false;
|
||||
}
|
||||
|
||||
ViewCompat.setTranslationX(containerView, prevTranslationX);
|
||||
|
||||
final ViewPropertyAnimatorCompat animator = ViewCompat.animate(containerView);
|
||||
|
||||
animator.setDuration(duration);
|
||||
if (interpolator != null) {
|
||||
animator.setInterpolator(interpolator);
|
||||
}
|
||||
animator.translationX(toX);
|
||||
animator.setListener(new ViewPropertyAnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
animator.setListener(null);
|
||||
mActive.remove(holder);
|
||||
ViewCompat.setTranslationX(view, toX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
}
|
||||
});
|
||||
|
||||
mActive.add(holder);
|
||||
|
||||
animator.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void endAnimation(RecyclerView.ViewHolder holder) {
|
||||
if (!(holder instanceof SwipeableItemViewHolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelDeferredProcess(holder);
|
||||
|
||||
final View containerView = ((SwipeableItemViewHolder) holder).getSwipeableContainerView();
|
||||
|
||||
ViewCompat.animate(containerView).cancel();
|
||||
|
||||
if (mActive.remove(holder)) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in the active animation list [slide]");
|
||||
}
|
||||
}
|
||||
|
||||
public void endAnimations() {
|
||||
for (int i = mActive.size() - 1; i >= 0; i--) {
|
||||
final RecyclerView.ViewHolder holder = mActive.get(i);
|
||||
endAnimation(holder);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRunning(RecyclerView.ViewHolder holder) {
|
||||
return mActive.contains(holder);
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return !(mActive.isEmpty());
|
||||
}
|
||||
|
||||
public int getImmediatelySetTranslationThreshold() {
|
||||
return mImmediatelySetTranslationThreshold;
|
||||
}
|
||||
|
||||
public void setImmediatelySetTranslationThreshold(int threshold) {
|
||||
mImmediatelySetTranslationThreshold = threshold;
|
||||
}
|
||||
|
||||
private static boolean supportsViewPropertyAnimator() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
|
||||
}
|
||||
|
||||
public int getSwipeContainerViewTranslationX(RecyclerView.ViewHolder holder) {
|
||||
if (supportsViewPropertyAnimator()) {
|
||||
final View containerView = ((SwipeableItemViewHolder) holder).getSwipeableContainerView();
|
||||
return (int) (ViewCompat.getTranslationX(containerView) + 0.5f);
|
||||
} else {
|
||||
return getTranslationXPreHoneycomb(holder);
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class ViewHolderDeferredProcess implements Runnable {
|
||||
final WeakReference<RecyclerView.ViewHolder> mRefHolder;
|
||||
|
||||
public ViewHolderDeferredProcess(RecyclerView.ViewHolder holder) {
|
||||
mRefHolder = new WeakReference<RecyclerView.ViewHolder>(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
RecyclerView.ViewHolder holder = mRefHolder.get();
|
||||
|
||||
if (holder != null) {
|
||||
onProcess(holder);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasTargetViewHolderOrLostReference(RecyclerView.ViewHolder holder) {
|
||||
RecyclerView.ViewHolder holder2 = mRefHolder.get();
|
||||
return ((holder2 == null) || (holder2 == holder));
|
||||
}
|
||||
|
||||
protected abstract void onProcess(RecyclerView.ViewHolder holder);
|
||||
}
|
||||
|
||||
private static final class DeferredSlideProcess extends ViewHolderDeferredProcess {
|
||||
final float mPosition;
|
||||
|
||||
public DeferredSlideProcess(RecyclerView.ViewHolder holder, float position) {
|
||||
super(holder);
|
||||
mPosition = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProcess(RecyclerView.ViewHolder holder) {
|
||||
final View containerView = ((SwipeableItemViewHolder) holder).getSwipeableContainerView();
|
||||
final int width = containerView.getWidth();
|
||||
final int translationX;
|
||||
|
||||
translationX = (int) (width * mPosition + 0.5f);
|
||||
slideInternalCompat(holder, translationX);
|
||||
}
|
||||
}
|
||||
}
|
||||
+831
@@ -0,0 +1,831 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.swipeable;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.support.v4.view.MotionEventCompat;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
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 com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.CustomRecyclerViewUtils;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
|
||||
|
||||
/**
|
||||
* Provides item swipe operation for {@link android.support.v7.widget.RecyclerView}
|
||||
*/
|
||||
@SuppressWarnings("PointlessBitwiseExpression")
|
||||
public class RecyclerViewSwipeManager {
|
||||
private static final String TAG = "ARVSwipeManager";
|
||||
|
||||
/**
|
||||
* Result code of swipe operation. Used for the second argument of the
|
||||
* {@link SwipeableItemAdapter#onPerformAfterSwipeReaction(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int)} method.
|
||||
*
|
||||
* None. (internal default value, this value is not used for the argument)
|
||||
*/
|
||||
public static final int RESULT_NONE = 0;
|
||||
|
||||
/**
|
||||
* Result code of swipe operation. Used for the second argument of the
|
||||
* {@link SwipeableItemAdapter#onPerformAfterSwipeReaction(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int)} method.
|
||||
*
|
||||
* Canceled.
|
||||
*/
|
||||
public static final int RESULT_CANCELED = 1;
|
||||
|
||||
/**
|
||||
* Result code of swipe operation. Used for the second argument of the
|
||||
* {@link SwipeableItemAdapter#onPerformAfterSwipeReaction(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int)} method.
|
||||
*
|
||||
* Swipe left performed.
|
||||
*/
|
||||
public static final int RESULT_SWIPED_LEFT = 2;
|
||||
|
||||
/**
|
||||
* Result code of swipe operation. Used for the second argument of the
|
||||
* {@link SwipeableItemAdapter#onPerformAfterSwipeReaction(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int)} method.
|
||||
*
|
||||
* Swipe right performed.
|
||||
*/
|
||||
public static final int RESULT_SWIPED_RIGHT = 3;
|
||||
|
||||
// ---
|
||||
/**
|
||||
* Reaction type to swipe operation. Used for the return value of the
|
||||
* {@link SwipeableItemAdapter#onGetSwipeReactionType(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int)} method.
|
||||
*
|
||||
* Indicates "can not swipe left" (completely no reactions)
|
||||
*/
|
||||
public static final int REACTION_CAN_NOT_SWIPE_LEFT = (0 << 0);
|
||||
|
||||
/**
|
||||
* Reaction type to swipe operation. Used for the return value of the
|
||||
* {@link SwipeableItemAdapter#onGetSwipeReactionType(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int)} method.
|
||||
*
|
||||
* Indicates "can not swipe left" (not swipeable, but rubber-band effect applied)
|
||||
*/
|
||||
public static final int REACTION_CAN_NOT_SWIPE_LEFT_WITH_RUBBER_BAND_EFFECT = (1 << 0);
|
||||
|
||||
/**
|
||||
* Reaction type to swipe operation. Used for the return value of the
|
||||
* {@link SwipeableItemAdapter#onGetSwipeReactionType(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int)} method.
|
||||
*
|
||||
* Indicates "can swipe left"
|
||||
*/
|
||||
public static final int REACTION_CAN_SWIPE_LEFT = (2 << 0);
|
||||
|
||||
/**
|
||||
* Reaction type to swipe operation. Used for the return value of the
|
||||
* {@link SwipeableItemAdapter#onGetSwipeReactionType(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int)} method.
|
||||
*
|
||||
* Indicates "can not swipe right" (completely no reactions)
|
||||
*/
|
||||
public static final int REACTION_CAN_NOT_SWIPE_RIGHT = (0 << 16);
|
||||
|
||||
/**
|
||||
* Reaction type to swipe operation. Used for the return value of the
|
||||
* {@link SwipeableItemAdapter#onGetSwipeReactionType(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int)} method.
|
||||
*
|
||||
* Indicates "can not swipe right" (not swipeable, but rubber-band effect applied)
|
||||
*/
|
||||
public static final int REACTION_CAN_NOT_SWIPE_RIGHT_WITH_RUBBER_BAND_EFFECT = (1 << 16);
|
||||
|
||||
/**
|
||||
* Reaction type to swipe operation. Used for the return value of the
|
||||
* {@link SwipeableItemAdapter#onGetSwipeReactionType(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int)} method.
|
||||
*
|
||||
* Indicates "can swipe right"
|
||||
*/
|
||||
public static final int REACTION_CAN_SWIPE_RIGHT = (2 << 16);
|
||||
|
||||
/**
|
||||
* Convenient constant value: Equals to {@link #REACTION_CAN_NOT_SWIPE_LEFT} | {@link #REACTION_CAN_NOT_SWIPE_RIGHT}
|
||||
*/
|
||||
public static final int REACTION_CAN_NOT_SWIPE_BOTH =
|
||||
REACTION_CAN_NOT_SWIPE_LEFT | REACTION_CAN_NOT_SWIPE_RIGHT;
|
||||
|
||||
/**
|
||||
* Convenient constant value: Equals to {@link #REACTION_CAN_NOT_SWIPE_LEFT_WITH_RUBBER_BAND_EFFECT} | {@link #REACTION_CAN_NOT_SWIPE_RIGHT_WITH_RUBBER_BAND_EFFECT}
|
||||
*/
|
||||
public static final int REACTION_CAN_NOT_SWIPE_BOTH_WITH_RUBBER_BAND_EFFECT =
|
||||
REACTION_CAN_NOT_SWIPE_LEFT_WITH_RUBBER_BAND_EFFECT |
|
||||
REACTION_CAN_NOT_SWIPE_RIGHT_WITH_RUBBER_BAND_EFFECT;
|
||||
|
||||
/**
|
||||
* Convenient constant value: Equals to {@link #REACTION_CAN_SWIPE_LEFT} | {@link #REACTION_CAN_SWIPE_RIGHT}
|
||||
*/
|
||||
public static final int REACTION_CAN_SWIPE_BOTH =
|
||||
REACTION_CAN_SWIPE_LEFT | REACTION_CAN_SWIPE_RIGHT;
|
||||
|
||||
// ---
|
||||
/**
|
||||
* Background drawable type used for the second argument of the
|
||||
* {@link SwipeableItemAdapter#onSetSwipeBackground(android.support.v7.widget.RecyclerView.ViewHolder, int, int)} method.
|
||||
*
|
||||
* Background image for the neutral (= not swiping) item.
|
||||
*/
|
||||
public static final int DRAWABLE_SWIPE_NEUTRAL_BACKGROUND = 0;
|
||||
|
||||
/**
|
||||
* Background drawable type used for the second argument of the
|
||||
* {@link SwipeableItemAdapter#onSetSwipeBackground(android.support.v7.widget.RecyclerView.ViewHolder, int, int)} method.
|
||||
*
|
||||
* Background image for the swiping-left item.
|
||||
*/
|
||||
public static final int DRAWABLE_SWIPE_LEFT_BACKGROUND = 1;
|
||||
|
||||
/**
|
||||
* Background drawable type used for the second argument of the
|
||||
* {@link SwipeableItemAdapter#onSetSwipeBackground(android.support.v7.widget.RecyclerView.ViewHolder, int, int)} method.
|
||||
*
|
||||
* Background image for the swiping-right item.
|
||||
*/
|
||||
public static final int DRAWABLE_SWIPE_RIGHT_BACKGROUND = 2;
|
||||
|
||||
// ---
|
||||
/**
|
||||
* After-reaction type used for the {@link SwipeableItemViewHolder#setAfterSwipeReaction(int)} and {@link SwipeableItemViewHolder#getAfterSwipeReaction()} methods.
|
||||
* Represents perform nothing.
|
||||
*/
|
||||
public static final int AFTER_SWIPE_REACTION_DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* After-reaction type used for the {@link SwipeableItemViewHolder#setAfterSwipeReaction(int)} and {@link SwipeableItemViewHolder#getAfterSwipeReaction()} methods.
|
||||
* Represents remove the swiped item.
|
||||
*/
|
||||
public static final int AFTER_SWIPE_REACTION_REMOVE_ITEM = 1;
|
||||
|
||||
/**
|
||||
* After-reaction type used for the {@link SwipeableItemViewHolder#setAfterSwipeReaction(int)} and {@link SwipeableItemViewHolder#getAfterSwipeReaction()} methods.
|
||||
* Represents that the item moved to swiped direction.
|
||||
*/
|
||||
public static final int AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION = 2;
|
||||
|
||||
// ---
|
||||
/**
|
||||
* Special value for the {@link SwipeableItemViewHolder#setSwipeItemSlideAmount(float)} and {@link SwipeableItemViewHolder#getSwipeItemSlideAmount()} methods.
|
||||
* Indicates that this item is pinned to LEFT of the window.
|
||||
*/
|
||||
public static final float OUTSIDE_OF_THE_WINDOW_LEFT = -Float.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* Special value for the {@link SwipeableItemViewHolder#setSwipeItemSlideAmount(float)} and {@link SwipeableItemViewHolder#getSwipeItemSlideAmount()} methods.
|
||||
* Indicates that this item is pinned to RIGHT the window.
|
||||
*/
|
||||
public static final float OUTSIDE_OF_THE_WINDOW_RIGHT = Float.MAX_VALUE;
|
||||
|
||||
|
||||
// ---
|
||||
/**
|
||||
* State flag for the {@link SwipeableItemViewHolder#setSwipeStateFlags(int)} and {@link SwipeableItemViewHolder#getSwipeStateFlags()} methods.
|
||||
* Indicates that currently performing swiping.
|
||||
*/
|
||||
public static final int STATE_FLAG_SWIPING = (1 << 0);
|
||||
|
||||
/**
|
||||
* State flag for the {@link SwipeableItemViewHolder#setSwipeStateFlags(int)} and {@link SwipeableItemViewHolder#getSwipeStateFlags()} methods.
|
||||
* Indicates that this item is being swiped.
|
||||
*/
|
||||
public static final int STATE_FLAG_IS_ACTIVE = (1 << 1);
|
||||
|
||||
/**
|
||||
* State flag for the {@link SwipeableItemViewHolder#setSwipeStateFlags(int)} and {@link SwipeableItemViewHolder#getSwipeStateFlags()} methods.
|
||||
* If this flag is set, some other flags are changed and require to apply.
|
||||
*/
|
||||
public static final int STATE_FLAG_IS_UPDATED = (1 << 31);
|
||||
|
||||
// ---
|
||||
|
||||
/**
|
||||
* Used for listening item swipe events
|
||||
*/
|
||||
public interface OnItemSwipeEventListener {
|
||||
/**
|
||||
* Callback method to be invoked when swiping is started.
|
||||
*
|
||||
* @param position The position of the item.
|
||||
*/
|
||||
void onItemSwipeStarted(int position);
|
||||
|
||||
/**
|
||||
* Callback method to be invoked when swiping is finished.
|
||||
*
|
||||
* @param position The position of the item.
|
||||
* @param result The result code of the swipe operation.
|
||||
* @param afterSwipeReaction The reaction type to the swipe operation.
|
||||
*/
|
||||
void onItemSwipeFinished(int position, int result, int afterSwipeReaction);
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
private static final int MIN_DISTANCE_TOUCH_SLOP_MUL = 10;
|
||||
private static final int SLIDE_ITEM_IMMEDIATELY_SET_TRANSLATION_THRESHOLD_DP = 8;
|
||||
|
||||
private static final boolean LOCAL_LOGV = false;
|
||||
private static final boolean LOCAL_LOGD = false;
|
||||
|
||||
private RecyclerView.OnItemTouchListener mInternalUseOnItemTouchListener;
|
||||
private RecyclerView mRecyclerView;
|
||||
|
||||
private long mReturnToDefaultPositionAnimationDuration = 300;
|
||||
private long mMoveToOutsideWindowAnimationDuration = 200;
|
||||
|
||||
private int mTouchSlop;
|
||||
private int mMinFlingVelocity; // [pixels per second]
|
||||
private int mMaxFlingVelocity; // [pixels per second]
|
||||
private int mInitialTouchX;
|
||||
private int mInitialTouchY;
|
||||
private long mCheckingTouchSlop = RecyclerView.NO_ID;
|
||||
|
||||
private ItemSlidingAnimator mItemSlideAnimator;
|
||||
private SwipeableItemWrapperAdapter<RecyclerView.ViewHolder> mAdapter;
|
||||
private RecyclerView.ViewHolder mSwipingItem;
|
||||
private int mSwipingItemPosition = RecyclerView.NO_POSITION;
|
||||
private Rect mSwipingItemMargins = new Rect();
|
||||
private int mTouchedItemOffsetX;
|
||||
private int mLastTouchX;
|
||||
private int mSwipingItemReactionType;
|
||||
private VelocityTracker mVelocityTracker;
|
||||
private SwipingItemOperator mSwipingItemOperator;
|
||||
private OnItemSwipeEventListener mItemSwipeEventListener;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public RecyclerViewSwipeManager() {
|
||||
mInternalUseOnItemTouchListener = new RecyclerView.OnItemTouchListener() {
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
|
||||
return RecyclerViewSwipeManager.this.onInterceptTouchEvent(rv, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
|
||||
RecyclerViewSwipeManager.this.onTouchEvent(rv, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
}
|
||||
};
|
||||
mItemSlideAnimator = new ItemSlidingAnimator();
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create wrapped adapter.
|
||||
*
|
||||
* @param adapter The target adapter.
|
||||
*
|
||||
* @return Wrapped adapter which is associated to this {@link RecyclerViewSwipeManager} instance.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public RecyclerView.Adapter createWrappedAdapter(RecyclerView.Adapter adapter) {
|
||||
if (mAdapter != null) {
|
||||
throw new IllegalStateException("already have a wrapped adapter");
|
||||
}
|
||||
|
||||
mAdapter = new SwipeableItemWrapperAdapter(this, adapter);
|
||||
|
||||
return mAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates this manager instance has released or not.
|
||||
*
|
||||
* @return True if this manager instance has released
|
||||
*/
|
||||
public boolean isReleased() {
|
||||
return (mInternalUseOnItemTouchListener == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches {@link android.support.v7.widget.RecyclerView} instance.
|
||||
*
|
||||
* Before calling this method, the target {@link android.support.v7.widget.RecyclerView} must set
|
||||
* the wrapped adapter instance which is returned by the
|
||||
* {@link #createWrappedAdapter(android.support.v7.widget.RecyclerView.Adapter)} method.
|
||||
*
|
||||
* @param rv The {@link android.support.v7.widget.RecyclerView} instance
|
||||
*/
|
||||
public void attachRecyclerView(RecyclerView rv) {
|
||||
if (rv == null) {
|
||||
throw new IllegalArgumentException("RecyclerView cannot be null");
|
||||
}
|
||||
|
||||
if (isReleased()) {
|
||||
throw new IllegalStateException("Accessing released object");
|
||||
}
|
||||
|
||||
if (mRecyclerView != null) {
|
||||
throw new IllegalStateException("RecyclerView instance has already been set");
|
||||
}
|
||||
|
||||
if (mAdapter == null || getSwipeableItemWrapperAdapter(rv) != mAdapter) {
|
||||
throw new IllegalStateException("adapter is not set properly");
|
||||
}
|
||||
|
||||
mRecyclerView = rv;
|
||||
mRecyclerView.addOnItemTouchListener(mInternalUseOnItemTouchListener);
|
||||
|
||||
final ViewConfiguration vc = ViewConfiguration.get(rv.getContext());
|
||||
|
||||
mTouchSlop = vc.getScaledTouchSlop();
|
||||
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
|
||||
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
|
||||
|
||||
mItemSlideAnimator.setImmediatelySetTranslationThreshold(
|
||||
(int) (rv.getResources().getDisplayMetrics().density * SLIDE_ITEM_IMMEDIATELY_SET_TRANSLATION_THRESHOLD_DP + 0.5f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach the {@link android.support.v7.widget.RecyclerView} instance and release internal field references.
|
||||
*
|
||||
* This method should be called in order to avoid memory leaks.
|
||||
*/
|
||||
public void release() {
|
||||
if (mRecyclerView != null && mInternalUseOnItemTouchListener != null) {
|
||||
mRecyclerView.removeOnItemTouchListener(mInternalUseOnItemTouchListener);
|
||||
}
|
||||
mInternalUseOnItemTouchListener = null;
|
||||
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
|
||||
if (mItemSlideAnimator != null) {
|
||||
mItemSlideAnimator.endAnimations();
|
||||
mItemSlideAnimator = null;
|
||||
}
|
||||
|
||||
mAdapter = null;
|
||||
mRecyclerView = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether currently performing item swiping.
|
||||
*
|
||||
* @return True if currently performing item swiping
|
||||
*/
|
||||
public boolean isSwiping() {
|
||||
return (mSwipingItem != null);
|
||||
}
|
||||
|
||||
/*package*/ boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
|
||||
final int action = MotionEventCompat.getActionMasked(e);
|
||||
|
||||
if (LOCAL_LOGV) {
|
||||
Log.v(TAG, "onInterceptTouchEvent() action = " + action);
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
if (isSwiping()) {
|
||||
handleActionUpOrCancelWhileSwiping(e);
|
||||
return true;
|
||||
} else {
|
||||
handleActionUpOrCancelWhileNotSwiping();
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
if (!isSwiping()) {
|
||||
handleActionDown(rv, e);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (isSwiping()) {
|
||||
// NOTE: The first ACTION_MOVE event will come here. (maybe a bug of RecyclerView?)
|
||||
handleActionMoveWhileSwiping(e);
|
||||
return true;
|
||||
} else {
|
||||
if (handleActionMoveWhileNotSwiping(rv, e)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*package*/ void onTouchEvent(RecyclerView rv, MotionEvent e) {
|
||||
final int action = MotionEventCompat.getActionMasked(e);
|
||||
|
||||
if (LOCAL_LOGV) {
|
||||
Log.v(TAG, "onTouchEvent() action = " + action);
|
||||
}
|
||||
|
||||
if (!isSwiping()) {
|
||||
// Log.w(TAG, "onTouchEvent() - unexpected state");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
handleActionUpOrCancelWhileSwiping(e);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
handleActionMoveWhileSwiping(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleActionDown(RecyclerView rv, MotionEvent e) {
|
||||
final RecyclerView.Adapter adapter = rv.getAdapter();
|
||||
final RecyclerView.ViewHolder holder = CustomRecyclerViewUtils.findChildViewHolderUnderWithTranslation(rv, e.getX(), e.getY());
|
||||
|
||||
if (!(holder instanceof SwipeableItemViewHolder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int itemPosition = CustomRecyclerViewUtils.getSynchronizedPosition(holder);
|
||||
|
||||
// verify the touched item is valid state
|
||||
if (!(itemPosition >= 0 && itemPosition < adapter.getItemCount())) {
|
||||
return false;
|
||||
}
|
||||
if (holder.getItemId() != adapter.getItemId(itemPosition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int touchX = (int) (e.getX() + 0.5f);
|
||||
final int touchY = (int) (e.getY() + 0.5f);
|
||||
|
||||
final View view = holder.itemView;
|
||||
final int translateX = (int) (ViewCompat.getTranslationX(view) + 0.5f);
|
||||
final int translateY = (int) (ViewCompat.getTranslationY(view) + 0.5f);
|
||||
final int viewX = touchX - (view.getLeft() + translateX);
|
||||
final int viewY = touchY - (view.getTop() + translateY);
|
||||
|
||||
final int reactionType = mAdapter.getSwipeReactionType(holder, itemPosition, viewX, viewY);
|
||||
|
||||
if (reactionType == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mInitialTouchX = touchX;
|
||||
mInitialTouchY = touchY;
|
||||
mCheckingTouchSlop = holder.getItemId();
|
||||
mSwipingItemReactionType = reactionType;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static SwipeableItemWrapperAdapter getSwipeableItemWrapperAdapter(RecyclerView rv) {
|
||||
return WrapperAdapterUtils.findWrappedAdapter(rv.getAdapter(), SwipeableItemWrapperAdapter.class);
|
||||
}
|
||||
|
||||
private void handleActionUpOrCancelWhileNotSwiping() {
|
||||
mCheckingTouchSlop = RecyclerView.NO_ID;
|
||||
mSwipingItemReactionType = 0;
|
||||
}
|
||||
|
||||
private boolean handleActionUpOrCancelWhileSwiping(MotionEvent e) {
|
||||
mLastTouchX = (int) (e.getX() + 0.5f);
|
||||
|
||||
int result = RESULT_CANCELED;
|
||||
|
||||
if (MotionEventCompat.getActionMasked(e) == MotionEvent.ACTION_UP) {
|
||||
final int viewWidth = mSwipingItem.itemView.getWidth();
|
||||
final float distance = mLastTouchX - mInitialTouchX;
|
||||
final float absDistance = Math.abs(distance);
|
||||
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); // 1000: pixels per second
|
||||
|
||||
final float xVelocity = mVelocityTracker.getXVelocity();
|
||||
final float absXVelocity = Math.abs(xVelocity);
|
||||
|
||||
if ((absDistance > (mTouchSlop * MIN_DISTANCE_TOUCH_SLOP_MUL)) &&
|
||||
((distance * xVelocity) > 0.0f) &&
|
||||
(absXVelocity <= mMaxFlingVelocity) &&
|
||||
((absDistance > (viewWidth / 2)) || (absXVelocity >= mMinFlingVelocity))) {
|
||||
|
||||
if ((distance < 0) && SwipeReactionUtils.canSwipeLeft(mSwipingItemReactionType)) {
|
||||
result = RESULT_SWIPED_LEFT;
|
||||
} else if ((distance > 0) && SwipeReactionUtils.canSwipeRight(mSwipingItemReactionType)) {
|
||||
result = RESULT_SWIPED_RIGHT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LOCAL_LOGD) {
|
||||
Log.d(TAG, "swping finished --- result = " + result);
|
||||
}
|
||||
|
||||
finishSwiping(result);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleActionMoveWhileNotSwiping(RecyclerView rv, MotionEvent e) {
|
||||
if (mCheckingTouchSlop == RecyclerView.NO_ID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Math.abs(e.getY() - mInitialTouchY) > mTouchSlop) {
|
||||
// scrolling occurred
|
||||
mCheckingTouchSlop = RecyclerView.NO_ID;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Math.abs(e.getX() - mInitialTouchX) <= mTouchSlop) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final RecyclerView.ViewHolder holder = CustomRecyclerViewUtils.findChildViewHolderUnderWithTranslation(rv, e.getX(), e.getY());
|
||||
|
||||
if (holder == null || holder.getItemId() != mCheckingTouchSlop) {
|
||||
mCheckingTouchSlop = RecyclerView.NO_ID;
|
||||
return false;
|
||||
}
|
||||
|
||||
final int itemPosition = CustomRecyclerViewUtils.getSynchronizedPosition(holder);
|
||||
|
||||
if (itemPosition == RecyclerView.NO_POSITION) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (LOCAL_LOGD) {
|
||||
Log.d(TAG, "swiping started");
|
||||
}
|
||||
|
||||
startSwiping(rv, e, holder, itemPosition);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleActionMoveWhileSwiping(MotionEvent e) {
|
||||
mLastTouchX = (int) (e.getX() + 0.5f);
|
||||
mVelocityTracker.addMovement(e);
|
||||
|
||||
final int swipeDistance = mLastTouchX - mTouchedItemOffsetX;
|
||||
|
||||
mSwipingItemOperator.update(swipeDistance);
|
||||
}
|
||||
|
||||
private void startSwiping(RecyclerView rv, MotionEvent e, RecyclerView.ViewHolder holder, int itemPosition) {
|
||||
mSwipingItem = holder;
|
||||
mSwipingItemPosition = itemPosition;
|
||||
mLastTouchX = (int) (e.getX() + 0.5f);
|
||||
mTouchedItemOffsetX = mLastTouchX;
|
||||
CustomRecyclerViewUtils.getLayoutMargins(holder.itemView, mSwipingItemMargins);
|
||||
|
||||
mSwipingItemOperator = new SwipingItemOperator(this, mSwipingItem, mSwipingItemPosition, mSwipingItemReactionType);
|
||||
mSwipingItemOperator.start();
|
||||
|
||||
mVelocityTracker.clear();
|
||||
mVelocityTracker.addMovement(e);
|
||||
|
||||
mRecyclerView.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
|
||||
// raise onItemSwipeStarted() event
|
||||
if (mItemSwipeEventListener != null) {
|
||||
mItemSwipeEventListener.onItemSwipeStarted(itemPosition);
|
||||
}
|
||||
|
||||
// raise onSwipeItemStarted() event
|
||||
mAdapter.onSwipeItemStarted(this, holder, itemPosition);
|
||||
}
|
||||
|
||||
private void finishSwiping(int result) {
|
||||
final RecyclerView.ViewHolder swipingItem = mSwipingItem;
|
||||
|
||||
if (swipingItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mRecyclerView != null && mRecyclerView.getParent() != null) {
|
||||
mRecyclerView.getParent().requestDisallowInterceptTouchEvent(false);
|
||||
}
|
||||
|
||||
final int itemPosition = mSwipingItemPosition;
|
||||
|
||||
mVelocityTracker.clear();
|
||||
|
||||
mSwipingItem = null;
|
||||
mSwipingItemPosition = RecyclerView.NO_POSITION;
|
||||
mLastTouchX = 0;
|
||||
mInitialTouchX = 0;
|
||||
mTouchedItemOffsetX = 0;
|
||||
mCheckingTouchSlop = RecyclerView.NO_ID;
|
||||
mSwipingItemReactionType = 0;
|
||||
|
||||
if (mSwipingItemOperator != null) {
|
||||
mSwipingItemOperator.finish();
|
||||
mSwipingItemOperator = null;
|
||||
}
|
||||
|
||||
final boolean toLeft = (result == RESULT_SWIPED_LEFT);
|
||||
int afterReaction = AFTER_SWIPE_REACTION_DEFAULT;
|
||||
|
||||
if (mAdapter != null) {
|
||||
afterReaction = mAdapter.onSwipeItemFinished(swipingItem, itemPosition, result);
|
||||
}
|
||||
|
||||
afterReaction = correctAfterReaction(result, afterReaction);
|
||||
|
||||
switch (afterReaction) {
|
||||
case AFTER_SWIPE_REACTION_DEFAULT:
|
||||
mItemSlideAnimator.slideToDefaultPosition(swipingItem, true, mReturnToDefaultPositionAnimationDuration);
|
||||
break;
|
||||
case AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION:
|
||||
mItemSlideAnimator.slideToOutsideOfWindow(
|
||||
swipingItem, toLeft, true, mMoveToOutsideWindowAnimationDuration);
|
||||
break;
|
||||
case AFTER_SWIPE_REACTION_REMOVE_ITEM: {
|
||||
final RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
|
||||
|
||||
final long removeAnimationDuration = (itemAnimator != null) ? itemAnimator.getRemoveDuration() : 0;
|
||||
|
||||
if (supportsViewPropertyAnimator()) {
|
||||
final long moveAnimationDuration = (itemAnimator != null) ? itemAnimator.getMoveDuration() : 0;
|
||||
|
||||
final RemovingItemDecorator decorator = new RemovingItemDecorator(
|
||||
mRecyclerView, swipingItem, removeAnimationDuration, moveAnimationDuration);
|
||||
|
||||
decorator.setMoveAnimationInterpolator(SwipeDismissItemAnimator.MOVE_INTERPOLATOR);
|
||||
decorator.start();
|
||||
}
|
||||
|
||||
mItemSlideAnimator.slideToOutsideOfWindow(
|
||||
swipingItem, toLeft, true, removeAnimationDuration);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknwon after reaction type: " + afterReaction);
|
||||
}
|
||||
|
||||
if (mAdapter != null) {
|
||||
mAdapter.onSwipeItemFinished2(swipingItem, itemPosition, result, afterReaction);
|
||||
}
|
||||
|
||||
// raise onItemSwipeFinished() event
|
||||
if (mItemSwipeEventListener != null) {
|
||||
mItemSwipeEventListener.onItemSwipeFinished(itemPosition, result, afterReaction);
|
||||
}
|
||||
}
|
||||
|
||||
private static int correctAfterReaction(int result, int afterReaction) {
|
||||
if ((afterReaction == AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION) ||
|
||||
(afterReaction == AFTER_SWIPE_REACTION_REMOVE_ITEM)) {
|
||||
if (!((result == RESULT_SWIPED_LEFT) || (result == RESULT_SWIPED_RIGHT))) {
|
||||
afterReaction = AFTER_SWIPE_REACTION_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
return afterReaction;
|
||||
}
|
||||
|
||||
public void cancelSwipe() {
|
||||
finishSwiping(RESULT_CANCELED);
|
||||
}
|
||||
|
||||
/*package*/ boolean isAnimationRunning(RecyclerView.ViewHolder item) {
|
||||
return (mItemSlideAnimator != null) && (mItemSlideAnimator.isRunning(item));
|
||||
}
|
||||
|
||||
private void slideItem(RecyclerView.ViewHolder holder, float amount, boolean shouldAnimate) {
|
||||
if (amount == OUTSIDE_OF_THE_WINDOW_LEFT) {
|
||||
mItemSlideAnimator.slideToOutsideOfWindow(holder, true, shouldAnimate, mMoveToOutsideWindowAnimationDuration);
|
||||
} else if (amount == OUTSIDE_OF_THE_WINDOW_RIGHT) {
|
||||
mItemSlideAnimator.slideToOutsideOfWindow(holder, false, shouldAnimate, mMoveToOutsideWindowAnimationDuration);
|
||||
} else if (amount == 0.0f) {
|
||||
mItemSlideAnimator.slideToDefaultPosition(holder, shouldAnimate, mReturnToDefaultPositionAnimationDuration);
|
||||
} else {
|
||||
mItemSlideAnimator.slideToSpecifiedPosition(holder, amount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the duration of the "return to default position" animation
|
||||
*
|
||||
* @return Duration of the "return to default position" animation in milliseconds
|
||||
*/
|
||||
public long getReturnToDefaultPositionAnimationDuration() {
|
||||
return mReturnToDefaultPositionAnimationDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration of the "return to default position" animation
|
||||
*
|
||||
* @param duration Duration of the "return to default position" animation in milliseconds
|
||||
*/
|
||||
public void setReturnToDefaultPositionAnimationDuration(long duration) {
|
||||
mReturnToDefaultPositionAnimationDuration = duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the duration of the "move to outside of the window" animation
|
||||
*
|
||||
* @return Duration of the "move to outside of the window" animation in milliseconds
|
||||
*/
|
||||
public long getMoveToOutsideWindowAnimationDuration() {
|
||||
return mMoveToOutsideWindowAnimationDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration of the "move to outside of the window" animation
|
||||
*
|
||||
* @param duration Duration of the "move to outside of the window" animation in milliseconds
|
||||
*/
|
||||
public void setMoveToOutsideWindowAnimationDuration(long duration) {
|
||||
mMoveToOutsideWindowAnimationDuration = duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets OnItemSwipeEventListener listener
|
||||
* @return The listener object
|
||||
*/
|
||||
public OnItemSwipeEventListener getOnItemSwipeEventListener() {
|
||||
return mItemSwipeEventListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets OnItemSwipeEventListener listener
|
||||
*
|
||||
* @param listener The listener object
|
||||
*/
|
||||
public void setOnItemSwipeEventListener(OnItemSwipeEventListener listener) {
|
||||
mItemSwipeEventListener = listener;
|
||||
}
|
||||
|
||||
/*package*/ void applySlideItem(RecyclerView.ViewHolder holder, int itemPosition, float prevAmount, float amount, boolean shouldAnimate) {
|
||||
final SwipeableItemViewHolder holder2 = (SwipeableItemViewHolder) holder;
|
||||
final View containerView = holder2.getSwipeableContainerView();
|
||||
|
||||
if (containerView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int reqBackgroundType;
|
||||
|
||||
if (amount == 0.0f) {
|
||||
if (prevAmount == 0.0f) {
|
||||
reqBackgroundType = DRAWABLE_SWIPE_NEUTRAL_BACKGROUND;
|
||||
} else {
|
||||
reqBackgroundType = (prevAmount < 0)
|
||||
? DRAWABLE_SWIPE_LEFT_BACKGROUND
|
||||
: DRAWABLE_SWIPE_RIGHT_BACKGROUND;
|
||||
}
|
||||
} else {
|
||||
reqBackgroundType = (amount < 0)
|
||||
? DRAWABLE_SWIPE_LEFT_BACKGROUND
|
||||
: DRAWABLE_SWIPE_RIGHT_BACKGROUND;
|
||||
}
|
||||
|
||||
if (amount == 0.0f) {
|
||||
slideItem(holder, amount, shouldAnimate);
|
||||
mAdapter.setSwipeBackgroundDrawable(holder, itemPosition, reqBackgroundType);
|
||||
} else {
|
||||
float adjustedAmount = amount;
|
||||
|
||||
adjustedAmount = Math.max(adjustedAmount, holder2.getMaxLeftSwipeAmount());
|
||||
adjustedAmount = Math.min(adjustedAmount, holder2.getMaxRightSwipeAmount());
|
||||
|
||||
mAdapter.setSwipeBackgroundDrawable(holder, itemPosition, reqBackgroundType);
|
||||
slideItem(holder, adjustedAmount, shouldAnimate);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void cancelPendingAnimations(RecyclerView.ViewHolder holder) {
|
||||
if (mItemSlideAnimator != null) {
|
||||
mItemSlideAnimator.endAnimation(holder);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ int getSwipeContainerViewTranslationX(RecyclerView.ViewHolder holder) {
|
||||
return mItemSlideAnimator.getSwipeContainerViewTranslationX(holder);
|
||||
}
|
||||
|
||||
private static boolean supportsViewPropertyAnimator() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
|
||||
}
|
||||
}
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.swipeable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.CustomRecyclerViewUtils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
class RemovingItemDecorator extends RecyclerView.ItemDecoration {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "RemovingItemDecorator";
|
||||
|
||||
private static final int NOTIFY_REMOVAL_EFFECT_PHASE_1 = 0;
|
||||
private static final int NOTIFY_REMOVAL_EFFECT_END = 1;
|
||||
|
||||
private static final long ADDITIONAL_REMOVE_DURATION = 100; // workaround: to avoid the gap between the below item
|
||||
|
||||
private RecyclerView mRecyclerView;
|
||||
private RecyclerView.ViewHolder mSwipingItem;
|
||||
private long mSwipingItemId;
|
||||
private Rect mSwipingItemBounds = new Rect();
|
||||
private int mInitialTranslationX;
|
||||
private int mTranslationY;
|
||||
private long mStartTime;
|
||||
private long mRemoveAnimationDuration;
|
||||
private long mMoveAnimationDuration;
|
||||
private Interpolator mMoveAnimationInterpolator;
|
||||
private Drawable mSwipeBackgroundDrawable;
|
||||
|
||||
private int mPendingNotificationMask = 0;
|
||||
|
||||
public RemovingItemDecorator(RecyclerView rv, RecyclerView.ViewHolder swipingItem, long removeAnimationDuration, long moveAnimationDuration) {
|
||||
mRecyclerView = rv;
|
||||
mSwipingItem = swipingItem;
|
||||
mSwipingItemId = swipingItem.getItemId();
|
||||
|
||||
mRemoveAnimationDuration = removeAnimationDuration + ADDITIONAL_REMOVE_DURATION;
|
||||
mMoveAnimationDuration = moveAnimationDuration;
|
||||
mInitialTranslationX = (int) (ViewCompat.getTranslationX(swipingItem.itemView) + 0.5f);
|
||||
|
||||
CustomRecyclerViewUtils.getViewBounds(mSwipingItem.itemView, mSwipingItemBounds);
|
||||
}
|
||||
|
||||
public void setMoveAnimationInterpolator(Interpolator interpolator) {
|
||||
mMoveAnimationInterpolator = interpolator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
final long elapsedTime = getElapsedTime(mStartTime);
|
||||
|
||||
final Rect bounds = mSwipingItemBounds;
|
||||
final float heightScale = determineBackgroundHeightScaleSwipeCompletedSuccessfully(elapsedTime);
|
||||
|
||||
final int origHeight = bounds.height();
|
||||
final int actualHeight = (int) (heightScale * origHeight + 0.5f);
|
||||
|
||||
fillSwipingItemBackground(c, mSwipeBackgroundDrawable, actualHeight);
|
||||
|
||||
if (mSwipingItemId == mSwipingItem.getItemId()) {
|
||||
mTranslationY = (int) (ViewCompat.getTranslationY(mSwipingItem.itemView) + 0.5f);
|
||||
}
|
||||
|
||||
if (requiresContinuousAnimationAfterSwipeCompletedSuccessfully(elapsedTime)) {
|
||||
postInvalidateOnAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean requiresContinuousAnimationAfterSwipeCompletedSuccessfully(long elapsedTime) {
|
||||
return (elapsedTime >= mRemoveAnimationDuration) &&
|
||||
(elapsedTime < (mRemoveAnimationDuration + mMoveAnimationDuration));
|
||||
}
|
||||
|
||||
private float determineBackgroundHeightScaleSwipeCompletedSuccessfully(long elapsedTime) {
|
||||
float heightScale = 0.0f;
|
||||
|
||||
if (elapsedTime < mRemoveAnimationDuration) {
|
||||
heightScale = 1.0f;
|
||||
} else if (elapsedTime < (mRemoveAnimationDuration + mMoveAnimationDuration)) {
|
||||
if (mMoveAnimationDuration != 0) {
|
||||
heightScale = 1.0f - (float) (elapsedTime - mRemoveAnimationDuration) / mMoveAnimationDuration;
|
||||
if (mMoveAnimationInterpolator != null) {
|
||||
heightScale = mMoveAnimationInterpolator.getInterpolation(heightScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return heightScale;
|
||||
}
|
||||
|
||||
private void fillSwipingItemBackground(Canvas c, Drawable drawable, int height) {
|
||||
final Rect bounds = mSwipingItemBounds;
|
||||
final int translationX = mInitialTranslationX;
|
||||
final int translationY = mTranslationY;
|
||||
|
||||
if (height < 0) {
|
||||
height = bounds.height();
|
||||
}
|
||||
|
||||
if ((height == 0) || (drawable == null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int savedCount = c.save();
|
||||
|
||||
c.clipRect(
|
||||
bounds.left + translationX,
|
||||
bounds.top + translationY,
|
||||
bounds.right + translationX,
|
||||
bounds.top + translationY + height);
|
||||
|
||||
// c.drawColor(0xffff0000); // <-- debug
|
||||
|
||||
c.translate(
|
||||
bounds.left + translationX,
|
||||
bounds.top + translationY - (bounds.height() - height) / 2);
|
||||
drawable.setBounds(0, 0, bounds.width(), bounds.height());
|
||||
|
||||
drawable.draw(c);
|
||||
|
||||
c.restoreToCount(savedCount);
|
||||
}
|
||||
|
||||
private void postInvalidateOnAnimation() {
|
||||
// final Rect bounds = mSwipingItemBounds;
|
||||
// final int translationX = mInitialTranslationX;
|
||||
// final int translationY = mTranslationY;
|
||||
|
||||
// ViewCompat.postInvalidateOnAnimation(
|
||||
// mRecyclerView,
|
||||
// bounds.left + translationX,
|
||||
// bounds.top + translationY,
|
||||
// bounds.right + translationX,
|
||||
// bounds.bottom + translationY);
|
||||
ViewCompat.postInvalidateOnAnimation(mRecyclerView);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
final View containerView = ((SwipeableItemViewHolder) mSwipingItem).getSwipeableContainerView();
|
||||
|
||||
ViewCompat.animate(containerView).cancel();
|
||||
|
||||
mRecyclerView.addItemDecoration(this);
|
||||
|
||||
mStartTime = System.currentTimeMillis();
|
||||
mTranslationY = (int) (ViewCompat.getTranslationY(mSwipingItem.itemView) + 0.5f);
|
||||
mSwipeBackgroundDrawable = mSwipingItem.itemView.getBackground();
|
||||
|
||||
postInvalidateOnAnimation();
|
||||
notifyDelayed(NOTIFY_REMOVAL_EFFECT_PHASE_1, mRemoveAnimationDuration);
|
||||
}
|
||||
|
||||
private void notifyDelayed(int code, long delay) {
|
||||
final int mask = (1 << code);
|
||||
|
||||
if ((mPendingNotificationMask & mask) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPendingNotificationMask |= mask;
|
||||
|
||||
final DelayedNotificationRunner notification = new DelayedNotificationRunner(this, code);
|
||||
ViewCompat.postOnAnimationDelayed(mRecyclerView, notification, delay);
|
||||
}
|
||||
|
||||
/*package*/ void onDelayedNotification(int code) {
|
||||
final int mask = (1 << code);
|
||||
final long elapsedTime = getElapsedTime(mStartTime);
|
||||
|
||||
mPendingNotificationMask &= (~mask);
|
||||
|
||||
switch (code) {
|
||||
case NOTIFY_REMOVAL_EFFECT_PHASE_1:
|
||||
if (elapsedTime < mRemoveAnimationDuration) {
|
||||
notifyDelayed(NOTIFY_REMOVAL_EFFECT_PHASE_1, (mRemoveAnimationDuration - elapsedTime));
|
||||
} else {
|
||||
postInvalidateOnAnimation();
|
||||
notifyDelayed(NOTIFY_REMOVAL_EFFECT_END, mMoveAnimationDuration);
|
||||
}
|
||||
break;
|
||||
case NOTIFY_REMOVAL_EFFECT_END:
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
mRecyclerView.removeItemDecoration(this);
|
||||
postInvalidateOnAnimation(); // this is required to avoid remnant of the decoration
|
||||
|
||||
mRecyclerView = null;
|
||||
mSwipingItem = null;
|
||||
mTranslationY = 0;
|
||||
mMoveAnimationInterpolator = null;
|
||||
}
|
||||
|
||||
protected static long getElapsedTime(long initialTime) {
|
||||
final long curTime = System.currentTimeMillis();
|
||||
return (curTime >= initialTime) ? (curTime - initialTime) : Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
private static class DelayedNotificationRunner implements Runnable {
|
||||
private WeakReference<RemovingItemDecorator> mRefDecorator;
|
||||
private final int mCode;
|
||||
|
||||
public DelayedNotificationRunner(RemovingItemDecorator decorator, int code) {
|
||||
mRefDecorator = new WeakReference<>(decorator);
|
||||
mCode = code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final RemovingItemDecorator decorator = mRefDecorator.get();
|
||||
mRefDecorator.clear();
|
||||
mRefDecorator = null;
|
||||
|
||||
if (decorator != null) {
|
||||
decorator.onDelayedNotification(mCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.swipeable;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
class RubberBandInterpolator implements Interpolator {
|
||||
private final float mLimit;
|
||||
|
||||
public RubberBandInterpolator(float limit) {
|
||||
mLimit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getInterpolation(float input) {
|
||||
final float t = (1 - input);
|
||||
return mLimit * (1 - (t * t));
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.swipeable;
|
||||
|
||||
class SwipeReactionUtils {
|
||||
public static final int REACTION_CAN_NOT_SWIPE = 0;
|
||||
public static final int REACTION_CAN_NOT_SWIPE_WITH_RUBBER_EFFECT = 1;
|
||||
public static final int REACTION_CAN_SWIPE = 2;
|
||||
|
||||
public static int extractLeftReaction(int type) {
|
||||
return (type & 0x3);
|
||||
}
|
||||
|
||||
public static int extractRightReaction(int type) {
|
||||
return ((type >>> 16) & 0x3);
|
||||
}
|
||||
|
||||
public static boolean canSwipeLeft(int reactionType) {
|
||||
return (extractLeftReaction(reactionType) == REACTION_CAN_SWIPE);
|
||||
}
|
||||
|
||||
public static boolean canSwipeRight(int reactionType) {
|
||||
return (extractRightReaction(reactionType) == REACTION_CAN_SWIPE);
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.swipeable;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
public interface SwipeableItemAdapter<T extends RecyclerView.ViewHolder> {
|
||||
|
||||
/**
|
||||
* Called when user is attempt to swipe the item.
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to item user is attempt to start swiping.
|
||||
* @param position The position of the item within the adapter's data set.
|
||||
* @param x Touched X position. Relative from the itemView's top-left.
|
||||
* @param y Touched Y position. Relative from the itemView's top-left.
|
||||
|
||||
* @return Reaction type. Bitwise OR of these flags;
|
||||
* - {@link RecyclerViewSwipeManager#REACTION_CAN_NOT_SWIPE_LEFT}
|
||||
* - {@link RecyclerViewSwipeManager#REACTION_CAN_NOT_SWIPE_LEFT_WITH_RUBBER_BAND_EFFECT}
|
||||
* - {@link RecyclerViewSwipeManager#REACTION_CAN_SWIPE_LEFT}
|
||||
* - {@link RecyclerViewSwipeManager#REACTION_CAN_NOT_SWIPE_RIGHT}
|
||||
* - {@link RecyclerViewSwipeManager#REACTION_CAN_NOT_SWIPE_RIGHT_WITH_RUBBER_BAND_EFFECT}
|
||||
* - {@link RecyclerViewSwipeManager#REACTION_CAN_SWIPE_RIGHT}
|
||||
*/
|
||||
int onGetSwipeReactionType(T holder, int position, int x, int y);
|
||||
|
||||
/**
|
||||
* Called when sets background of the swiping item.
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to the swiping item.
|
||||
* @param position The position of the item within the adapter's data set.
|
||||
* @param type Background type. One of the
|
||||
* {@link RecyclerViewSwipeManager#DRAWABLE_SWIPE_NEUTRAL_BACKGROUND},
|
||||
* {@link RecyclerViewSwipeManager#DRAWABLE_SWIPE_LEFT_BACKGROUND} or
|
||||
* {@link RecyclerViewSwipeManager#DRAWABLE_SWIPE_RIGHT_BACKGROUND}.
|
||||
*/
|
||||
void onSetSwipeBackground(T holder, int position, int type);
|
||||
|
||||
/**
|
||||
* Called when item is swiped.
|
||||
*
|
||||
* *Note that do not change data set and do not call notifyDataXXX() methods inside of this method.*
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to the swiped item.
|
||||
* @param position The position of the item within the adapter's data set.
|
||||
* @param result The result code of user's swipe operation.
|
||||
* {@link RecyclerViewSwipeManager#RESULT_CANCELED},
|
||||
* {@link RecyclerViewSwipeManager#RESULT_SWIPED_LEFT} or
|
||||
* {@link RecyclerViewSwipeManager#RESULT_SWIPED_RIGHT}
|
||||
*
|
||||
* @return Reaction type of after swiping.
|
||||
* One of the {@link RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_DEFAULT},
|
||||
* {@link RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION} or
|
||||
* {@link RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_REMOVE_ITEM}.
|
||||
*/
|
||||
int onSwipeItem(T holder, int position, int result);
|
||||
|
||||
/**
|
||||
* Called after {@link #onSwipeItem(android.support.v7.widget.RecyclerView.ViewHolder, int, int)} method.
|
||||
*
|
||||
* You can update data set and call notifyDataXXX() methods inside of this method.
|
||||
*
|
||||
* @param holder The ViewHolder which is associated to the swiped item.
|
||||
* @param position The position of the item within the adapter's data set.
|
||||
* @param result The result code of user's swipe operation.
|
||||
* {@link RecyclerViewSwipeManager#RESULT_CANCELED},
|
||||
* {@link RecyclerViewSwipeManager#RESULT_SWIPED_LEFT} or
|
||||
* {@link RecyclerViewSwipeManager#RESULT_SWIPED_RIGHT}
|
||||
* @param reaction Reaction type. One of the {@link RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_DEFAULT},
|
||||
* {@link RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION} or
|
||||
* {@link RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_REMOVE_ITEM}.
|
||||
*/
|
||||
void onPerformAfterSwipeReaction(T holder, int position, int result, int reaction);
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.swipeable;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Interface which provides required information for swiping item.
|
||||
*
|
||||
* Implement this interface on your sub-class of the {@link android.support.v7.widget.RecyclerView.ViewHolder}.
|
||||
*/
|
||||
public interface SwipeableItemViewHolder {
|
||||
/**
|
||||
* Sets the state flags value for swiping item
|
||||
*
|
||||
* @param flags Bitwise OR of these flags;
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#STATE_FLAG_SWIPING}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#STATE_FLAG_IS_ACTIVE}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#STATE_FLAG_IS_UPDATED}
|
||||
*/
|
||||
void setSwipeStateFlags(int flags);
|
||||
|
||||
/**
|
||||
* Gets the state flags value for swiping item
|
||||
*
|
||||
* @return Bitwise OR of these flags;
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#STATE_FLAG_SWIPING}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#STATE_FLAG_IS_ACTIVE}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#STATE_FLAG_IS_UPDATED}
|
||||
*/
|
||||
int getSwipeStateFlags();
|
||||
|
||||
/**
|
||||
* Sets the result code of swiping item.
|
||||
*
|
||||
* @param result Result code. One of these values;
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_NONE}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_SWIPED_LEFT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_SWIPED_RIGHT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_CANCELED}
|
||||
*/
|
||||
void setSwipeResult(int result);
|
||||
|
||||
/**
|
||||
* Gets the result code of swiping item.
|
||||
*
|
||||
* @return Result code. One of these values;
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_NONE}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_SWIPED_LEFT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_SWIPED_RIGHT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#RESULT_CANCELED}
|
||||
*/
|
||||
int getSwipeResult();
|
||||
|
||||
/**
|
||||
* Sets the reaction type of after swiping item.
|
||||
*
|
||||
* @param reaction After-reaction type. One of these values;
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_DEFAULT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_REMOVE_ITEM}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION}
|
||||
*/
|
||||
void setAfterSwipeReaction(int reaction);
|
||||
|
||||
/**
|
||||
* Gets the reaction type of after swiping item.
|
||||
*
|
||||
* @return After-reaction type. One of these values;
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_DEFAULT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_REMOVE_ITEM}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION}
|
||||
*/
|
||||
int getAfterSwipeReaction();
|
||||
|
||||
/**
|
||||
* Sets the item swipe amount.
|
||||
*
|
||||
* @param amount Item swipe amount. Generally the range is [-1.0 .. 1.0], but these special values can be accepted;
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#OUTSIDE_OF_THE_WINDOW_LEFT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#OUTSIDE_OF_THE_WINDOW_RIGHT}
|
||||
*/
|
||||
void setSwipeItemSlideAmount(float amount);
|
||||
|
||||
/**
|
||||
* Gets the item swipe amount.
|
||||
*
|
||||
* @return Item swipe amount. Generally the range is [-1.0 .. 1.0], but these special values may be returned;
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#OUTSIDE_OF_THE_WINDOW_LEFT}
|
||||
* - {@link com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager#OUTSIDE_OF_THE_WINDOW_RIGHT}
|
||||
*/
|
||||
float getSwipeItemSlideAmount();
|
||||
|
||||
/**
|
||||
* Sets the maximum item right swipe amount.
|
||||
*
|
||||
* @param amount Item swipe amount. Generally the range is [-1.0 .. 1.0]
|
||||
*/
|
||||
void setMaxLeftSwipeAmount(float amount);
|
||||
|
||||
/**
|
||||
* Gets the maximum item left swipe amount.
|
||||
*
|
||||
* @return Item swipe amount. Generally the range is [-1.0 .. 1.0]
|
||||
*/
|
||||
float getMaxLeftSwipeAmount();
|
||||
|
||||
/**
|
||||
* Sets the maximum item right swipe amount.
|
||||
*
|
||||
* @param amount Item swipe amount. Generally the range is [-1.0 .. 1.0]
|
||||
*/
|
||||
void setMaxRightSwipeAmount(float amount);
|
||||
|
||||
/**
|
||||
* Gets the maximum item right swipe amount.
|
||||
*
|
||||
* @return Item swipe amount. Generally the range is [-1.0 .. 1.0]
|
||||
*/
|
||||
float getMaxRightSwipeAmount();
|
||||
|
||||
/**
|
||||
* Gets the container view for the swipeable area.
|
||||
*
|
||||
* @return The container view instance.
|
||||
*/
|
||||
View getSwipeableContainerView();
|
||||
}
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.swipeable;
|
||||
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.BaseWrapperAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
|
||||
|
||||
class SwipeableItemWrapperAdapter<VH extends RecyclerView.ViewHolder> extends BaseWrapperAdapter<VH> {
|
||||
private static final String TAG = "ARVSwipeableWrapper";
|
||||
|
||||
private static final int STATE_FLAG_INITIAL_VALUE = -1;
|
||||
|
||||
private static final boolean LOCAL_LOGV = false;
|
||||
private static final boolean LOCAL_LOGD = false;
|
||||
|
||||
private SwipeableItemAdapter mSwipeableItemAdapter;
|
||||
private RecyclerViewSwipeManager mSwipeManager;
|
||||
private int mSwipingItemPosition = RecyclerView.NO_POSITION;
|
||||
|
||||
public SwipeableItemWrapperAdapter(RecyclerViewSwipeManager manager, RecyclerView.Adapter<VH> adapter) {
|
||||
super(adapter);
|
||||
|
||||
mSwipeableItemAdapter = getSwipeableItemAdapter(adapter);
|
||||
if (mSwipeableItemAdapter == null) {
|
||||
throw new IllegalArgumentException("adapter does not implement SwipeableItemAdapter");
|
||||
}
|
||||
|
||||
if (manager == null) {
|
||||
throw new IllegalArgumentException("manager cannot be null");
|
||||
}
|
||||
|
||||
mSwipeManager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRelease() {
|
||||
super.onRelease();
|
||||
|
||||
mSwipeableItemAdapter = null;
|
||||
mSwipeManager = null;
|
||||
mSwipingItemPosition = RecyclerView.NO_POSITION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRecycled(VH holder) {
|
||||
super.onViewRecycled(holder);
|
||||
|
||||
// reset SwipeableItemViewHolder state
|
||||
if (holder instanceof SwipeableItemViewHolder) {
|
||||
mSwipeManager.cancelPendingAnimations(holder);
|
||||
|
||||
((SwipeableItemViewHolder) holder).setSwipeItemSlideAmount(0.0f);
|
||||
|
||||
View containerView = ((SwipeableItemViewHolder) holder).getSwipeableContainerView();
|
||||
|
||||
if (containerView != null) {
|
||||
ViewCompat.animate(containerView).cancel();
|
||||
ViewCompat.setTranslationX(containerView, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
final VH holder = super.onCreateViewHolder(parent, viewType);
|
||||
|
||||
if (holder instanceof SwipeableItemViewHolder) {
|
||||
((SwipeableItemViewHolder) holder).setSwipeStateFlags(STATE_FLAG_INITIAL_VALUE);
|
||||
}
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(VH holder, int position) {
|
||||
float prevSwipeItemSlideAmount = 0;
|
||||
|
||||
if (holder instanceof SwipeableItemViewHolder) {
|
||||
prevSwipeItemSlideAmount = ((SwipeableItemViewHolder) holder).getSwipeItemSlideAmount();
|
||||
}
|
||||
|
||||
if (isSwiping()) {
|
||||
int flags = RecyclerViewSwipeManager.STATE_FLAG_SWIPING;
|
||||
|
||||
if (position == mSwipingItemPosition) {
|
||||
flags |= RecyclerViewSwipeManager.STATE_FLAG_IS_ACTIVE;
|
||||
}
|
||||
|
||||
safeUpdateFlags(holder, flags);
|
||||
super.onBindViewHolder(holder, position);
|
||||
} else {
|
||||
safeUpdateFlags(holder, 0);
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
if (holder instanceof SwipeableItemViewHolder) {
|
||||
final float swipeItemSlideAmount = ((SwipeableItemViewHolder) holder).getSwipeItemSlideAmount();
|
||||
|
||||
if ((prevSwipeItemSlideAmount != swipeItemSlideAmount) ||
|
||||
!(mSwipeManager.isSwiping() || mSwipeManager.isAnimationRunning(holder))) {
|
||||
mSwipeManager.applySlideItem(holder, position, prevSwipeItemSlideAmount, swipeItemSlideAmount, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterChanged() {
|
||||
if (isSwiping()) {
|
||||
cancelSwipe();
|
||||
} else {
|
||||
super.onHandleWrappedAdapterChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterItemRangeChanged(int positionStart, int itemCount) {
|
||||
if (isSwiping()) {
|
||||
cancelSwipe();
|
||||
} else {
|
||||
super.onHandleWrappedAdapterItemRangeChanged(positionStart, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterItemRangeInserted(int positionStart, int itemCount) {
|
||||
if (isSwiping()) {
|
||||
cancelSwipe();
|
||||
} else {
|
||||
super.onHandleWrappedAdapterItemRangeInserted(positionStart, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterItemRangeRemoved(int positionStart, int itemCount) {
|
||||
if (isSwiping()) {
|
||||
cancelSwipe();
|
||||
} else {
|
||||
super.onHandleWrappedAdapterItemRangeRemoved(positionStart, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWrappedAdapterRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||
if (isSwiping()) {
|
||||
cancelSwipe();
|
||||
} else {
|
||||
super.onHandleWrappedAdapterRangeMoved(fromPosition, toPosition, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelSwipe() {
|
||||
if (mSwipeManager != null) {
|
||||
mSwipeManager.cancelSwipe();
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This method is called from RecyclerViewDragDropManager
|
||||
/*package*/
|
||||
@SuppressWarnings("unchecked")
|
||||
int getSwipeReactionType(RecyclerView.ViewHolder holder, int position, int x, int y) {
|
||||
if (LOCAL_LOGV) {
|
||||
Log.v(TAG, "getSwipeReactionType(holder = " + holder + ", position = " + position + ", x = " + x + ", y = " + y + ")");
|
||||
}
|
||||
|
||||
return mSwipeableItemAdapter.onGetSwipeReactionType(holder, position, x, y);
|
||||
}
|
||||
|
||||
// NOTE: This method is called from RecyclerViewDragDropManager
|
||||
/*package*/
|
||||
@SuppressWarnings("unchecked")
|
||||
void setSwipeBackgroundDrawable(RecyclerView.ViewHolder holder, int position, int type) {
|
||||
if (LOCAL_LOGV) {
|
||||
Log.v(TAG, "setSwipeBackgroundDrawable(holder = " + holder + ", position = " + position + ", type = " + type + ")");
|
||||
}
|
||||
|
||||
mSwipeableItemAdapter.onSetSwipeBackground(holder, position, type);
|
||||
}
|
||||
|
||||
// NOTE: This method is called from RecyclerViewDragDropManager
|
||||
/*package*/ void onSwipeItemStarted(RecyclerViewSwipeManager manager, RecyclerView.ViewHolder holder, int position) {
|
||||
if (LOCAL_LOGD) {
|
||||
Log.d(TAG, "onSwipeItemStarted(holder = " + holder + ", position = " + position + ")");
|
||||
}
|
||||
|
||||
mSwipingItemPosition = position;
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// NOTE: This method is called from RecyclerViewDragDropManager
|
||||
/*package*/
|
||||
@SuppressWarnings("unchecked")
|
||||
int onSwipeItemFinished(RecyclerView.ViewHolder holder, int position, int result) {
|
||||
if (LOCAL_LOGD) {
|
||||
Log.d(TAG, "onSwipeItemFinished(holder = " + holder + ", position = " + position + ", result = " + result + ")");
|
||||
}
|
||||
|
||||
mSwipingItemPosition = RecyclerView.NO_POSITION;
|
||||
|
||||
return mSwipeableItemAdapter.onSwipeItem(holder, position, result);
|
||||
}
|
||||
|
||||
/*package*/
|
||||
@SuppressWarnings("unchecked")
|
||||
void onSwipeItemFinished2(RecyclerView.ViewHolder holder, int position, int result, int afterReaction) {
|
||||
|
||||
((SwipeableItemViewHolder) holder).setSwipeResult(result);
|
||||
((SwipeableItemViewHolder) holder).setAfterSwipeReaction(afterReaction);
|
||||
((SwipeableItemViewHolder) holder).setSwipeItemSlideAmount(getSwipeAmountFromAfterReaction(result, afterReaction));
|
||||
|
||||
mSwipeableItemAdapter.onPerformAfterSwipeReaction(holder, position, result, afterReaction);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
protected boolean isSwiping() {
|
||||
return (mSwipingItemPosition != RecyclerView.NO_POSITION);
|
||||
}
|
||||
|
||||
private static float getSwipeAmountFromAfterReaction(int result, int afterReaction) {
|
||||
switch (afterReaction) {
|
||||
case RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_DEFAULT:
|
||||
return 0.0f;
|
||||
case RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION:
|
||||
case RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_REMOVE_ITEM:
|
||||
return (result == RecyclerViewSwipeManager.RESULT_SWIPED_LEFT)
|
||||
? RecyclerViewSwipeManager.OUTSIDE_OF_THE_WINDOW_LEFT
|
||||
: RecyclerViewSwipeManager.OUTSIDE_OF_THE_WINDOW_RIGHT;
|
||||
default:
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private static void safeUpdateFlags(RecyclerView.ViewHolder holder, int flags) {
|
||||
if (!(holder instanceof SwipeableItemViewHolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final SwipeableItemViewHolder holder2 = (SwipeableItemViewHolder) holder;
|
||||
|
||||
final int curFlags = holder2.getSwipeStateFlags();
|
||||
final int mask = ~RecyclerViewSwipeManager.STATE_FLAG_IS_UPDATED;
|
||||
|
||||
// append UPDATED flag
|
||||
if ((curFlags == STATE_FLAG_INITIAL_VALUE) || (((curFlags ^ flags) & mask) != 0)) {
|
||||
flags |= RecyclerViewSwipeManager.STATE_FLAG_IS_UPDATED;
|
||||
}
|
||||
|
||||
((SwipeableItemViewHolder) holder).setSwipeStateFlags(flags);
|
||||
}
|
||||
|
||||
private static SwipeableItemAdapter getSwipeableItemAdapter(RecyclerView.Adapter adapter) {
|
||||
return WrapperAdapterUtils.findWrappedAdapter(adapter, SwipeableItemAdapter.class);
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.swipeable;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
class SwipingItemOperator {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "SwipingItemOperator";
|
||||
|
||||
private static final int REACTION_CAN_NOT_SWIPE = SwipeReactionUtils.REACTION_CAN_NOT_SWIPE;
|
||||
private static final int REACTION_CAN_NOT_SWIPE_WITH_RUBBER_EFFECT = SwipeReactionUtils.REACTION_CAN_NOT_SWIPE_WITH_RUBBER_EFFECT;
|
||||
private static final int REACTION_CAN_SWIPE = SwipeReactionUtils.REACTION_CAN_SWIPE;
|
||||
|
||||
private static final float RUBBER_BAND_LIMIT = 0.15f;
|
||||
private static final int MIN_GRABBING_AREA_WIDTH = 48;
|
||||
|
||||
private static final Interpolator RUBBER_BAND_INTERPOLATOR = new RubberBandInterpolator(RUBBER_BAND_LIMIT);
|
||||
|
||||
private RecyclerViewSwipeManager mSwipeManager;
|
||||
private RecyclerView.ViewHolder mSwipingItem;
|
||||
private int mSwipingItemPosition;
|
||||
private View mSwipingItemContainerView;
|
||||
private int mLeftSwipeReactionType;
|
||||
private int mRightSwipeReactionType;
|
||||
private int mSwipingItemWidth;
|
||||
private float mInvSwipingItemWidth;
|
||||
private int mSwipeDistance;
|
||||
private float mPrevTranslateAmount;
|
||||
private int mInitialTranslateAmount;
|
||||
|
||||
public SwipingItemOperator(RecyclerViewSwipeManager manager, RecyclerView.ViewHolder swipingItem, int position, int swipeReactionType) {
|
||||
mSwipeManager = manager;
|
||||
mSwipingItem = swipingItem;
|
||||
mSwipingItemPosition = position;
|
||||
mLeftSwipeReactionType = SwipeReactionUtils.extractLeftReaction(swipeReactionType);
|
||||
mRightSwipeReactionType = SwipeReactionUtils.extractRightReaction(swipeReactionType);
|
||||
|
||||
mSwipingItemContainerView = ((SwipeableItemViewHolder) swipingItem).getSwipeableContainerView();
|
||||
mSwipingItemWidth = mSwipingItemContainerView.getWidth();
|
||||
mInvSwipingItemWidth = (mSwipingItemWidth != 0) ? (1.0f / mSwipingItemWidth) : 0.0f;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
float density = mSwipingItem.itemView.getResources().getDisplayMetrics().density;
|
||||
int maxAmount = Math.max(0, mSwipingItemWidth - (int) (density * MIN_GRABBING_AREA_WIDTH));
|
||||
|
||||
mInitialTranslateAmount = mSwipeManager.getSwipeContainerViewTranslationX(mSwipingItem);
|
||||
mInitialTranslateAmount = Math.min(mInitialTranslateAmount, maxAmount);
|
||||
mInitialTranslateAmount = Math.max(mInitialTranslateAmount, -maxAmount);
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
mSwipeManager = null;
|
||||
mSwipingItem = null;
|
||||
mSwipeDistance = 0;
|
||||
mSwipingItemWidth = 0;
|
||||
mInvSwipingItemWidth = 0;
|
||||
mLeftSwipeReactionType = REACTION_CAN_NOT_SWIPE;
|
||||
mRightSwipeReactionType = REACTION_CAN_NOT_SWIPE;
|
||||
mPrevTranslateAmount = 0;
|
||||
mInitialTranslateAmount = 0;
|
||||
mSwipingItemContainerView = null;
|
||||
}
|
||||
|
||||
public void update(int swipeDistance) {
|
||||
if (mSwipeDistance == swipeDistance) {
|
||||
return;
|
||||
}
|
||||
|
||||
mSwipeDistance = swipeDistance;
|
||||
|
||||
int distance = mSwipeDistance + mInitialTranslateAmount;
|
||||
|
||||
final int reactionType = (distance > 0) ? mRightSwipeReactionType : mLeftSwipeReactionType;
|
||||
|
||||
float translateAmount = 0;
|
||||
|
||||
switch (reactionType) {
|
||||
case REACTION_CAN_NOT_SWIPE:
|
||||
break;
|
||||
case REACTION_CAN_NOT_SWIPE_WITH_RUBBER_EFFECT:
|
||||
float proportion = Math.min(Math.abs(distance), mSwipingItemWidth) * mInvSwipingItemWidth;
|
||||
translateAmount = Math.signum(distance) * RUBBER_BAND_INTERPOLATOR.getInterpolation(proportion);
|
||||
break;
|
||||
case REACTION_CAN_SWIPE:
|
||||
translateAmount = Math.min(Math.max((distance * mInvSwipingItemWidth), -1.0f), 1.0f);
|
||||
break;
|
||||
}
|
||||
|
||||
mSwipeManager.applySlideItem(mSwipingItem, mSwipingItemPosition, mPrevTranslateAmount, translateAmount, false);
|
||||
|
||||
mPrevTranslateAmount = translateAmount;
|
||||
}
|
||||
}
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.touchguard;
|
||||
|
||||
import android.support.v4.view.MotionEventCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
/**
|
||||
* Hooks touch events to avoid unexpected scrolling.
|
||||
*/
|
||||
public class RecyclerViewTouchActionGuardManager {
|
||||
private static final String TAG = "ARVTouchActionGuardMgr";
|
||||
|
||||
private static final boolean LOCAL_LOGV = false;
|
||||
private static final boolean LOCAL_LOGD = false;
|
||||
|
||||
private RecyclerView.OnItemTouchListener mInternalUseOnItemTouchListener;
|
||||
private RecyclerView mRecyclerView;
|
||||
private boolean mGuarding;
|
||||
private int mInitialTouchY;
|
||||
private int mLastTouchY;
|
||||
private int mTouchSlop;
|
||||
private boolean mEnabled;
|
||||
private boolean mInterceptScrollingWhileAnimationRunning;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public RecyclerViewTouchActionGuardManager() {
|
||||
mInternalUseOnItemTouchListener = new RecyclerView.OnItemTouchListener() {
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
|
||||
return RecyclerViewTouchActionGuardManager.this.onInterceptTouchEvent(rv, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
|
||||
RecyclerViewTouchActionGuardManager.this.onTouchEvent(rv, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates this manager instance has released or not.
|
||||
*
|
||||
* @return True if this manager instance has released
|
||||
*/
|
||||
public boolean isReleased() {
|
||||
return (mInternalUseOnItemTouchListener == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches {@link android.support.v7.widget.RecyclerView} instance.
|
||||
*
|
||||
* @param rv The {@link android.support.v7.widget.RecyclerView} instance
|
||||
*/
|
||||
public void attachRecyclerView(RecyclerView rv) {
|
||||
if (rv == null) {
|
||||
throw new IllegalArgumentException("RecyclerView cannot be null");
|
||||
}
|
||||
|
||||
if (isReleased()) {
|
||||
throw new IllegalStateException("Accessing released object");
|
||||
}
|
||||
|
||||
if (mRecyclerView != null) {
|
||||
throw new IllegalStateException("RecyclerView instance has already been set");
|
||||
}
|
||||
|
||||
mRecyclerView = rv;
|
||||
mRecyclerView.addOnItemTouchListener(mInternalUseOnItemTouchListener);
|
||||
|
||||
mTouchSlop = ViewConfiguration.get(rv.getContext()).getScaledTouchSlop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach the {@link android.support.v7.widget.RecyclerView} instance and release internal field references.
|
||||
*
|
||||
* This method should be called in order to avoid memory leaks.
|
||||
*/
|
||||
public void release() {
|
||||
if (mRecyclerView != null && mInternalUseOnItemTouchListener != null) {
|
||||
mRecyclerView.removeOnItemTouchListener(mInternalUseOnItemTouchListener);
|
||||
}
|
||||
mInternalUseOnItemTouchListener = null;
|
||||
mRecyclerView = null;
|
||||
}
|
||||
|
||||
/*package*/ boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
|
||||
if (!mEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int action = MotionEventCompat.getActionMasked(e);
|
||||
|
||||
if (LOCAL_LOGV) {
|
||||
Log.v(TAG, "onInterceptTouchEvent() action = " + action);
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
handleActionUpOrCancel();
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
handleActionDown(e);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (handleActionMove(rv, e)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*package*/ void onTouchEvent(RecyclerView rv, MotionEvent e) {
|
||||
if (!mEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int action = MotionEventCompat.getActionMasked(e);
|
||||
|
||||
if (LOCAL_LOGV) {
|
||||
Log.v(TAG, "onTouchEvent() action = " + action);
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
handleActionUpOrCancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleActionMove(RecyclerView rv, MotionEvent e) {
|
||||
if (!mGuarding) {
|
||||
mLastTouchY = (int) (e.getY() + 0.5f);
|
||||
|
||||
final int distance = mLastTouchY - mInitialTouchY;
|
||||
|
||||
if (mInterceptScrollingWhileAnimationRunning && (Math.abs(distance) > mTouchSlop) && isAnimationRunning(rv)) {
|
||||
// intercept vertical move touch events while animation is running
|
||||
mGuarding = true;
|
||||
}
|
||||
}
|
||||
|
||||
return mGuarding;
|
||||
}
|
||||
|
||||
private static boolean isAnimationRunning(RecyclerView rv) {
|
||||
final RecyclerView.ItemAnimator itemAnimator = rv.getItemAnimator();
|
||||
return (itemAnimator != null) && (itemAnimator.isRunning());
|
||||
}
|
||||
|
||||
private void handleActionUpOrCancel() {
|
||||
mGuarding = false;
|
||||
mInitialTouchY = 0;
|
||||
mLastTouchY = 0;
|
||||
}
|
||||
|
||||
private void handleActionDown(MotionEvent e) {
|
||||
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
|
||||
mGuarding = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to use touch guard feature. If set false, all touch event interceptions will be disabled.
|
||||
*
|
||||
* @param enabled enabled / disabled
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
if (mEnabled == enabled) {
|
||||
return;
|
||||
}
|
||||
mEnabled = enabled;
|
||||
|
||||
if (!mEnabled) {
|
||||
handleActionUpOrCancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the touch guard feature is enabled.
|
||||
*
|
||||
* @return True for currently touch guard feature is enabled, otherwise false
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to use interception of "vertical scroll while animation running".
|
||||
*
|
||||
* @param enabled enabled / disabled
|
||||
*/
|
||||
public void setInterceptVerticalScrollingWhileAnimationRunning(boolean enabled) {
|
||||
mInterceptScrollingWhileAnimationRunning = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the interception of "vertical scroll while animation running" is enabled.
|
||||
*
|
||||
* @return enabled / disabled
|
||||
*/
|
||||
public boolean isInterceptScrollingWhileAnimationRunning() {
|
||||
return mInterceptScrollingWhileAnimationRunning;
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.utils;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder;
|
||||
|
||||
public abstract class AbstractDraggableItemViewHolder extends RecyclerView.ViewHolder implements DraggableItemViewHolder {
|
||||
private int mDragStateFlags;
|
||||
|
||||
public AbstractDraggableItemViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDragStateFlags(int flags) {
|
||||
mDragStateFlags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDragStateFlags() {
|
||||
return mDragStateFlags;
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.utils;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemViewHolder;
|
||||
|
||||
public abstract class AbstractDraggableSwipeableItemViewHolder extends RecyclerView.ViewHolder implements DraggableItemViewHolder, SwipeableItemViewHolder {
|
||||
private int mDragStateFlags;
|
||||
private int mSwipeStateFlags;
|
||||
private int mSwipeResult = RecyclerViewSwipeManager.RESULT_NONE;
|
||||
private int mAfterSwipeReaction = RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_DEFAULT;
|
||||
private float mSwipeAmount;
|
||||
private float mMaxLeftSwipeAmount = RecyclerViewSwipeManager.OUTSIDE_OF_THE_WINDOW_LEFT;
|
||||
private float mMaxRightSwipeAmount = RecyclerViewSwipeManager.OUTSIDE_OF_THE_WINDOW_RIGHT;
|
||||
|
||||
public AbstractDraggableSwipeableItemViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDragStateFlags(int flags) {
|
||||
mDragStateFlags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDragStateFlags() {
|
||||
return mDragStateFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSwipeStateFlags(int flags) {
|
||||
mSwipeStateFlags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSwipeStateFlags() {
|
||||
return mSwipeStateFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSwipeResult(int result) {
|
||||
mSwipeResult = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSwipeResult() {
|
||||
return mSwipeResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAfterSwipeReaction() {
|
||||
return mAfterSwipeReaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAfterSwipeReaction(int reaction) {
|
||||
mAfterSwipeReaction = reaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getSwipeItemSlideAmount() {
|
||||
return mSwipeAmount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSwipeItemSlideAmount(float amount) {
|
||||
mSwipeAmount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxLeftSwipeAmount(float amount) {
|
||||
mMaxLeftSwipeAmount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxLeftSwipeAmount() {
|
||||
return mMaxLeftSwipeAmount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxRightSwipeAmount(float amount) {
|
||||
mMaxRightSwipeAmount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxRightSwipeAmount() {
|
||||
return mMaxRightSwipeAmount;
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.utils;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.expandable.ExpandableItemAdapter;
|
||||
|
||||
public abstract class AbstractExpandableItemAdapter<GVH extends RecyclerView.ViewHolder, CVH extends RecyclerView.ViewHolder>
|
||||
extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
||||
implements ExpandableItemAdapter<GVH, CVH> {
|
||||
|
||||
/**
|
||||
* This method will not be called.
|
||||
* Override {@link #onCreateGroupViewHolder(android.view.ViewGroup, int)} and
|
||||
* {@link #onCreateChildViewHolder(android.view.ViewGroup, int)} instead.
|
||||
*
|
||||
* @param parent not used
|
||||
* @param viewType not used
|
||||
* @return null
|
||||
*/
|
||||
@Override
|
||||
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will not be called.
|
||||
* Override {@link #getGroupId(int)} and {@link #getChildId(int, int)} instead.
|
||||
*
|
||||
* @param position not used
|
||||
* @return {@link RecyclerView#NO_ID}
|
||||
*/
|
||||
@Override
|
||||
public final long getItemId(int position) {
|
||||
return RecyclerView.NO_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will not be called.
|
||||
* Override {@link #getGroupItemViewType(int)} and {@link #getChildItemViewType(int, int)} instead.
|
||||
*
|
||||
* @param position not used
|
||||
* @return 0
|
||||
*/
|
||||
@Override
|
||||
public final int getItemViewType(int position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will not be called.
|
||||
* Override {@link #getGroupCount()} and {@link #getChildCount(int)} instead.
|
||||
*
|
||||
* @return 0
|
||||
*/
|
||||
@Override
|
||||
public final int getItemCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will not be called.
|
||||
* Override {@link #onBindGroupViewHolder(RecyclerView.ViewHolder, int, int)} ()} and
|
||||
* {@link #onBindChildViewHolder(RecyclerView.ViewHolder, int, int, int)} instead.
|
||||
*
|
||||
* @param holder not used
|
||||
* @param position not used
|
||||
*/
|
||||
@Override
|
||||
public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if need to customize the behavior.
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean onHookGroupExpand(int groupPosition, boolean fromUser) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if need to customize the behavior.
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean onHookGroupCollapse(int groupPosition, boolean fromUser) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.utils;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.expandable.ExpandableItemViewHolder;
|
||||
|
||||
public abstract class AbstractExpandableItemViewHolder extends RecyclerView.ViewHolder implements ExpandableItemViewHolder {
|
||||
private int mExpandStateFlags;
|
||||
|
||||
public AbstractExpandableItemViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExpandStateFlags(int flags) {
|
||||
mExpandStateFlags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExpandStateFlags() {
|
||||
return mExpandStateFlags;
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.utils;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemViewHolder;
|
||||
|
||||
public abstract class AbstractSwipeableItemViewHolder extends RecyclerView.ViewHolder implements SwipeableItemViewHolder {
|
||||
private int mSwipeStateFlags;
|
||||
private int mSwipeResult = RecyclerViewSwipeManager.RESULT_NONE;
|
||||
private int mAfterSwipeReaction = RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_DEFAULT;
|
||||
private float mSwipeAmount;
|
||||
private float mMaxLeftSwipeAmount = RecyclerViewSwipeManager.OUTSIDE_OF_THE_WINDOW_LEFT;
|
||||
private float mMaxRightSwipeAmount = RecyclerViewSwipeManager.OUTSIDE_OF_THE_WINDOW_RIGHT;
|
||||
|
||||
public AbstractSwipeableItemViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSwipeStateFlags(int flags) {
|
||||
mSwipeStateFlags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSwipeStateFlags() {
|
||||
return mSwipeStateFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSwipeResult(int result) {
|
||||
mSwipeResult = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSwipeResult() {
|
||||
return mSwipeResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAfterSwipeReaction() {
|
||||
return mAfterSwipeReaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAfterSwipeReaction(int reaction) {
|
||||
mAfterSwipeReaction = reaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getSwipeItemSlideAmount() {
|
||||
return mSwipeAmount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSwipeItemSlideAmount(float amount) {
|
||||
mSwipeAmount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxLeftSwipeAmount(float amount) {
|
||||
mMaxLeftSwipeAmount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxLeftSwipeAmount() {
|
||||
return mMaxLeftSwipeAmount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxRightSwipeAmount(float amount) {
|
||||
mMaxRightSwipeAmount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxRightSwipeAmount() {
|
||||
return mMaxRightSwipeAmount;
|
||||
}
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.utils;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class BaseWrapperAdapter<VH extends RecyclerView.ViewHolder>
|
||||
extends RecyclerView.Adapter<VH> {
|
||||
private static final String TAG = "ARVBaseWrapperAdapter";
|
||||
private static final boolean LOCAL_LOGD = false;
|
||||
|
||||
private RecyclerView.Adapter<VH> mWrappedAdapter;
|
||||
private BridgeObserver mBridgeObserver;
|
||||
|
||||
public BaseWrapperAdapter(RecyclerView.Adapter<VH> adapter) {
|
||||
mWrappedAdapter = adapter;
|
||||
mBridgeObserver = new BridgeObserver<>(this);
|
||||
mWrappedAdapter.registerAdapterDataObserver(mBridgeObserver);
|
||||
super.setHasStableIds(mWrappedAdapter.hasStableIds());
|
||||
}
|
||||
|
||||
public void release() {
|
||||
onRelease();
|
||||
|
||||
if (mWrappedAdapter != null && mBridgeObserver != null) {
|
||||
mWrappedAdapter.unregisterAdapterDataObserver(mBridgeObserver);
|
||||
}
|
||||
|
||||
mWrappedAdapter = null;
|
||||
mBridgeObserver = null;
|
||||
}
|
||||
|
||||
protected void onRelease() {
|
||||
// override this method if needed
|
||||
}
|
||||
|
||||
public RecyclerView.Adapter<VH> getWrappedAdapter() {
|
||||
return mWrappedAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
|
||||
mWrappedAdapter.onAttachedToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
|
||||
mWrappedAdapter.onDetachedFromRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAttachedToWindow(VH holder) {
|
||||
mWrappedAdapter.onViewAttachedToWindow(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(VH holder) {
|
||||
mWrappedAdapter.onViewDetachedFromWindow(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRecycled(VH holder) {
|
||||
mWrappedAdapter.onViewRecycled(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHasStableIds(boolean hasStableIds) {
|
||||
super.setHasStableIds(hasStableIds);
|
||||
mWrappedAdapter.setHasStableIds(hasStableIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return mWrappedAdapter.onCreateViewHolder(parent, viewType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(VH holder, int position) {
|
||||
mWrappedAdapter.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mWrappedAdapter.getItemCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return mWrappedAdapter.getItemId(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return mWrappedAdapter.getItemViewType(position);
|
||||
}
|
||||
|
||||
protected void onHandleWrappedAdapterChanged() {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
protected void onHandleWrappedAdapterItemRangeChanged(int positionStart, int itemCount) {
|
||||
notifyItemRangeChanged(positionStart, itemCount);
|
||||
}
|
||||
|
||||
protected void onHandleWrappedAdapterItemRangeInserted(int positionStart, int itemCount) {
|
||||
notifyItemRangeInserted(positionStart, itemCount);
|
||||
}
|
||||
|
||||
protected void onHandleWrappedAdapterItemRangeRemoved(int positionStart, int itemCount) {
|
||||
notifyItemRangeRemoved(positionStart, itemCount);
|
||||
}
|
||||
|
||||
protected void onHandleWrappedAdapterRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||
if (itemCount != 1) {
|
||||
throw new IllegalStateException("itemCount should be always 1 (actual: " + itemCount + ")");
|
||||
}
|
||||
|
||||
notifyItemMoved(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
/*package*/ final void onWrappedAdapterChanged() {
|
||||
if (LOCAL_LOGD) {
|
||||
Log.d(TAG, "onWrappedAdapterChanged");
|
||||
}
|
||||
|
||||
onHandleWrappedAdapterChanged();
|
||||
}
|
||||
|
||||
/*package*/ final void onWrappedAdapterItemRangeChanged(int positionStart, int itemCount) {
|
||||
if (LOCAL_LOGD) {
|
||||
Log.d(TAG, "onWrappedAdapterItemRangeChanged(positionStart = " + positionStart + ", itemCount = " + itemCount + ")");
|
||||
}
|
||||
|
||||
onHandleWrappedAdapterItemRangeChanged(positionStart, itemCount);
|
||||
}
|
||||
|
||||
/*package*/ final void onWrappedAdapterItemRangeInserted(int positionStart, int itemCount) {
|
||||
if (LOCAL_LOGD) {
|
||||
Log.d(TAG, "onWrappedAdapterItemRangeInserted(positionStart = " + positionStart + ", itemCount = " + itemCount + ")");
|
||||
}
|
||||
|
||||
onHandleWrappedAdapterItemRangeInserted(positionStart, itemCount);
|
||||
}
|
||||
|
||||
/*package*/ final void onWrappedAdapterItemRangeRemoved(int positionStart, int itemCount) {
|
||||
if (LOCAL_LOGD) {
|
||||
Log.d(TAG, "onWrappedAdapterItemRangeRemoved(positionStart = " + positionStart + ", itemCount = " + itemCount + ")");
|
||||
}
|
||||
|
||||
onHandleWrappedAdapterItemRangeRemoved(positionStart, itemCount);
|
||||
}
|
||||
|
||||
/*package*/ final void onWrappedAdapterRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||
if (LOCAL_LOGD) {
|
||||
Log.d(TAG, "onWrappedAdapterRangeMoved(fromPosition = " + fromPosition + ", toPosition = " + toPosition + ", itemCount = " + itemCount + ")");
|
||||
}
|
||||
|
||||
onHandleWrappedAdapterRangeMoved(fromPosition, toPosition, itemCount);
|
||||
}
|
||||
|
||||
private static final class BridgeObserver<VH extends RecyclerView.ViewHolder> extends RecyclerView.AdapterDataObserver {
|
||||
private WeakReference<BaseWrapperAdapter<VH>> mRefHolder;
|
||||
|
||||
public BridgeObserver(BaseWrapperAdapter<VH> holder) {
|
||||
mRefHolder = new WeakReference<>(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged() {
|
||||
final BaseWrapperAdapter<VH> holder = mRefHolder.get();
|
||||
if (holder != null) {
|
||||
holder.onWrappedAdapterChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount) {
|
||||
final BaseWrapperAdapter<VH> holder = mRefHolder.get();
|
||||
if (holder != null) {
|
||||
holder.onWrappedAdapterItemRangeChanged(positionStart, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||
final BaseWrapperAdapter<VH> holder = mRefHolder.get();
|
||||
if (holder != null) {
|
||||
holder.onWrappedAdapterItemRangeInserted(positionStart, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(int positionStart, int itemCount) {
|
||||
final BaseWrapperAdapter<VH> holder = mRefHolder.get();
|
||||
if (holder != null) {
|
||||
holder.onWrappedAdapterItemRangeRemoved(positionStart, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||
final BaseWrapperAdapter<VH> holder = mRefHolder.get();
|
||||
|
||||
if (holder != null) {
|
||||
holder.onWrappedAdapterRangeMoved(fromPosition, toPosition, itemCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.utils;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class CustomRecyclerViewUtils {
|
||||
public static RecyclerView.ViewHolder findChildViewHolderUnderWithoutTranslation(RecyclerView rv, float x, float y) {
|
||||
final View child = findChildViewUnderWithoutTranslation(rv, x, y);
|
||||
return (child != null) ? rv.getChildViewHolder(child) : null;
|
||||
}
|
||||
|
||||
private static View findChildViewUnderWithoutTranslation(ViewGroup parent, float x, float y) {
|
||||
final int count = parent.getChildCount();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
final View child = parent.getChildAt(i);
|
||||
if (x >= child.getLeft() &&
|
||||
x <= child.getRight() &&
|
||||
y >= child.getTop() &&
|
||||
y <= child.getBottom()) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static RecyclerView.ViewHolder findChildViewHolderUnderWithTranslation(RecyclerView rv, float x, float y) {
|
||||
final View child = rv.findChildViewUnder(x, y);
|
||||
return (child != null) ? rv.getChildViewHolder(child) : null;
|
||||
}
|
||||
|
||||
public static Rect getLayoutMargins(View v, Rect outMargins) {
|
||||
final ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
|
||||
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
||||
final ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) layoutParams;
|
||||
outMargins.left = marginLayoutParams.leftMargin;
|
||||
outMargins.right = marginLayoutParams.rightMargin;
|
||||
outMargins.top = marginLayoutParams.topMargin;
|
||||
outMargins.bottom = marginLayoutParams.bottomMargin;
|
||||
} else {
|
||||
outMargins.left = outMargins.right = outMargins.top = outMargins.bottom = 0;
|
||||
}
|
||||
return outMargins;
|
||||
}
|
||||
|
||||
public static Rect getDecorationOffsets(RecyclerView.LayoutManager layoutManager, View view, Rect outDecorations) {
|
||||
outDecorations.left = layoutManager.getLeftDecorationWidth(view);
|
||||
outDecorations.right = layoutManager.getRightDecorationWidth(view);
|
||||
outDecorations.top = layoutManager.getTopDecorationHeight(view);
|
||||
outDecorations.bottom = layoutManager.getBottomDecorationHeight(view);
|
||||
|
||||
return outDecorations;
|
||||
}
|
||||
|
||||
public static Rect getViewBounds(View v, Rect outBounds) {
|
||||
outBounds.left = v.getLeft();
|
||||
outBounds.right = v.getRight();
|
||||
outBounds.top = v.getTop();
|
||||
outBounds.bottom = v.getBottom();
|
||||
return outBounds;
|
||||
}
|
||||
|
||||
|
||||
public static int findFirstVisibleItemPosition(RecyclerView rv) {
|
||||
RecyclerView.LayoutManager layoutManager = rv.getLayoutManager();
|
||||
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
return (((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition());
|
||||
} else {
|
||||
return RecyclerView.NO_POSITION;
|
||||
}
|
||||
}
|
||||
|
||||
public static int findLastVisibleItemPosition(RecyclerView rv) {
|
||||
RecyclerView.LayoutManager layoutManager = rv.getLayoutManager();
|
||||
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
return (((LinearLayoutManager) layoutManager).findLastVisibleItemPosition());
|
||||
} else {
|
||||
return RecyclerView.NO_POSITION;
|
||||
}
|
||||
}
|
||||
|
||||
public static int findFirstCompletelyVisibleItemPosition(RecyclerView rv) {
|
||||
RecyclerView.LayoutManager layoutManager = rv.getLayoutManager();
|
||||
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
return (((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition());
|
||||
} else {
|
||||
return RecyclerView.NO_POSITION;
|
||||
}
|
||||
}
|
||||
|
||||
public static int findLastCompletelyVisibleItemPosition(RecyclerView rv) {
|
||||
RecyclerView.LayoutManager layoutManager = rv.getLayoutManager();
|
||||
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
return (((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition());
|
||||
} else {
|
||||
return RecyclerView.NO_POSITION;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getSynchronizedPosition(RecyclerView.ViewHolder holder) {
|
||||
int pos1 = holder.getLayoutPosition();
|
||||
int pos2 = holder.getAdapterPosition();
|
||||
if (pos1 == pos2) {
|
||||
return pos1;
|
||||
} else {
|
||||
return RecyclerView.NO_POSITION;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.utils;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
|
||||
public class RecyclerViewAdapterUtils {
|
||||
private RecyclerViewAdapterUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets parent RecyclerView instance.
|
||||
* @param view Child view of the RecyclerView's item
|
||||
* @return Parent RecyclerView instance
|
||||
*/
|
||||
public static RecyclerView getParentRecyclerView(View view) {
|
||||
if (view == null) {
|
||||
return null;
|
||||
}
|
||||
ViewParent parent = view.getParent();
|
||||
if (parent instanceof RecyclerView) {
|
||||
return (RecyclerView) parent;
|
||||
} else if (parent instanceof View) {
|
||||
return getParentRecyclerView((View) parent);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets directly child of RecyclerView (== {@link android.support.v7.widget.RecyclerView.ViewHolder#itemView}})
|
||||
* @param view Child view of the RecyclerView's item
|
||||
* @return Item view
|
||||
*/
|
||||
public static View getParentViewHolderItemView(View view) {
|
||||
if (view == null) {
|
||||
return null;
|
||||
}
|
||||
ViewParent parent = view.getParent();
|
||||
if (parent instanceof RecyclerView) {
|
||||
return view;
|
||||
} else if (parent instanceof View) {
|
||||
return getParentViewHolderItemView((View) parent);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets {@link android.support.v7.widget.RecyclerView.ViewHolder}.
|
||||
* @param view Child view of the RecyclerView's item
|
||||
* @return ViewHolder
|
||||
*/
|
||||
public static RecyclerView.ViewHolder getViewHolder(View view) {
|
||||
RecyclerView rv = getParentRecyclerView(view);
|
||||
View rvChild = getParentViewHolderItemView(view);
|
||||
|
||||
if (rv != null && rvChild != null) {
|
||||
return rv.getChildViewHolder(rvChild);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Haruki Hasegawa
|
||||
*
|
||||
* 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.h6ah4i.android.widget.advrecyclerview.utils;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
public class WrapperAdapterUtils {
|
||||
|
||||
private WrapperAdapterUtils() {
|
||||
}
|
||||
|
||||
public static <T> T findWrappedAdapter(RecyclerView.Adapter adapter, Class<T> clazz) {
|
||||
if (clazz.isInstance(adapter)) {
|
||||
return clazz.cast(adapter);
|
||||
} else if (adapter instanceof BaseWrapperAdapter) {
|
||||
final RecyclerView.Adapter wrappedAdapter = ((BaseWrapperAdapter) adapter).getWrappedAdapter();
|
||||
return findWrappedAdapter(wrappedAdapter, clazz);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static RecyclerView.Adapter releaseAll(RecyclerView.Adapter adapter) {
|
||||
return releaseCyclically(adapter);
|
||||
}
|
||||
|
||||
private static RecyclerView.Adapter releaseCyclically(RecyclerView.Adapter adapter) {
|
||||
if (!(adapter instanceof BaseWrapperAdapter)) {
|
||||
return adapter;
|
||||
}
|
||||
|
||||
final BaseWrapperAdapter wrapperAdapter = (BaseWrapperAdapter) adapter;
|
||||
final RecyclerView.Adapter wrappedAdapter = wrapperAdapter.getWrappedAdapter();
|
||||
|
||||
wrapperAdapter.release();
|
||||
|
||||
return releaseCyclically(wrappedAdapter);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ public final class Constants {
|
||||
public static final int MESSENGER_ID_IMAGE_DOWNLOAD_LOCATION;
|
||||
public static final int MESSENGER_ID_ARCHIVE_DOWNLOAD_LOCATION;
|
||||
public static final int MESSENGER_ID_UPDATE_QUICK_SEARCH;
|
||||
public static final int MESSENGER_ID_UPDATE_QUICK_SEARCH_NAME;
|
||||
|
||||
static {
|
||||
Messenger messenger = Messenger.getInstance();
|
||||
@@ -41,5 +42,6 @@ public final class Constants {
|
||||
MESSENGER_ID_IMAGE_DOWNLOAD_LOCATION = messenger.newId();
|
||||
MESSENGER_ID_ARCHIVE_DOWNLOAD_LOCATION = messenger.newId();
|
||||
MESSENGER_ID_UPDATE_QUICK_SEARCH = messenger.newId();
|
||||
MESSENGER_ID_UPDATE_QUICK_SEARCH_NAME = messenger.newId();
|
||||
}
|
||||
}
|
||||
@@ -39,4 +39,12 @@ public class QuickSearch {
|
||||
quickSearch.minRating = quickSearchObj.getMinRating();
|
||||
return quickSearch;
|
||||
}
|
||||
|
||||
public void set(ListUrlBuilder builder) {
|
||||
mode = builder.getMode();
|
||||
category = builder.getCategory();
|
||||
keyword = builder.getKeyword();
|
||||
advancedSearch = builder.getAdvanceSearch();
|
||||
minRating = builder.getMinRating();
|
||||
}
|
||||
}
|
||||
@@ -557,10 +557,7 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener, M
|
||||
if (mLayoutOffset != 0) {
|
||||
return false;
|
||||
}
|
||||
if (mScroll) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return !mScroll;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,17 +27,17 @@ import android.view.WindowManager;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class ApiHelper {
|
||||
public static interface VERSION_CODES {
|
||||
public interface VERSION_CODES {
|
||||
// These value are copied from Build.VERSION_CODES
|
||||
public static final int GINGERBREAD_MR1 = 10;
|
||||
public static final int HONEYCOMB = 11;
|
||||
public static final int HONEYCOMB_MR1 = 12;
|
||||
public static final int HONEYCOMB_MR2 = 13;
|
||||
public static final int ICE_CREAM_SANDWICH = 14;
|
||||
public static final int ICE_CREAM_SANDWICH_MR1 = 15;
|
||||
public static final int JELLY_BEAN = 16;
|
||||
public static final int JELLY_BEAN_MR1 = 17;
|
||||
public static final int JELLY_BEAN_MR2 = 18;
|
||||
int GINGERBREAD_MR1 = 10;
|
||||
int HONEYCOMB = 11;
|
||||
int HONEYCOMB_MR1 = 12;
|
||||
int HONEYCOMB_MR2 = 13;
|
||||
int ICE_CREAM_SANDWICH = 14;
|
||||
int ICE_CREAM_SANDWICH_MR1 = 15;
|
||||
int JELLY_BEAN = 16;
|
||||
int JELLY_BEAN_MR1 = 17;
|
||||
int JELLY_BEAN_MR2 = 18;
|
||||
}
|
||||
|
||||
public static final boolean AT_LEAST_16 = Build.VERSION.SDK_INT >= 16;
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* 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.ui.scene;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.NinePatchDrawable;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.decoration.SimpleListDividerDecorator;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.rippleold.RippleSalon;
|
||||
import com.hippo.scene.AppbarScene;
|
||||
import com.hippo.widget.recyclerview.EasyRecyclerView;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbsDragSortScene extends AppbarScene {
|
||||
|
||||
private EasyRecyclerView mRecyclerView;
|
||||
private RecyclerView.LayoutManager mLayoutManager;
|
||||
private RecyclerView.Adapter mAdapter;
|
||||
private RecyclerView.Adapter mWrappedAdapter;
|
||||
private RecyclerViewDragDropManager mRecyclerViewDragDropManager;
|
||||
private RecyclerViewSwipeManager mRecyclerViewSwipeManager;
|
||||
private RecyclerViewTouchActionGuardManager mRecyclerViewTouchActionGuardManager;
|
||||
|
||||
private int mOriginalPaddingBottom;
|
||||
|
||||
@Override
|
||||
protected void onCreate(boolean rebirth) {
|
||||
super.onCreate(rebirth);
|
||||
setContentView(R.layout.scene_abs_drag_sort_scene);
|
||||
setTitle(getTitle());
|
||||
setIcon(R.drawable.ic_arrow_left_dark);
|
||||
|
||||
Resources resources = getStageActivity().getResources();
|
||||
|
||||
mRecyclerView = (EasyRecyclerView) findViewById(R.id.recycler_view);
|
||||
mOriginalPaddingBottom = mRecyclerView.getPaddingBottom();
|
||||
|
||||
// Layout Manager
|
||||
mLayoutManager = new LinearLayoutManager(getStageActivity());
|
||||
|
||||
// touch guard manager (this class is required to suppress scrolling while swipe-dismiss animation is running)
|
||||
mRecyclerViewTouchActionGuardManager = new RecyclerViewTouchActionGuardManager();
|
||||
mRecyclerViewTouchActionGuardManager.setInterceptVerticalScrollingWhileAnimationRunning(true);
|
||||
mRecyclerViewTouchActionGuardManager.setEnabled(true);
|
||||
|
||||
// drag & drop manager
|
||||
mRecyclerViewDragDropManager = new RecyclerViewDragDropManager();
|
||||
mRecyclerViewDragDropManager.setDraggingItemShadowDrawable(
|
||||
(NinePatchDrawable) resources.getDrawable(R.drawable.shadow_8dp));
|
||||
|
||||
// swipe manager
|
||||
mRecyclerViewSwipeManager = new RecyclerViewSwipeManager();
|
||||
|
||||
mAdapter = getAdapter();
|
||||
mWrappedAdapter = mRecyclerViewDragDropManager.createWrappedAdapter(mAdapter); // wrap for dragging
|
||||
mWrappedAdapter = mRecyclerViewSwipeManager.createWrappedAdapter(mWrappedAdapter); // wrap for swiping\
|
||||
|
||||
final GeneralItemAnimator animator = new SwipeDismissItemAnimator();
|
||||
|
||||
// Change animations are enabled by default since support-v7-recyclerview v22.
|
||||
// Disable the change animation in order to make turning back animation of swiped item works properly.
|
||||
animator.setSupportsChangeAnimations(false);
|
||||
|
||||
mRecyclerView.setLayoutManager(mLayoutManager);
|
||||
mRecyclerView.setAdapter(mWrappedAdapter); // requires *wrapped* adapter
|
||||
mRecyclerView.setItemAnimator(animator);
|
||||
mRecyclerView.setOnItemClickListener((EasyRecyclerView.OnItemClickListener) mAdapter);
|
||||
|
||||
// additional decorations
|
||||
mRecyclerView.addItemDecoration(new SimpleListDividerDecorator(resources.getDrawable(R.drawable.list_divider), true));
|
||||
|
||||
// additional selector
|
||||
mRecyclerView.setSelector(RippleSalon.generateRippleDrawable(false));
|
||||
mRecyclerView.setDrawSelectorOnTop(true);
|
||||
|
||||
// NOTE:
|
||||
// The initialization order is very important! This order determines the priority of touch event handling.
|
||||
//
|
||||
// priority: TouchActionGuard > Swipe > DragAndDrop
|
||||
mRecyclerViewTouchActionGuardManager.attachRecyclerView(mRecyclerView);
|
||||
mRecyclerViewSwipeManager.attachRecyclerView(mRecyclerView);
|
||||
mRecyclerViewDragDropManager.attachRecyclerView(mRecyclerView);
|
||||
}
|
||||
|
||||
public void notifyDataSetChanged() {
|
||||
if (mWrappedAdapter != null) {
|
||||
mWrappedAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIconClick() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onGetFitPaddingBottom(int b) {
|
||||
super.onGetFitPaddingBottom(b);
|
||||
mRecyclerView.setPadding(mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(),
|
||||
mRecyclerView.getPaddingRight(), mOriginalPaddingBottom + b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy(boolean die) {
|
||||
// TODO onClose
|
||||
if (mRecyclerViewDragDropManager != null) {
|
||||
mRecyclerViewDragDropManager.release();
|
||||
mRecyclerViewDragDropManager = null;
|
||||
}
|
||||
|
||||
if (mRecyclerViewSwipeManager != null) {
|
||||
mRecyclerViewSwipeManager.release();
|
||||
mRecyclerViewSwipeManager = null;
|
||||
}
|
||||
|
||||
if (mRecyclerViewTouchActionGuardManager != null) {
|
||||
mRecyclerViewTouchActionGuardManager.release();
|
||||
mRecyclerViewTouchActionGuardManager = null;
|
||||
}
|
||||
|
||||
if (mRecyclerView != null) {
|
||||
mRecyclerView.setItemAnimator(null);
|
||||
mRecyclerView.setAdapter(null);
|
||||
mRecyclerView = null;
|
||||
}
|
||||
|
||||
if (mWrappedAdapter != null) {
|
||||
WrapperAdapterUtils.releaseAll(mWrappedAdapter);
|
||||
mWrappedAdapter = null;
|
||||
}
|
||||
mAdapter = null;
|
||||
mLayoutManager = null;
|
||||
|
||||
super.onDestroy(die);
|
||||
}
|
||||
|
||||
protected abstract String getTitle();
|
||||
|
||||
protected abstract SortAdapter getAdapter();
|
||||
|
||||
private class DragSortHolder extends AbstractDraggableSwipeableItemViewHolder {
|
||||
|
||||
public ViewGroup contentContainer;
|
||||
public TextView textView;
|
||||
public View dragHandler;
|
||||
public ViewGroup background;
|
||||
public View leftDelete;
|
||||
public View rightDelete;
|
||||
|
||||
public DragSortHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
contentContainer = (ViewGroup) itemView.findViewById(R.id.content_container);
|
||||
textView = (TextView) contentContainer.findViewById(R.id.text_view);
|
||||
dragHandler = contentContainer.findViewById(R.id.drag_handler);
|
||||
background = (ViewGroup) itemView.findViewById(R.id.background);
|
||||
leftDelete = background.findViewById(R.id.delete_left);
|
||||
rightDelete = background.findViewById(R.id.delete_right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getSwipeableContainerView() {
|
||||
return contentContainer;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class SortAdapter<E> extends RecyclerView.Adapter<DragSortHolder>
|
||||
implements DraggableItemAdapter<DragSortHolder>,
|
||||
SwipeableItemAdapter<DragSortHolder>,
|
||||
EasyRecyclerView.OnItemClickListener {
|
||||
|
||||
private List<E> mList;
|
||||
|
||||
public SortAdapter(List<E> list) {
|
||||
mList = list;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public abstract String getString(E e, int position);
|
||||
|
||||
public abstract long getId(E e);
|
||||
|
||||
public E getData(int position) {
|
||||
return mList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DragSortHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return new DragSortHolder(getStageActivity().getLayoutInflater().inflate(R.layout.item_drag_sort, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(DragSortHolder holder, int position) {
|
||||
holder.textView.setText(getString(mList.get(position), position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getId(mList.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCheckCanStartDrag(DragSortHolder holder, int position, int x, int y) {
|
||||
return ViewUtils.isViewUnder(holder.dragHandler, x, y, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemDraggableRange onGetItemDraggableRange(DragSortHolder holder, int position) {
|
||||
// no drag-sortable range specified
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoveItem(int fromPosition, int toPosition) {
|
||||
if (fromPosition == toPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
// onMove must be first, ensure call getData inside
|
||||
onMove(fromPosition, toPosition);
|
||||
mList.add(toPosition, mList.remove(fromPosition));
|
||||
notifyItemMoved(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onGetSwipeReactionType(DragSortHolder holder, int position, int x, int y) {
|
||||
return RecyclerViewSwipeManager.REACTION_CAN_SWIPE_BOTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetSwipeBackground(DragSortHolder holder, int position, int type) {
|
||||
switch (type) {
|
||||
case RecyclerViewSwipeManager.DRAWABLE_SWIPE_LEFT_BACKGROUND:
|
||||
holder.background.setVisibility(View.VISIBLE);
|
||||
holder.leftDelete.setVisibility(View.INVISIBLE);
|
||||
holder.rightDelete.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
case RecyclerViewSwipeManager.DRAWABLE_SWIPE_RIGHT_BACKGROUND:
|
||||
holder.background.setVisibility(View.VISIBLE);
|
||||
holder.leftDelete.setVisibility(View.VISIBLE);
|
||||
holder.rightDelete.setVisibility(View.INVISIBLE);
|
||||
break;
|
||||
case RecyclerViewSwipeManager.DRAWABLE_SWIPE_NEUTRAL_BACKGROUND:
|
||||
holder.background.setVisibility(View.INVISIBLE);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onSwipeItem(DragSortHolder holder, int position, int result) {
|
||||
switch (result) {
|
||||
// remove
|
||||
case RecyclerViewSwipeManager.RESULT_SWIPED_RIGHT:
|
||||
case RecyclerViewSwipeManager.RESULT_SWIPED_LEFT:
|
||||
return RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_REMOVE_ITEM;
|
||||
// other --- do nothing
|
||||
case RecyclerViewSwipeManager.RESULT_CANCELED:
|
||||
default:
|
||||
return RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPerformAfterSwipeReaction(DragSortHolder holder, int position, int result, int reaction) {
|
||||
if (reaction == RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_REMOVE_ITEM) {
|
||||
// onRemove must be first, ensure call getData inside
|
||||
onRemove(position);
|
||||
mList.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void onMove(int fromPosition, int toPosition);
|
||||
|
||||
protected abstract void onRemove(int position);
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,8 @@ import android.text.TextUtils;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -77,9 +75,7 @@ import com.hippo.util.AnimationUtils;
|
||||
import com.hippo.widget.FabLayout;
|
||||
import com.hippo.widget.FloatingActionButton;
|
||||
import com.hippo.widget.recyclerview.EasyRecyclerView;
|
||||
import com.hippo.widget.recyclerview.LinearDividerItemDecoration;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.Messenger;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
@@ -151,7 +147,8 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
|
||||
private boolean mJpnTitle;
|
||||
|
||||
|
||||
private FrameLayout mRightDrawerView;
|
||||
private ViewGroup mRightDrawerView;
|
||||
private View mQuickSearchSettings;
|
||||
private EasyRecyclerView mQuickSearchView;
|
||||
private QuickSearchAdapter mQuickSearchAdapter;
|
||||
private List<QuickSearch> mQuickSearches;
|
||||
@@ -294,6 +291,20 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
|
||||
SearchBarMoveHelper sbmHelper = new SearchBarMoveHelper();
|
||||
mContentRecyclerView.addOnScrollListener(sbmHelper);
|
||||
mSearchLayout.addOnScrollListener(sbmHelper);
|
||||
mSearchLayout.setAction1Text(mResources.getString(R.string.search_add));
|
||||
mSearchLayout.setAction2Text(mResources.getString(R.string.search_mode));
|
||||
mSearchLayout.setHelper(this);
|
||||
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mSearchBar.getLayoutParams();
|
||||
ViewUtils.measureView(mSearchBar, 200, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
int fitPaddingTop = mSearchBar.getMeasuredHeight() + lp.topMargin;
|
||||
mContentLayout.setFitPaddingTop(fitPaddingTop);
|
||||
mSearchLayout.setFitPaddingTop(fitPaddingTop);
|
||||
mSearchBarOriginalTop = lp.topMargin;
|
||||
mSearchBarOriginalBottom = lp.topMargin + mSearchBar.getMeasuredHeight();
|
||||
|
||||
|
||||
/*
|
||||
mSearchBar.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
@@ -303,9 +314,15 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
|
||||
int fitPaddingTop = mSearchBar.getHeight() + mResources.getDimensionPixelOffset(R.dimen.search_bar_padding_vertical);
|
||||
mContentLayout.setFitPaddingTop(fitPaddingTop);
|
||||
mSearchLayout.setFitPaddingTop(fitPaddingTop);
|
||||
|
||||
Log.d("TAG", "fitPaddingTop = " + fitPaddingTop);
|
||||
Log.d("TAG", "mSearchBarOriginalTop = " + mSearchBarOriginalTop);
|
||||
Log.d("TAG", "mSearchBarOriginalBottom = " + mSearchBarOriginalBottom);
|
||||
}
|
||||
});
|
||||
mSearchLayout.setHelper(this);
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// Content Layout
|
||||
// onRebirth may create new GalleryListHelper
|
||||
@@ -332,22 +349,22 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
|
||||
mSearchDrawable = mResources.getDrawable(R.drawable.ic_search_dark);
|
||||
mCornerFab.setDrawable(mAddDeleteDrawable);
|
||||
|
||||
//
|
||||
mRightDrawerView = (FrameLayout) getStageActivity().getLayoutInflater().inflate(R.layout.drawer_right_gallery_list, null);
|
||||
mQuickSearchView = (EasyRecyclerView) mRightDrawerView.getChildAt(0);
|
||||
mRightDrawerView = (ViewGroup) getStageActivity().getLayoutInflater().inflate(R.layout.drawer_right_gallery_list, null);
|
||||
mQuickSearchSettings = mRightDrawerView.findViewById(R.id.settings);
|
||||
RippleSalon.addRipple(mQuickSearchSettings, true);
|
||||
mQuickSearchSettings.setOnClickListener(this);
|
||||
TextView rightDrawerTitle = (TextView) mRightDrawerView.findViewById(R.id.drawer_right_title);
|
||||
rightDrawerTitle.setText(R.string.quick_search);
|
||||
mQuickSearchView = (EasyRecyclerView) mRightDrawerView.findViewById(R.id.drawer_right_recycler_view);
|
||||
mQuickSearches = DBUtils.getAllQuickSearch();
|
||||
mQuickSearchAdapter = new QuickSearchAdapter();
|
||||
mQuickSearchView.setAdapter(mQuickSearchAdapter);
|
||||
mQuickSearchView.setLayoutManager(new LinearLayoutManager(getStageActivity()));
|
||||
mQuickSearchView.setSelector(RippleSalon.generateRippleDrawable(false));
|
||||
mQuickSearchView.setDrawSelectorOnTop(true);
|
||||
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
|
||||
LinearDividerItemDecoration.VERTICAL,
|
||||
mResources.getColor(R.color.divider_light),
|
||||
LayoutUtils.dp2pix(getStageActivity(), 1));
|
||||
decoration.setShowLastDivider(true);
|
||||
mQuickSearchView.addItemDecoration(decoration);
|
||||
mQuickSearchView.setOnItemClickListener(new QuickSearchClickListener());
|
||||
mQuickSearchView.hasFixedSize();
|
||||
mQuickSearchView.setClipToPadding(false);
|
||||
|
||||
// When scene start
|
||||
if (!rebirth) {
|
||||
@@ -401,6 +418,7 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
|
||||
mSearchLayout.setFitPaddingBottom(b);
|
||||
// Corner Fab
|
||||
mFabLayout.setPadding(mFabLayout.getPaddingLeft(), mFabLayout.getPaddingTop(), mFabLayout.getPaddingRight(), mCornerFabOriginalBottom + b);
|
||||
mQuickSearchView.setPadding(0, 0, 0, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -603,6 +621,8 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
|
||||
if (mGalleryListHelper.canGoTo()) {
|
||||
showGoToDialog();
|
||||
}
|
||||
} else if (v == mQuickSearchSettings) {
|
||||
startScene(QuickSearchScene.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -677,6 +697,7 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
|
||||
|
||||
setState(STATE_NORMAL);
|
||||
|
||||
showSearchBar(true);
|
||||
mGalleryListHelper.refresh();
|
||||
}
|
||||
|
||||
@@ -689,6 +710,10 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
|
||||
}
|
||||
|
||||
private void showSearchBar(boolean animation) {
|
||||
if (mSearchBar.isLayoutRequested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel old animator
|
||||
if (mSearchBarMoveAnimator != null) {
|
||||
mSearchBarMoveAnimator.cancel();
|
||||
@@ -736,6 +761,10 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
|
||||
}
|
||||
|
||||
private void returnSearchBarPosition() {
|
||||
if (mSearchBar.isLayoutRequested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean show;
|
||||
if (mState == STATE_SIMPLE_SEARCH || mState == STATE_SEARCH_SHOW_LIST) {
|
||||
show = true;
|
||||
@@ -824,9 +853,11 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
|
||||
String displayname = mEditText.getText().toString();
|
||||
if (!TextUtils.isEmpty(displayname)) {
|
||||
DBUtils.addQuickSearch(displayname, mBuilder);
|
||||
// Notify
|
||||
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_QUICK_SEARCH, null);
|
||||
return true;
|
||||
} else {
|
||||
Toast.makeText(getStageActivity(), "Empty displayname", Toast.LENGTH_SHORT).show(); // TODO
|
||||
Toast.makeText(getStageActivity(), "Empty displayname", Toast.LENGTH_SHORT).show(); // TODO hardcode
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -836,14 +867,23 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdd(ListUrlBuilder builder) {
|
||||
// Set keyword from search bar
|
||||
builder.setKeyword(mSearchBar.getText());
|
||||
AddToQuickSearchHelper helper = new AddToQuickSearchHelper(builder);
|
||||
new SimpleDialog.Builder(getStageActivity()).setTitle("Add to quick search")
|
||||
.setCustomView(R.layout.dialog_add_to_quick_search, helper)
|
||||
.setPositiveButton(android.R.string.ok)
|
||||
.setOnButtonClickListener(helper).show(this);
|
||||
public void onAction1() {
|
||||
if (mSearchLayout.getSearchMode() == SearchLayout.SEARCH_MODE_NORMAL) {
|
||||
ListUrlBuilder builder = new ListUrlBuilder();
|
||||
mSearchLayout.formatListUrlBuilder(builder);
|
||||
// Set keyword from search bar
|
||||
builder.setKeyword(mSearchBar.getText());
|
||||
AddToQuickSearchHelper helper = new AddToQuickSearchHelper(builder);
|
||||
new SimpleDialog.Builder(getStageActivity()).setTitle("Add to quick search") // TODO hardcode
|
||||
.setCustomView(R.layout.dialog_add_to_quick_search, helper)
|
||||
.setPositiveButton(android.R.string.ok)
|
||||
.setOnButtonClickListener(helper).show(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAction2() {
|
||||
mSearchLayout.toggleSearchMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -940,8 +980,7 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
|
||||
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
if (mState != STATE_SIMPLE_SEARCH && mState != STATE_SEARCH_SHOW_LIST &&
|
||||
isVaildView(recyclerView)) {
|
||||
if (isVaildView(recyclerView)) {
|
||||
|
||||
int oldBottom = mSearchBar.getBottom();
|
||||
int offsetYStep = MathUtils.clamp(-dy, -mSearchBar.getBottom(), mSearchBarOriginalTop - mSearchBar.getTop());
|
||||
@@ -1213,59 +1252,34 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
|
||||
}
|
||||
}
|
||||
|
||||
private static final int TYPE_SETTINGS = 0;
|
||||
private static final int TYPE_TEXT = 1;
|
||||
|
||||
private class QuickSearchAdapter extends RecyclerView.Adapter<SimpleHolder> {
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return position == 0 ? TYPE_SETTINGS : TYPE_TEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
case TYPE_SETTINGS:
|
||||
return new SimpleHolder(getStageActivity().getLayoutInflater().inflate(
|
||||
R.layout.item_quick_search_setting, parent, false));
|
||||
case TYPE_TEXT:
|
||||
return new SimpleHolder(getStageActivity().getLayoutInflater().inflate(
|
||||
R.layout.item_quick_search, parent, false));
|
||||
default:
|
||||
throw new IllegalStateException("Unknown view type");
|
||||
}
|
||||
return new SimpleHolder(getStageActivity().getLayoutInflater().inflate(
|
||||
R.layout.item_quick_search, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(SimpleHolder holder, int position) {
|
||||
if (getItemViewType(position) == TYPE_TEXT) {
|
||||
((TextView) holder.itemView).setText(mQuickSearches.get(position - 1).name);
|
||||
}
|
||||
((TextView) holder.itemView).setText(mQuickSearches.get(position).name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mQuickSearches.size() + 1;
|
||||
return mQuickSearches.size();
|
||||
}
|
||||
}
|
||||
|
||||
private class QuickSearchClickListener implements EasyRecyclerView.OnItemClickListener {
|
||||
@Override
|
||||
public boolean onItemClick(EasyRecyclerView parent, View view, int position, long id) {
|
||||
switch (parent.getAdapter().getItemViewType(position)) {
|
||||
case TYPE_SETTINGS:
|
||||
// TODO
|
||||
return true;
|
||||
case TYPE_TEXT:
|
||||
setState(STATE_NORMAL);
|
||||
mListUrlBuilder.set(mQuickSearches.get(position - 1));
|
||||
mGalleryListHelper.refresh();
|
||||
((ContentActivity) getStageActivity()).closeDrawers();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
setState(STATE_NORMAL);
|
||||
mListUrlBuilder.set(mQuickSearches.get(position));
|
||||
showSearchBar(true);
|
||||
mGalleryListHelper.refresh();
|
||||
((ContentActivity) getStageActivity()).closeDrawers();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* 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.ui.scene;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.text.TextUtils;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.hippo.ehviewer.Constants;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder;
|
||||
import com.hippo.ehviewer.client.data.QuickSearch;
|
||||
import com.hippo.ehviewer.util.DBUtils;
|
||||
import com.hippo.ehviewer.widget.SearchBar;
|
||||
import com.hippo.ehviewer.widget.SearchLayout;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.AppbarScene;
|
||||
import com.hippo.scene.Scene;
|
||||
import com.hippo.scene.SimpleDialog;
|
||||
import com.hippo.yorozuya.Messenger;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
public class ModifyQuickSearchScene extends AppbarScene implements SearchLayout.SearhLayoutHelper,
|
||||
SearchBar.Helper {
|
||||
|
||||
public static final String KEY_QUICK_SEARCH = "quick_search";
|
||||
|
||||
private SearchLayout mSearchLayout;
|
||||
|
||||
private QuickSearch mQuickSearch;
|
||||
private ListUrlBuilder mListUrlBuilder;
|
||||
private SearchBar mSearchBar;
|
||||
|
||||
private String mNewName;
|
||||
|
||||
@Override
|
||||
protected void onCreate(boolean rebirth) {
|
||||
super.onCreate(rebirth);
|
||||
setContentView(R.layout.scene_modify_quick_search);
|
||||
setIcon(R.drawable.ic_arrow_left_dark);
|
||||
|
||||
Resources resources = getStageActivity().getResources();
|
||||
|
||||
mSearchLayout = (SearchLayout) findViewById(R.id.search_layout);
|
||||
mSearchLayout.setSearchMode(SearchLayout.SEARCH_MODE_NORMAL, false);
|
||||
mSearchLayout.setAction1Text(resources.getString(R.string.rename));
|
||||
mSearchLayout.setAction2Text(resources.getString(android.R.string.ok));
|
||||
mSearchLayout.setHelper(this);
|
||||
|
||||
mSearchBar = (SearchBar) findViewById(R.id.search_bar);
|
||||
mSearchBar.setState(SearchBar.STATE_SEARCH, false);
|
||||
mSearchBar.setHelper(this);
|
||||
mSearchBar.setEditTextHint(resources.getString(R.string.keyword));
|
||||
|
||||
ViewUtils.measureView(mSearchBar, 200, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
mSearchLayout.setFitPaddingTop(mSearchBar.getMeasuredHeight() +
|
||||
((ViewGroup.MarginLayoutParams) mSearchBar.getLayoutParams()).topMargin);
|
||||
}
|
||||
|
||||
private String getName() {
|
||||
if (!TextUtils.isEmpty(mNewName)) {
|
||||
return mNewName;
|
||||
} else {
|
||||
return mQuickSearch.name;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBind() {
|
||||
super.onBind();
|
||||
|
||||
Announcer announcer = getAnnouncer();
|
||||
if (announcer != null) {
|
||||
QuickSearch quickSearch = announcer.getExtra(KEY_QUICK_SEARCH, QuickSearch.class);
|
||||
if (quickSearch != null) {
|
||||
mQuickSearch = quickSearch;
|
||||
mListUrlBuilder = new ListUrlBuilder();
|
||||
mListUrlBuilder.set(quickSearch);
|
||||
mSearchLayout.bind(mListUrlBuilder);
|
||||
mSearchBar.setText(quickSearch.keyword);
|
||||
setTitle(getName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestore() {
|
||||
super.onRestore();
|
||||
|
||||
setTitle(getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onGetFitPaddingBottom(int b) {
|
||||
super.onGetFitPaddingBottom(b);
|
||||
|
||||
mSearchLayout.setFitPaddingBottom(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mSearchBar.getState() == SearchBar.STATE_SEARCH_LIST) {
|
||||
mSearchBar.setState(SearchBar.STATE_SEARCH, true);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIconClick() {
|
||||
finish();
|
||||
}
|
||||
|
||||
private class RenameHelper implements SimpleDialog.OnCreateCustomViewListener,
|
||||
SimpleDialog.OnClickListener {
|
||||
|
||||
private EditText mEditText;
|
||||
|
||||
@Override
|
||||
public void onCreateCustomView(final SimpleDialog dialog, View view) {
|
||||
mEditText = (EditText) view.findViewById(R.id.edit_text);
|
||||
mEditText.setText(getName());
|
||||
mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_NULL) {
|
||||
dialog.pressPositiveButton();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onClick(SimpleDialog dialog, int which) {
|
||||
if (which == SimpleDialog.POSITIVE) {
|
||||
String displayname = mEditText.getText().toString();
|
||||
if (!TextUtils.isEmpty(displayname)) {
|
||||
mNewName = displayname;
|
||||
// Update title
|
||||
setTitle(getName());
|
||||
return true;
|
||||
} else {
|
||||
Toast.makeText(getStageActivity(), "Empty displayname", Toast.LENGTH_SHORT).show(); // TODO hardcode
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAction1() {
|
||||
RenameHelper helper = new RenameHelper();
|
||||
new SimpleDialog.Builder(getStageActivity()).setTitle(R.string.rename)
|
||||
.setCustomView(R.layout.dialog_add_to_quick_search, helper)
|
||||
.setPositiveButton(android.R.string.ok)
|
||||
.setOnButtonClickListener(helper).show(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAction2() {
|
||||
mSearchLayout.formatListUrlBuilder(mListUrlBuilder);
|
||||
mListUrlBuilder.setKeyword(mSearchBar.getText());
|
||||
mQuickSearch.set(mListUrlBuilder);
|
||||
|
||||
boolean newName = !TextUtils.isEmpty(mNewName);
|
||||
if (newName) {
|
||||
// New name
|
||||
mQuickSearch.name = mNewName;
|
||||
}
|
||||
DBUtils.modifyQuickSearch(mQuickSearch);
|
||||
|
||||
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_QUICK_SEARCH, null);
|
||||
if (newName) {
|
||||
// Notify
|
||||
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_QUICK_SEARCH_NAME, null);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChangeSearchMode() {
|
||||
// No change
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestSelectImage() {
|
||||
// No image search
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpecifyGallery(int gid, String token) {
|
||||
// No specify gallery
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scene getScene() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onClickTitle() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickMenu() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickArrow() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickAdvanceSearch() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchEditTextClick() {
|
||||
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplySearch(String query) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* 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.ui.scene;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.hippo.ehviewer.Constants;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.data.QuickSearch;
|
||||
import com.hippo.ehviewer.util.DBUtils;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.widget.recyclerview.EasyRecyclerView;
|
||||
import com.hippo.yorozuya.Messenger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class QuickSearchScene extends AbsDragSortScene implements Messenger.Receiver {
|
||||
|
||||
private boolean mChanged = false;
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
super.onInit();
|
||||
|
||||
Messenger.getInstance().register(Constants.MESSENGER_ID_UPDATE_QUICK_SEARCH_NAME, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDie() {
|
||||
super.onDie();
|
||||
|
||||
Messenger.getInstance().unregister(Constants.MESSENGER_ID_UPDATE_QUICK_SEARCH_NAME, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy(boolean die) {
|
||||
super.onDestroy(die);
|
||||
|
||||
// notify update
|
||||
if (mChanged) {
|
||||
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_QUICK_SEARCH, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return getStageActivity().getResources().getString(R.string.quick_search);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SortAdapter getAdapter() {
|
||||
return new QuickSearchAdapter(DBUtils.getAllQuickSearch());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(int id, Object obj) {
|
||||
if (Constants.MESSENGER_ID_UPDATE_QUICK_SEARCH_NAME == id) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private class QuickSearchAdapter extends SortAdapter<QuickSearch> {
|
||||
|
||||
public QuickSearchAdapter(List<QuickSearch> list) {
|
||||
super(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(QuickSearch quickSearch, int position) {
|
||||
return quickSearch.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId(QuickSearch quickSearch) {
|
||||
return quickSearch.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMove(int fromPosition, int toPosition) {
|
||||
mChanged = true;
|
||||
DBUtils.moveQuickSearch(getData(fromPosition).id, getData(toPosition).id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove(int position) {
|
||||
mChanged = true;
|
||||
DBUtils.removeQuickSearch(getData(position).id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemClick(EasyRecyclerView parent, View view, int position, long id) {
|
||||
Announcer announcer = new Announcer();
|
||||
announcer.putExtra(ModifyQuickSearchScene.KEY_QUICK_SEARCH, getData(position));
|
||||
startScene(ModifyQuickSearchScene.class, announcer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.hippo.ehviewer.Constants;
|
||||
import com.hippo.ehviewer.client.data.GalleryBase;
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder;
|
||||
import com.hippo.ehviewer.client.data.QuickSearch;
|
||||
@@ -32,7 +31,6 @@ import com.hippo.ehviewer.dao.GalleryBaseObj;
|
||||
import com.hippo.ehviewer.dao.GalleryBaseObjDao;
|
||||
import com.hippo.ehviewer.dao.QuickSearchObj;
|
||||
import com.hippo.ehviewer.dao.QuickSearchObjDao;
|
||||
import com.hippo.yorozuya.Messenger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -135,12 +133,34 @@ public class DBUtils {
|
||||
quickSearchObj.setMinRating(builder.getMinRating());
|
||||
quickSearchObj.setTime(System.currentTimeMillis());
|
||||
sDaoSession.getQuickSearchObjDao().insert(quickSearchObj);
|
||||
|
||||
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_QUICK_SEARCH, null);
|
||||
}
|
||||
|
||||
public static void removeQuickSearch(QuickSearch quickSearch) {
|
||||
sDaoSession.getQuickSearchObjDao().deleteByKey(quickSearch.id);
|
||||
public static void moveQuickSearch(long fromId, long toId) {
|
||||
QuickSearchObjDao dao = sDaoSession.getQuickSearchObjDao();
|
||||
QuickSearchObj from = dao.load(fromId);
|
||||
QuickSearchObj to = dao.load(toId);
|
||||
long fromTime = from.getTime();
|
||||
long toTime = to.getTime();
|
||||
from.setTime(toTime);
|
||||
to.setTime(fromTime);
|
||||
dao.update(from);
|
||||
dao.update(to);
|
||||
}
|
||||
|
||||
public static void modifyQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchObjDao dao = sDaoSession.getQuickSearchObjDao();
|
||||
QuickSearchObj obj = dao.load(quickSearch.id);
|
||||
obj.setName(quickSearch.name);
|
||||
obj.setMode(quickSearch.mode);
|
||||
obj.setCategory(quickSearch.category);
|
||||
obj.setKeyword(quickSearch.keyword);
|
||||
obj.setAdvancedSearch(quickSearch.advancedSearch);
|
||||
obj.setMinRating(quickSearch.minRating);
|
||||
dao.update(obj);
|
||||
}
|
||||
|
||||
public static void removeQuickSearch(long id) {
|
||||
sDaoSession.getQuickSearchObjDao().deleteByKey(id);
|
||||
}
|
||||
|
||||
public static List<QuickSearch> getAllQuickSearch() {
|
||||
|
||||
@@ -36,7 +36,7 @@ import com.hippo.widget.ProgressView;
|
||||
import com.hippo.widget.recyclerview.EasyRecyclerView;
|
||||
import com.hippo.widget.refreshlayout.RefreshLayout;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.IdGenerator;
|
||||
import com.hippo.yorozuya.IdIntGenerator;
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
import com.hippo.yorozuya.Say;
|
||||
|
||||
@@ -180,7 +180,7 @@ public class ContentLayout extends FrameLayout {
|
||||
*/
|
||||
private List<E> mData;
|
||||
|
||||
private IdGenerator mIdGenerator;
|
||||
private IdIntGenerator mIdGenerator;
|
||||
|
||||
/**
|
||||
* First shown page index
|
||||
@@ -256,7 +256,7 @@ public class ContentLayout extends FrameLayout {
|
||||
public ContentHelper(Context context) {
|
||||
mContext = context;
|
||||
mData = new ArrayList<>();
|
||||
mIdGenerator = new IdGenerator();
|
||||
mIdGenerator = new IdIntGenerator();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -93,4 +93,9 @@ public class DrawerRightPanel extends ViewGroup implements FitPaddingImpl {
|
||||
lp.topMargin = top;
|
||||
setLayoutParams(lp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchSetPressed(boolean pressed) {
|
||||
// disable dispatchSetPressed
|
||||
}
|
||||
}
|
||||
@@ -169,11 +169,7 @@ public final class PreviewLayout extends LinearLayout implements View.OnClickLis
|
||||
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
|
||||
}
|
||||
|
||||
if (Math.hypot(mTouchX - x, mTouchY - y) > mTouchSlop && Math.abs(mTouchX - x) > Math.abs(mTouchY - y)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return Math.hypot(mTouchX - x, mTouchY - y) > mTouchSlop && Math.abs(mTouchX - x) > Math.abs(mTouchY - y);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -171,6 +171,10 @@ public class SearchBar extends CardView implements View.OnClickListener,
|
||||
mHelper = helper;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
mEditText.setText(text);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return mEditText.getText().toString();
|
||||
}
|
||||
@@ -206,6 +210,10 @@ public class SearchBar extends CardView implements View.OnClickListener,
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
public void setState(int state) {
|
||||
setState(state, true);
|
||||
}
|
||||
@@ -218,7 +226,7 @@ public class SearchBar extends CardView implements View.OnClickListener,
|
||||
switch (oldState) {
|
||||
default:
|
||||
case STATE_NORMAL:
|
||||
mViewTransition.showView(1);
|
||||
mViewTransition.showView(1, animation);
|
||||
mEditText.requestFocus();
|
||||
mDrawerArrowDrawable.setArrow(animation ? ANIMATE_TIME : 0);
|
||||
mAddDeleteDrawable.setDelete(animation ? ANIMATE_TIME : 0);
|
||||
@@ -228,7 +236,7 @@ public class SearchBar extends CardView implements View.OnClickListener,
|
||||
break;
|
||||
case STATE_SEARCH:
|
||||
if (state == STATE_NORMAL) {
|
||||
mViewTransition.showView(0);
|
||||
mViewTransition.showView(0, animation);
|
||||
mDrawerArrowDrawable.setMenu(animation ? ANIMATE_TIME : 0);
|
||||
mAddDeleteDrawable.setAdd(animation ? ANIMATE_TIME : 0);
|
||||
} else if (state == STATE_SEARCH_LIST) {
|
||||
@@ -238,7 +246,7 @@ public class SearchBar extends CardView implements View.OnClickListener,
|
||||
case STATE_SEARCH_LIST:
|
||||
hideImeAndSuggestionsList(animation);
|
||||
if (state == STATE_NORMAL) {
|
||||
mViewTransition.showView(0);
|
||||
mViewTransition.showView(0, animation);
|
||||
mDrawerArrowDrawable.setMenu(animation ? ANIMATE_TIME : 0);
|
||||
mAddDeleteDrawable.setAdd(animation ? ANIMATE_TIME : 0);
|
||||
}
|
||||
|
||||
@@ -660,5 +660,5 @@ public class SearchItemAnimator extends RecyclerView.ItemAnimator {
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -42,14 +43,19 @@ import com.hippo.scene.Scene;
|
||||
import com.hippo.scene.SimpleDialog;
|
||||
import com.hippo.widget.recyclerview.EasyRecyclerView;
|
||||
import com.hippo.widget.recyclerview.MonoRecyclerView;
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
// TODO requst returnSearchBarPosition when content of recycler changed
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
public class SearchLayout extends MonoRecyclerView implements CompoundButton.OnCheckedChangeListener,
|
||||
View.OnClickListener, SelectSearchImageLayout.SelectSearchImageLayoutHelper,
|
||||
SpecifyGalleryLayout.SpecifyGalleryHelper {
|
||||
|
||||
@IntDef({SEARCH_MODE_NORMAL, SEARCH_MODE_IMAGE, SEARCH_MODE_SPECIFY})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface SearchMode {}
|
||||
|
||||
private static final String STATE_KEY_SUPER = "super";
|
||||
private static final String STATE_KEY_SEARCH_MODE = "search_mode";
|
||||
private static final String STATE_KEY_ENABLE_ADVANCE = "enable_advance";
|
||||
@@ -134,8 +140,9 @@ public class SearchLayout extends MonoRecyclerView implements CompoundButton.OnC
|
||||
|
||||
// Search Container
|
||||
mSearchPaddingTopOrigin = getPaddingTop();
|
||||
// TODO SearchLayout don't know fab, move 72dp to list scene
|
||||
// Original padding bottom and the padding bottom to make it above fab
|
||||
mSearchPaddingBottomOrigin = getPaddingBottom() + LayoutUtils.dp2pix(context, 72); // TODO
|
||||
mSearchPaddingBottomOrigin = getPaddingBottom(); // TODO
|
||||
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), mSearchPaddingBottomOrigin);
|
||||
|
||||
// Create normal view
|
||||
@@ -174,14 +181,20 @@ public class SearchLayout extends MonoRecyclerView implements CompoundButton.OnC
|
||||
mAction2 = (TextView) mActionView.findViewById(R.id.search_action_2);
|
||||
|
||||
// Init action view
|
||||
mAction1.setText(mContext.getString(R.string.search_add));
|
||||
mAction2.setText(mContext.getString(R.string.search_mode));
|
||||
RippleSalon.addRipple(mAction1, false);
|
||||
RippleSalon.addRipple(mAction2, false);
|
||||
mAction1.setOnClickListener(SearchLayout.this);
|
||||
mAction2.setOnClickListener(SearchLayout.this);
|
||||
}
|
||||
|
||||
public void setAction1Text(String action1) {
|
||||
mAction1.setText(action1);
|
||||
}
|
||||
|
||||
public void setAction2Text(String action2) {
|
||||
mAction2.setText(action2);
|
||||
}
|
||||
|
||||
public int getSearchMode() {
|
||||
return mSearchMode;
|
||||
}
|
||||
@@ -227,6 +240,29 @@ public class SearchLayout extends MonoRecyclerView implements CompoundButton.OnC
|
||||
}
|
||||
}
|
||||
|
||||
public void bind(ListUrlBuilder builder) {
|
||||
int mode = builder.getMode();
|
||||
if (mode == ListUrlBuilder.MODE_TAG) {
|
||||
mCheckSpecifyTag.setChecked(true);
|
||||
} else if (mode == ListUrlBuilder.MODE_NORMAL || mode == ListUrlBuilder.MODE_UPLOADER) {
|
||||
if (mode == ListUrlBuilder.MODE_UPLOADER) {
|
||||
mCheckSpecifyAuthor.setChecked(true);
|
||||
}
|
||||
mTableCategory.setCategory(builder.getCategory());
|
||||
int advanced = builder.getAdvanceSearch();
|
||||
if (advanced != -1) {
|
||||
mEnableAdvance = true;
|
||||
mSwitchEnableAdvance.setChecked(true);
|
||||
mTableAdvanceSearch.setAdvanceSearch(advanced);
|
||||
mTableAdvanceSearch.setMinRating(builder.getMinRating());
|
||||
} else {
|
||||
mEnableAdvance = false;
|
||||
mSwitchEnableAdvance.setChecked(false);
|
||||
}
|
||||
}
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void scrollSearchContainerToTop() {
|
||||
mLayoutManager.scrollToPositionWithOffset(0, 0);
|
||||
}
|
||||
@@ -287,7 +323,26 @@ public class SearchLayout extends MonoRecyclerView implements CompoundButton.OnC
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleSearchMode() {
|
||||
public void setSearchMode(@SearchMode int searchMode, boolean animation) {
|
||||
if (mSearchMode != searchMode) {
|
||||
int oldItemCount = mAdapter.getItemCount();
|
||||
mSearchMode = searchMode;
|
||||
int newItemCount = mAdapter.getItemCount();
|
||||
|
||||
if (animation) {
|
||||
mAdapter.notifyItemRangeRemoved(0, oldItemCount - 1);
|
||||
mAdapter.notifyItemRangeInserted(0, newItemCount - 1);
|
||||
} else {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
if (mHelper != null) {
|
||||
mHelper.onChangeSearchMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void toggleSearchMode() {
|
||||
int oldItemCount = mAdapter.getItemCount();
|
||||
|
||||
mSearchMode++;
|
||||
@@ -308,14 +363,9 @@ public class SearchLayout extends MonoRecyclerView implements CompoundButton.OnC
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v == mAction1) {
|
||||
if (mSearchMode == SEARCH_MODE_NORMAL) {
|
||||
ListUrlBuilder builder = new ListUrlBuilder();
|
||||
formatListUrlBuilder(builder);
|
||||
mHelper.onAdd(builder);
|
||||
}
|
||||
// TODO add quick search
|
||||
mHelper.onAction1();
|
||||
} else if (v == mAction2) {
|
||||
toggleSearchMode();
|
||||
mHelper.onAction2();
|
||||
} else if (v == mSearchTagHelp) {
|
||||
int[] center = new int [2];
|
||||
ViewUtils.getCenterInAncestor(mSearchTagHelp, center, R.id.stage);
|
||||
@@ -417,7 +467,9 @@ public class SearchLayout extends MonoRecyclerView implements CompoundButton.OnC
|
||||
}
|
||||
|
||||
public interface SearhLayoutHelper {
|
||||
void onAdd(ListUrlBuilder builder);
|
||||
void onAction1();
|
||||
|
||||
void onAction2();
|
||||
|
||||
void onChangeSearchMode();
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.hippo.yorozuya.IdGenerator;
|
||||
import com.hippo.yorozuya.IdIntGenerator;
|
||||
import com.hippo.yorozuya.Say;
|
||||
import com.squareup.leakcanary.LeakCanary;
|
||||
import com.squareup.leakcanary.RefWatcher;
|
||||
@@ -29,7 +29,7 @@ public class SceneApplication extends Application {
|
||||
private static final String TAG = SceneApplication.class.getSimpleName();
|
||||
|
||||
private SparseArray<SceneManager> mSceneManagers;
|
||||
private IdGenerator mIdGenerator;
|
||||
private IdIntGenerator mIdGenerator;
|
||||
|
||||
private RefWatcher mRefWatcher;
|
||||
|
||||
@@ -38,7 +38,7 @@ public class SceneApplication extends Application {
|
||||
super.onCreate();
|
||||
|
||||
mSceneManagers = new SparseArray<>();
|
||||
mIdGenerator = new IdGenerator();
|
||||
mIdGenerator = new IdIntGenerator();
|
||||
|
||||
mRefWatcher = LeakCanary.install(this);
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public class SceneDialogView extends SceneView implements View.OnClickListener,
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v == this && !ViewUtils.isViewUnder(((ViewGroup) getChildAt(0)).getChildAt(0), (int) mHostpotX, (int) mHostpotY) && mOnClickOutOfDialogListener != null) {
|
||||
if (v == this && !ViewUtils.isViewUnder(((ViewGroup) getChildAt(0)).getChildAt(0), (int) mHostpotX, (int) mHostpotY, 0) &&mOnClickOutOfDialogListener != null) {
|
||||
mOnClickOutOfDialogListener.onClickOutOfDialog((int) mHostpotX, (int) mHostpotY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.IdGenerator;
|
||||
import com.hippo.yorozuya.IdIntGenerator;
|
||||
import com.hippo.yorozuya.Say;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -44,7 +44,7 @@ public class SceneManager {
|
||||
private Set<Scene> mLegacySceneSet = new HashSet<>();
|
||||
private Set<Scene> mPrepareToDieSceneSet = new HashSet<>();
|
||||
|
||||
private IdGenerator mIdGenerator = new IdGenerator();
|
||||
private IdIntGenerator mIdGenerator = new IdIntGenerator();
|
||||
|
||||
private List<SceneStateListener> mSceneStateListenerList = new ArrayList<>();
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
|
||||
import com.hippo.app.StatsActivity;
|
||||
import com.hippo.yorozuya.IdGenerator;
|
||||
import com.hippo.yorozuya.IdIntGenerator;
|
||||
import com.hippo.yorozuya.Say;
|
||||
|
||||
public abstract class StageActivity extends StatsActivity {
|
||||
@@ -33,7 +33,7 @@ public abstract class StageActivity extends StatsActivity {
|
||||
|
||||
private static final String KEY_SCENEN_MANAGER_ID = "scene_manager_id";
|
||||
|
||||
private IdGenerator mActivityResultIdGenerator = new IdGenerator();
|
||||
private IdIntGenerator mActivityResultIdGenerator = new IdIntGenerator();
|
||||
private SparseArray<Scene.ActivityResultListener> mActivityResultListenerMap =
|
||||
new SparseArray<>();
|
||||
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* 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.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.drawable.NinePatchDrawable;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
|
||||
public class ShadowLinearLayout extends LinearLayout {
|
||||
|
||||
private NinePatchDrawable mShadow;
|
||||
private Rect mShadowPaddings = new Rect();
|
||||
private Rect mClipRect = new Rect();
|
||||
|
||||
public ShadowLinearLayout(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public ShadowLinearLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public ShadowLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
// TODO not only 2dp
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
setOutlineProvider(ViewOutlineProvider.BOUNDS);
|
||||
setElevation(LayoutUtils.dp2pix(context, 2));
|
||||
} else {
|
||||
setShadow((NinePatchDrawable) context.getResources().getDrawable(R.drawable.shadow_2dp));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutlineProvider(ViewOutlineProvider provider) {
|
||||
super.setOutlineProvider(provider);
|
||||
}
|
||||
|
||||
private void setShadow(NinePatchDrawable shadow) {
|
||||
mShadow = shadow;
|
||||
mShadow.getPadding(mShadowPaddings);
|
||||
updateShadowBounds();
|
||||
setWillNotDraw(false);
|
||||
}
|
||||
|
||||
private void updateShadowBounds() {
|
||||
NinePatchDrawable shadow = mShadow;
|
||||
if (shadow == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
Rect paddings = mShadowPaddings;
|
||||
if (width != 0 && height != 0) {
|
||||
shadow.setBounds(-paddings.left, -paddings.top, width + paddings.right, height + paddings.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
updateShadowBounds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
if (mShadow != null) {
|
||||
boolean needClip = false;
|
||||
Rect shadowPaddings = mShadowPaddings;
|
||||
Rect clipRect = mClipRect;
|
||||
canvas.getClipBounds(clipRect);
|
||||
|
||||
if (-shadowPaddings.left < clipRect.left) {
|
||||
needClip = true;
|
||||
clipRect.left = -shadowPaddings.left;
|
||||
}
|
||||
if (-shadowPaddings.top < clipRect.top) {
|
||||
needClip = true;
|
||||
clipRect.top = -shadowPaddings.top;
|
||||
}
|
||||
if (getWidth() + shadowPaddings.right > clipRect.right) {
|
||||
needClip = true;
|
||||
clipRect.right = getWidth() + shadowPaddings.right;
|
||||
}
|
||||
if (getHeight() + shadowPaddings.bottom > clipRect.bottom) {
|
||||
needClip = true;
|
||||
clipRect.bottom = getHeight() + shadowPaddings.bottom;
|
||||
}
|
||||
|
||||
int saved = 0;
|
||||
if (needClip) {
|
||||
saved = canvas.save();
|
||||
canvas.clipRect(clipRect, Region.Op.REPLACE);
|
||||
}
|
||||
|
||||
mShadow.draw(canvas);
|
||||
|
||||
if (needClip && saved != 0) {
|
||||
canvas.restoreToCount(saved);
|
||||
}
|
||||
}
|
||||
super.draw(canvas);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 Hippo Seven
|
||||
|
||||
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.
|
||||
-->
|
||||
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0"
|
||||
android:duration="300" />
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 Hippo Seven
|
||||
|
||||
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.
|
||||
-->
|
||||
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0.0"
|
||||
android:duration="300" />
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 258 B |
Binary file not shown.
|
After Width: | Height: | Size: 981 B |
+11
-10
@@ -1,14 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2015 Hippo Seven
|
||||
~ Copyright 2015 Hippo Seven
|
||||
~
|
||||
~ 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
|
||||
~ 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.
|
||||
@@ -19,21 +20,21 @@
|
||||
xmlns:auto="http://schemas.android.com/apk/res-auto"
|
||||
tools:targetApi="21"
|
||||
|
||||
auto:width="120dp"
|
||||
auto:height="120dp"
|
||||
auto:width="32dp"
|
||||
auto:height="32dp"
|
||||
auto:viewportWidth="24"
|
||||
auto:viewportHeight="24"
|
||||
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
auto:pathData="@string/pd_cursor_pointer"
|
||||
auto:fillColor="@color/secondary_drawable_light"
|
||||
auto:pathData="@string/pd_delete"
|
||||
auto:fillColor="@color/primary_drawable_dark"
|
||||
|
||||
android:pathData="@string/pd_cursor_pointer"
|
||||
android:fillColor="@color/secondary_drawable_light"/>
|
||||
android:pathData="@string/pd_delete"
|
||||
android:fillColor="@color/primary_drawable_dark"/>
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2015 Haruki Hasegawa
|
||||
|
||||
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.
|
||||
-->
|
||||
<shape
|
||||
android:shape="rectangle"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<size android:height="1dp"/>
|
||||
<solid android:color="#20000000"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2015 Hippo Seven
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/appbar_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:elevation="4dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/drawer_right_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/text_medium"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<com.hippo.widget.SimpleImageView
|
||||
android:id="@+id/settings"
|
||||
android:layout_width="@dimen/appbar_height"
|
||||
android:layout_height="@dimen/appbar_height"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:padding="@dimen/appbar_icon_margin"
|
||||
android:src="@drawable/ic_settings_dark"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<com.hippo.widget.recyclerview.EasyRecyclerView
|
||||
android:id="@+id/drawer_right_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -19,8 +19,8 @@
|
||||
xmlns:auto="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="@dimen/keyline_margin"
|
||||
android:paddingRight="@dimen/keyline_margin"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
auto:labelAppearance="@style/TextAppearance.FloatLabelEditText.Lable">
|
||||
|
||||
<EditText
|
||||
|
||||
@@ -14,14 +14,58 @@
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<FrameLayout
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.hippo.widget.recyclerview.EasyRecyclerView
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
android:layout_height="@dimen/appbar_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:background="?attr/colorPrimary">
|
||||
|
||||
</FrameLayout>
|
||||
<TextView
|
||||
android:id="@+id/drawer_right_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/text_medium"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<com.hippo.widget.SimpleImageView
|
||||
android:id="@+id/settings"
|
||||
android:layout_width="@dimen/appbar_height"
|
||||
android:layout_height="@dimen/appbar_height"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:padding="@dimen/appbar_icon_margin"
|
||||
android:src="@drawable/ic_settings_dark"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="4dp"
|
||||
android:background="@drawable/drop_shadow"/>
|
||||
|
||||
<com.hippo.widget.recyclerview.EasyRecyclerView
|
||||
android:id="@+id/drawer_right_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2015 Hippo Seven
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/item_drag_sort_height">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/material_red_500">
|
||||
|
||||
<com.hippo.widget.SimpleImageView
|
||||
android:id="@+id/delete_left"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/ic_delete_dark_x32"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<com.hippo.widget.SimpleImageView
|
||||
android:id="@+id/delete_right"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/ic_delete_dark_x32"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.hippo.widget.ShadowLinearLayout
|
||||
android:id="@+id/content_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@android:color/white"
|
||||
android:textSize="@dimen/text_little_small"
|
||||
android:textColor="@color/primary_text_light"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/drag_handler"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/material_pink_300"/>
|
||||
|
||||
</com.hippo.widget.ShadowLinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -19,8 +19,9 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:minHeight="48dp"
|
||||
android:textColor="@color/secondary_text_light"
|
||||
android:textSize="@dimen/text_little_small"
|
||||
android:background="@android:color/white"/>
|
||||
+11
-14
@@ -14,19 +14,16 @@
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<FrameLayout
|
||||
<com.hippo.widget.recyclerview.EasyRecyclerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:minHeight="48dp"
|
||||
android:background="@android:color/white">
|
||||
|
||||
<com.hippo.widget.SimpleImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="right|center_vertical"
|
||||
android:src="@drawable/ic_settings"/>
|
||||
|
||||
</FrameLayout>
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/keyline_margin_horizontal"
|
||||
android:paddingRight="@dimen/keyline_margin_horizontal"
|
||||
android:paddingTop="@dimen/keyline_margin_vertical"
|
||||
android:paddingBottom="@dimen/keyline_margin_vertical"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:scrollbarStyle="outsideOverlay"
|
||||
android:scrollbars="vertical"/>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2015 Hippo Seven
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:auto="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.hippo.ehviewer.widget.SearchLayout
|
||||
android:id="@+id/search_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
auto:space="@dimen/search_layout_space"
|
||||
auto:spaceLeft="@dimen/search_layout_space_horizontal"
|
||||
auto:spaceRight="@dimen/search_layout_space_horizontal"/>
|
||||
|
||||
<com.hippo.ehviewer.widget.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/search_bar_padding_horizontal"
|
||||
android:layout_marginRight="@dimen/search_bar_padding_horizontal"
|
||||
android:layout_marginTop="@dimen/search_bar_padding_vertical"
|
||||
auto:cardElevation="4dp"
|
||||
auto:forceNoElevation="true"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
<!-- Appbar Scene -->
|
||||
<dimen name="appbar_height">64dp</dimen>
|
||||
<!-- icon 24dp -->
|
||||
<dimen name="appbar_icon_margin">20dp</dimen>
|
||||
|
||||
<!-- Snackbar -->
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
<color name="misc">#fff06292</color>
|
||||
<color name="unknown_bg">@android:color/black</color>
|
||||
|
||||
<color name="material_red_500">#fff44336</color>
|
||||
<color name="material_pink_300">#fff06292</color>
|
||||
<color name="material_pink_500">#ffe91e63</color>
|
||||
<color name="material_teal_500">#ff009688</color>
|
||||
<color name="material_teal_700">#ff00796b</color>
|
||||
|
||||
@@ -42,11 +42,15 @@
|
||||
|
||||
<!-- Appbar Scene -->
|
||||
<dimen name="appbar_height">56dp</dimen>
|
||||
<!-- icon 24dp -->
|
||||
<dimen name="appbar_icon_margin">16dp</dimen>
|
||||
|
||||
<!-- DrawerList -->
|
||||
<dimen name="drawer_list_drawable_size">24dp</dimen>
|
||||
|
||||
<!-- item_drag_sort -->
|
||||
<dimen name="item_drag_sort_height">56dp</dimen>
|
||||
|
||||
<!-- RatingView -->
|
||||
<dimen name="rating_size">16dp</dimen>
|
||||
<dimen name="rating_interval">1dp</dimen>
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
<string name="pd_refresh" translatable="false">M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z</string>
|
||||
<string name="pd_url" translatable="false">M11.6,7v7.1c0,1-0.3,1.8-0.9,2.4c-0.6,0.6-1.5,1-2.5,1.1l-0.4,0c-1.1,0-2.1-0.3-2.7-0.9c-0.7-0.6-1-1.5-1-2.6V7h1.4v7.1c0,0.8,0.2,1.3,0.6,1.8c0.4,0.4,1,0.6,1.8,0.6c0.8,0,1.4-0.2,1.8-0.6c0.4-0.4,0.6-1,0.6-1.8V7H11.6M17.3,10.9c-0.2,0-0.4-0.1-0.7-0.1c-0.9,0-1.5,0.4-1.8,1.1v5.5h-1.3V9.7h1.3l0,0.9c0.4-0.7,1.1-1,1.9-1c0.3,0,0.5,0,0.6,0.1V10.9M20,17.5h-1.3V6.4H20V17.5z</string>
|
||||
<string name="pd_book_open" translatable="false">M11,19V9A2,2 0 0,0 9,7H5V17H9A2,2 0 0,1 11,19M13,9V19A2,2 0 0,1 15,17H19V7H15A2,2 0 0,0 13,9M21,19H15A2,2 0 0,0 13,21H11A2,2 0 0,0 9,19H3V5H9A2,2 0 0,1 11,7H13A2,2 0 0,1 15,5H21V19Z</string>
|
||||
<string name="pd_delete" translatable="false">M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z</string>
|
||||
|
||||
<string name="pd_slider_bubble" translatable="false">M13,0c7.2,0,13,5.9,13,13.2c0,3.7-2,7.6-7.3,12.9L13,32l-5.8-5.8C2,20.8,0,16.9,0,13.3C0,5.9,5.8,0,13,0z</string>
|
||||
|
||||
|
||||
@@ -71,6 +71,9 @@
|
||||
<string name="star_4">4 stars</string>
|
||||
<string name="star_5">5 stars</string>
|
||||
|
||||
<!-- Quick search -->
|
||||
<string name="quick_search">Quick search</string>
|
||||
|
||||
<!-- Go to -->
|
||||
<string name="go_to_page_info">Page %1$d, %2$s pages in total</string>
|
||||
<string name="go_to_string">Go to</string>
|
||||
@@ -244,5 +247,6 @@
|
||||
<string name="info">Info</string>
|
||||
|
||||
<string name="document">Document</string>
|
||||
<string name="rename">Rename</string>
|
||||
|
||||
</resources>
|
||||
+1
-1
@@ -5,7 +5,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle-experimental:0.2.0'
|
||||
classpath 'com.android.tools.build:gradle-experimental:0.2.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
||||
+1
-1
Submodule conaco updated: 1fe2dc16c0...6bd8937391.
+1
-1
Submodule yorozuya updated: 86de3b869d...cd4d1b981c.
Reference in new issue
Block a user