updated versions of Android-related libraries to better align Eclipse debug experience with production code

Change-Id: I4b5367f3569a25e2aa446d399d72f32316594a31
This commit is contained in:
Axel Uhl committed 2016-04-19 18:50:04 +02:00
1 parent 8908b06424
commit 21cd231707
655 files changed
+82955 -16714

No files matched your search

@@ -12,5 +12,6 @@
<classpathentry combineaccessrules="false" exported="true" kind="src" path="/com.sap.sse.shared.android"/>
<classpathentry kind="lib" path="/google-play-services_lib/libs/google-play-services.jar"/>
<classpathentry combineaccessrules="false" exported="true" kind="src" path="/org.json.simple"/>
<classpathentry exported="true" kind="var" path="ANDROID_HOME/extras/android/m2repository/com/android/support/support-annotations/22.2.1/support-annotations-22.2.1.jar"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
@@ -16,3 +16,5 @@ android.library=true
android.library.reference.1=../google-android-support-v7-appcompat_lib
android.library.reference.2=../licensesdialog_lib/de.psdev.licensesdialog
android.library.reference.3=../google-android-support-v4
@@ -9,5 +9,6 @@
<classpathentry combineaccessrules="false" kind="src" path="/org.json.simple"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="lib" path="/com.sap.sailing.targetplatform.base/plugins/target-test/mockito-core-1.10.14.jar"/>
<classpathentry kind="var" path="ANDROID_HOME/extras/android/m2repository/com/android/support/support-annotations/22.2.1/support-annotations-22.2.1.jar"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
@@ -17,3 +17,4 @@ android.library.reference.2=../com.sap.sailing.android.shared
android.library.reference.3=../google-android-support-v7-recyclerview_lib
android.library.reference.4=../github-advrecyclerview_lib
android.library.reference.5=../licensesdialog_lib/de.psdev.licensesdialog
android.library.reference.6=../google-android-support-v13
@@ -5,5 +5,8 @@
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry combineaccessrules="false" kind="src" path="/google-android-support-v4"/>
<classpathentry combineaccessrules="false" kind="src" path="/google-android-support-v7-appcompat_lib"/>
<classpathentry combineaccessrules="false" kind="src" path="/google-android-support-v7-recyclerview_lib"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
@@ -12,6 +12,4 @@
# Project target.
target=android-22
android.library.reference.1=../google-android-support-v7-recyclerview_lib
android.library=true
android.library.reference.2=../google-android-support-v7-appcompat_lib
@@ -1,151 +0,0 @@
/*
* 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;
}
}
@@ -1,285 +0,0 @@
/*
* 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;
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.getPosition() + ")");
}
return mRemoveAnimationManager.addPendingAnimation(holder);
}
@Override
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
if (mDebug) {
Log.d(TAG, "animateAdd(id = " + holder.getItemId() + ", position = " + holder.getPosition() + ")");
}
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.getPosition() + ", 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.getPosition()) : "-";
final String newId = (newHolder != null) ? Long.toString(newHolder.getItemId()) : "-";
final String newPosition = (newHolder != null) ? Long.toString(newHolder.getPosition()) : "-";
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);
}
}
}
@@ -1,313 +0,0 @@
/*
* 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)
*/
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 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)
*/
private 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)
*/
private 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;
}
}
}
@@ -1,395 +0,0 @@
/*
* 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;
}
}
}
@@ -1,46 +0,0 @@
/*
* 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 +
'}';
}
}
@@ -1,240 +0,0 @@
/*
* 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);
}
}
}
@@ -1,67 +0,0 @@
/*
* 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 +
'}';
}
}
@@ -1,70 +0,0 @@
/*
* 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);
}
@@ -1,26 +0,0 @@
/*
* 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);
}
@@ -1,90 +0,0 @@
/*
* 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);
}
@@ -1,70 +0,0 @@
/*
* 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);
}
@@ -1,70 +0,0 @@
/*
* 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);
}
@@ -1,55 +0,0 @@
/*
* 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 +
'}';
}
}
@@ -1,46 +0,0 @@
/*
* 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 +
'}';
}
}
@@ -1,112 +0,0 @@
/*
* 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);
ViewCompat.postInvalidateOnAnimation(parent);
}
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);
}
}
@@ -1,115 +0,0 @@
/*
* 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);
}
}
}
@@ -1,108 +0,0 @@
/*
* 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;
}
}
@@ -1,50 +0,0 @@
/*
* 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;
}
}
}
@@ -1,51 +0,0 @@
/*
* 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 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 x, int y);
/**
* Called after the {@link #onCheckCanStartDrag(android.support.v7.widget.RecyclerView.ViewHolder, int, int)} method returned true.
*
* @param holder The ViewHolder which is associated to item user is attempt to start dragging.
*
* @return null: no constraints (= new ItemDraggableRange(0, getItemCount() - 1)),
* otherwise: the range specified item can be drag-sortable.
*/
ItemDraggableRange onGetItemDraggableRange(T holder);
/**
* 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);
}
@@ -1,46 +0,0 @@
/*
* 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();
}
@@ -1,359 +0,0 @@
/*
* 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.utils.BaseWrapperAdapter;
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
class DraggableItemWrapperAdapter<VH extends RecyclerView.ViewHolder> extends BaseWrapperAdapter<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 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) {
Log.w(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.getPosition();
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 != 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) getWrappedAdapter()).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) {
try {
throw new IllegalStateException("onViewRecycled() - may be a bug. dragging item should be recycled.");
} catch (IllegalStateException e) {
// TODO is there any way to prevent the dragging item from be recycled...?
mDragDropManager.cancelDrag();
}
}
}
super.onViewRecycled(holder);
}
// NOTE: This method is called from RecyclerViewDragDropManager
/*package*/
@SuppressWarnings("unchecked")
boolean canStartDrag(RecyclerView.ViewHolder holder, int x, int y) {
if (LOCAL_LOGV) {
Log.v(TAG, "canStartDrag(holder = " + holder + ", x = " + x + ", y = " + y + ")");
}
return mDraggableItemAdapter.onCheckCanStartDrag(holder, x, y);
}
// NOTE: This method is called from RecyclerViewDragDropManager
/*package*/
@SuppressWarnings("unchecked")
ItemDraggableRange getItemDraggableRange(RecyclerView.ViewHolder holder) {
if (LOCAL_LOGV) {
Log.v(TAG, "getItemDraggableRange(holder = " + holder + ")");
}
return mDraggableItemAdapter.onGetItemDraggableRange(holder);
}
// 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();
}
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);
}
}
@@ -1,282 +0,0 @@
/*
* 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);
moveToDefaultPosition(mDraggingItem.itemView, animate);
// show
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);
}
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
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.getPosition();
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.getPosition();
if ((position >= firstVisiblePosition) &&
(position <= lastVisiblePosition) &&
range.checkInRange(position)) {
v = v2;
break;
}
}
}
return v;
}
}
@@ -1,154 +0,0 @@
/*
* 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)) {
ViewCompat.postInvalidateOnAnimation(mRecyclerView);
}
}
public void pullBottom(float deltaDistance) {
ensureBottomGlow(mRecyclerView);
mBottomGlow.onPull(deltaDistance);
if (mBottomGlow.onPull(deltaDistance)) {
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) {
return;
}
mTopGlow = new EdgeEffectCompat(rv.getContext());
if (getClipToPadding(rv)) {
mTopGlow.setSize(
rv.getMeasuredWidth() - rv.getPaddingLeft() - rv.getPaddingRight(),
rv.getMeasuredHeight() - rv.getPaddingTop() - rv.getPaddingBottom());
} else {
mTopGlow.setSize(rv.getMeasuredWidth(), rv.getMeasuredHeight());
}
}
private void ensureBottomGlow(RecyclerView rv) {
if (mBottomGlow != null) {
return;
}
mBottomGlow = new EdgeEffectCompat(rv.getContext());
if (getClipToPadding(rv)) {
mBottomGlow.setSize(
rv.getMeasuredWidth() - rv.getPaddingLeft() - rv.getPaddingRight(),
rv.getMeasuredHeight() - rv.getPaddingTop() - rv.getPaddingBottom());
} else {
mBottomGlow.setSize(rv.getMeasuredWidth(), rv.getMeasuredHeight());
}
}
private static boolean getClipToPadding(RecyclerView rv) {
return rv.getLayoutManager().getClipToPadding();
}
public void reorderToTop() {
if (mStarted) {
mRecyclerView.removeItemDecoration(this);
mRecyclerView.addItemDecoration(this);
}
}
}
@@ -1,52 +0,0 @@
/*
* 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));
}
@Override
public String toString() {
return "ItemDraggableRange{" +
"mStart=" + mStart +
", mEnd=" + mEnd +
'}';
}
}
@@ -1,197 +0,0 @@
/*
* 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.getPosition();
final int pos2 = swapTargetItem.getPosition();
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.getPosition();
final int pos2 = swapTargetItem.getPosition();
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;
}
}
@@ -1,252 +0,0 @@
/*
* 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. (&lt; 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");
}
}
}
@@ -1,22 +0,0 @@
/*
* 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);
}
@@ -1,98 +0,0 @@
/*
* 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 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.setOnScrollListener(mInternalOnScrollListener);
}
@Override
protected void onRelease() {
super.onRelease();
if (mInternalOnScrollListener != null) {
mInternalOnScrollListener.release();
mInternalOnScrollListener = null;
}
}
/*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);
}
}
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();
}
}
}
@@ -1,81 +0,0 @@
/*
* 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();
}
}
}
@@ -1,68 +0,0 @@
/*
* 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() {
}
}
@@ -1,89 +0,0 @@
/*
* 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);
}
@@ -1,139 +0,0 @@
/*
* 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 the user attempt to expand/collapse the group item.
*
* @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);
}
@@ -1,46 +0,0 @@
/*
* 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();
}
@@ -1,428 +0,0 @@
/*
* 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 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;
public ExpandablePositionTranslator() {
}
public void build(ExpandableItemAdapter adapter) {
final int groupCount = adapter.getGroupCount();
if (mCachedGroupPosInfo == null || mCachedGroupPosInfo.length < groupCount) {
mCachedGroupPosInfo = new long[groupCount];
}
if (mCachedGroupId == null || mCachedGroupId.length < groupCount) {
mCachedGroupId = new int[groupCount];
}
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);
}
mGroupCount = groupCount;
mExpandedGroupCount = 0;
mExpandedChildCount = 0;
mEndOfCalculatedOffsetGroupPosition = Math.max(0, groupCount - 1);
}
public void restoreExpandedGroupItems(int[] restoreGroupIds) {
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(restoreGroupIds);
Arrays.sort(idAndPos);
// 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);
if (id2 < id1) {
index = j;
} else if (id2 == id1) {
// matched
final int position = (int) (idAndPos[j] & LOWER_31BIT_MASK);
index = j + 1;
expandGroup(position);
break;
} else { // id2 > id1
break;
}
}
}
}
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 + ")");
}
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;
if (childCount > 0) {
mExpandedChildCount -= childCount;
mEndOfCalculatedOffsetGroupPosition = Math.min(mEndOfCalculatedOffsetGroupPosition, groupPosition);
// requires notifyItemRangeRemoved()
return true;
} else {
return false;
}
}
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;
if (childCount > 0) {
mExpandedChildCount += childCount;
mEndOfCalculatedOffsetGroupPosition = Math.min(mEndOfCalculatedOffsetGroupPosition, groupPosition);
// requires notifyItemRangeInserted()
return true;
} else {
return false;
}
}
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 = 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 = 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.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 removeGroupItem(int groupPosition) {
final long t = mCachedGroupPosInfo[groupPosition];
if ((t & FLAG_EXPANDED) != 0) {
mExpandedChildCount -= (int) (t & LOWER_31BIT_MASK);
mExpandedGroupCount -= 1;
}
mGroupCount -= 1;
// shift to forward
for (int i = groupPosition; i < mGroupCount; i++) {
mCachedGroupPosInfo[i] = mCachedGroupPosInfo[i + 1];
mCachedGroupId[i] = mCachedGroupId[i + 1];
}
}
public void removeChildItem(int groupPosition, int childPosition) {
final long t = mCachedGroupPosInfo[groupPosition];
if ((t & FLAG_EXPANDED) != 0) {
mExpandedChildCount -= 1;
}
mCachedGroupPosInfo[groupPosition] = (t & (UPPER_32BIT_MASK | FLAG_EXPANDED)) | ((t & LOWER_31BIT_MASK) - 1);
}
}
@@ -1,658 +0,0 @@
/*
* 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 mDraggingItemGroupPosition = RecyclerView.NO_POSITION;
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) {
mPositionTranslator.restoreExpandedGroupItems(expandedItemsSavedState);
}
}
@Override
protected void onRelease() {
super.onRelease();
mExpandableItemAdapter = null;
mExpandableListManager = 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);
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);
mPositionTranslator.restoreExpandedGroupItems(savedState);
}
}
@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 x, int y) {
if (!(mExpandableItemAdapter instanceof ExpandableDraggableItemAdapter)) {
return false;
}
final ExpandableDraggableItemAdapter adapter = (ExpandableDraggableItemAdapter) mExpandableItemAdapter;
final int flatPosition = holder.getPosition();
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);
}
mDraggingItemGroupPosition = (canStart) ? groupPosition : RecyclerView.NO_POSITION;
return canStart;
}
@SuppressWarnings("unchecked")
@Override
public ItemDraggableRange onGetItemDraggableRange(RecyclerView.ViewHolder holder) {
if (!(mExpandableItemAdapter instanceof ExpandableDraggableItemAdapter)) {
return null;
}
if (mExpandableItemAdapter.getGroupCount() < 1) {
return null;
}
final ExpandableDraggableItemAdapter adapter = (ExpandableDraggableItemAdapter) mExpandableItemAdapter;
final int flatPosition = holder.getPosition();
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 groupRange = adapter.onGetGroupItemDraggableRange(holder, groupPosition);
if (groupRange == 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 {
final long startPackedGroupPosition = ExpandableAdapterHelper.getPackedPositionForGroup(groupRange.getStart());
final long endPackedGroupPosition = ExpandableAdapterHelper.getPackedPositionForGroup(groupRange.getEnd());
final int start = mPositionTranslator.getFlatPosition(startPackedGroupPosition);
int end = mPositionTranslator.getFlatPosition(endPackedGroupPosition);
if (groupRange.getEnd() > groupPosition) {
end += mPositionTranslator.getVisibleChildCount(groupRange.getEnd());
}
return new ItemDraggableRange(start, end);
}
} else {
// child
final ItemDraggableRange groupRange = 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 (groupRange == 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 {
final long startPackedGroupPosition = ExpandableAdapterHelper.getPackedPositionForGroup(groupRange.getStart());
final long endPackedGroupPosition = ExpandableAdapterHelper.getPackedPositionForGroup(groupRange.getEnd());
final int end = mPositionTranslator.getFlatPosition(endPackedGroupPosition)
+ mPositionTranslator.getVisibleChildCount(groupRange.getEnd());
int start = mPositionTranslator.getFlatPosition(startPackedGroupPosition) + 1;
start = Math.min(start, end);
return new ItemDraggableRange(start, end);
}
}
}
@Override
public void onMoveItem(int fromPosition, int toPosition) {
if (!(mExpandableItemAdapter instanceof ExpandableDraggableItemAdapter)) {
return;
}
mDraggingItemGroupPosition = 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 x, int y) {
if (!(mExpandableItemAdapter instanceof ExpandableSwipeableItemAdapter)) {
return RecyclerViewSwipeManager.REACTION_CAN_NOT_SWIPE_BOTH;
}
final ExpandableSwipeableItemAdapter adapter = (ExpandableSwipeableItemAdapter) mExpandableItemAdapter;
final long expandablePosition = mPositionTranslator.getExpandablePosition(holder.getPosition());
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 type) {
if (!(mExpandableItemAdapter instanceof ExpandableSwipeableItemAdapter)) {
return;
}
final ExpandableSwipeableItemAdapter adapter = (ExpandableSwipeableItemAdapter) mExpandableItemAdapter;
final long expandablePosition = mPositionTranslator.getExpandablePosition(holder.getPosition());
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 result) {
if (!(mExpandableItemAdapter instanceof ExpandableSwipeableItemAdapter)) {
return RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_DEFAULT;
}
final ExpandableSwipeableItemAdapter adapter = (ExpandableSwipeableItemAdapter) mExpandableItemAdapter;
final long expandablePosition = mPositionTranslator.getExpandablePosition(holder.getPosition());
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 result, int reaction) {
if (!(mExpandableItemAdapter instanceof ExpandableSwipeableItemAdapter)) {
return;
}
final ExpandableSwipeableItemAdapter adapter = (ExpandableSwipeableItemAdapter) mExpandableItemAdapter;
final long expandablePosition = mPositionTranslator.getExpandablePosition(holder.getPosition());
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 x, int y) {
if (mExpandableItemAdapter == null) {
return false;
}
final int position = holder.getPosition();
final long expandablePosition = mPositionTranslator.getExpandablePosition(position);
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);
} else {
collapseGroup(groupPosition);
}
return true;
}
/*package*/ boolean collapseGroup(int groupPosition) {
if (!mPositionTranslator.isGroupExpanded(groupPosition)) {
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);
}
return true;
}
/*package*/ boolean expandGroup(int groupPosition) {
if (mPositionTranslator.isGroupExpanded(groupPosition)) {
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);
}
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;
}
}
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) {
if (!(holder instanceof DraggableItemViewHolder)) {
return;
}
final DraggableItemViewHolder holder2 = (DraggableItemViewHolder) holder;
final int flags = holder2.getDragStateFlags();
if (((flags & RecyclerViewDragDropManager.STATE_FLAG_DRAGGING) != 0) &&
((flags & RecyclerViewDragDropManager.STATE_FLAG_IS_IN_RANGE) == 0)) {
if (mDraggingItemGroupPosition == groupPosition) {
holder2.setDragStateFlags(
flags | RecyclerViewDragDropManager.STATE_FLAG_IS_IN_RANGE |
RecyclerViewDragDropManager.STATE_FLAG_IS_UPDATED);
}
}
}
}
@@ -1,159 +0,0 @@
/*
* 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);
}
@@ -1,472 +0,0 @@
/*
* 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);
// ---
private SavedState mSavedState;
private RecyclerView mRecyclerView;
private ExpandableRecyclerViewWrapperAdapter mAdapter;
private RecyclerView.OnItemTouchListener mInternalUseOnItemTouchListener;
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) {
}
};
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;
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);
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)) {
return true;
}
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 != null) {
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 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, 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);
}
/**
* 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);
}
/**
* 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 eirther 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);
}
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];
}
};
}
}
@@ -1,247 +0,0 @@
/*
* 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.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 int[] mTmpLocation = new int[2];
private Rect mTmpRect = new Rect();
private int mImmediatelySetTranslationThreshold;
public ItemSlidingAnimator() {
mActive = new ArrayList<>();
}
public void slideToDefaultPosition(RecyclerView.ViewHolder holder, boolean shouldAnimate, long duration) {
slideToSpecifiedPositionInternal(holder, 0, shouldAnimate, duration);
}
public void slideToOutsideOfWindow(RecyclerView.ViewHolder holder, boolean toLeft, boolean shouldAnimate, long duration) {
slideToOutsideOfWindowInternal(holder, toLeft, shouldAnimate, duration);
}
public void slideToSpecifiedPosition(RecyclerView.ViewHolder holder, float position) {
slideToSpecifiedPositionInternal(holder, position, false, 0);
}
private boolean slideToSpecifiedPositionInternal(RecyclerView.ViewHolder holder, float position, boolean shouldAnimate, long duration) {
final int translationX;
duration = (shouldAnimate) ? duration : 0;
if (position != 0.0f) {
final View containerView = ((SwipeableItemViewHolder) holder).getSwipeableContainerView();
translationX = (int) (containerView.getWidth() * position + 0.5f);
} else {
translationX = 0;
}
return animateSlideInternalCompat(holder, translationX, 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 animateSlideInternalPreHoneycomb(holder, translationX);
}
}
@SuppressLint("RtlHardcoded")
private boolean animateSlideInternalPreHoneycomb(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 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 toX = translationX;
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;
}
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;
}
}
@@ -1,750 +0,0 @@
/*
* 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)} 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)} 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)} 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)} 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)} 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)} 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)} 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)} 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)} 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)} 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)} 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)} 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)} 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);
// ---
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 Rect mSwipingItemMargins = new Rect();
private int mTouchedItemOffsetX;
private int mLastTouchX;
private int mSwipingItemReactionType;
private VelocityTracker mVelocityTracker;
private SwipingItemOperator mSwipingItemOperator;
/**
* 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);
}
};
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 = holder.getPosition();
// 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, 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;
}
if (LOCAL_LOGD) {
Log.d(TAG, "swiping started");
}
startSwiping(rv, e, holder);
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) {
mSwipingItem = holder;
mLastTouchX = (int) (e.getX() + 0.5f);
mTouchedItemOffsetX = mLastTouchX;
CustomRecyclerViewUtils.getLayoutMargins(holder.itemView, mSwipingItemMargins);
mSwipingItemOperator = new SwipingItemOperator(this, mSwipingItem, mSwipingItemReactionType);
mSwipingItemOperator.start();
mVelocityTracker.clear();
mVelocityTracker.addMovement(e);
mRecyclerView.getParent().requestDisallowInterceptTouchEvent(true);
// raise onSwipeItemStarted() event
mAdapter.onSwipeItemStarted(this, holder);
}
private void finishSwiping(int result) {
final RecyclerView.ViewHolder swipingItem = mSwipingItem;
if (swipingItem == null) {
return;
}
if (mRecyclerView != null && mRecyclerView.getParent() != null) {
mRecyclerView.getParent().requestDisallowInterceptTouchEvent(false);
}
mVelocityTracker.clear();
mSwipingItem = null;
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, 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, 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;
}
/*package*/ void applySlideItem(RecyclerView.ViewHolder holder, 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, reqBackgroundType);
} else {
mAdapter.setSwipeBackgroundDrawable(holder, reqBackgroundType);
slideItem(holder, amount, shouldAnimate);
}
}
private static boolean supportsViewPropertyAnimator() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
}
}
@@ -1,244 +0,0 @@
/*
* 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);
}
}
}
}
@@ -1,33 +0,0 @@
/*
* 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));
}
}
@@ -1,39 +0,0 @@
/*
* 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);
}
}
@@ -1,84 +0,0 @@
/*
* 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 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 x, int y);
/**
* Called when sets background of the swiping item.
*
* @param holder The ViewHolder which is associated to the swiping item.
* @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 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 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 result);
/**
* Called after {@link #onSwipeItem(android.support.v7.widget.RecyclerView.ViewHolder, 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 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 result, int reaction);
}
@@ -1,113 +0,0 @@
/*
* 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();
/**
* Gets the container view for the swipeable area.
*
* @return The container view instance.
*/
View getSwipeableContainerView();
}
@@ -1,272 +0,0 @@
/*
* 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) {
((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, 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 x, int y) {
if (LOCAL_LOGV) {
Log.v(TAG, "getSwipeReactionType(holder = " + holder + ", x = " + x + ", y = " + y + ")");
}
return mSwipeableItemAdapter.onGetSwipeReactionType(holder, x, y);
}
// NOTE: This method is called from RecyclerViewDragDropManager
/*package*/
@SuppressWarnings("unchecked")
void setSwipeBackgroundDrawable(RecyclerView.ViewHolder holder, int type) {
if (LOCAL_LOGV) {
Log.v(TAG, "setSwipeBackgroundDrawable(holder = " + holder + ", type = " + type + ")");
}
mSwipeableItemAdapter.onSetSwipeBackground(holder, type);
}
// NOTE: This method is called from RecyclerViewDragDropManager
/*package*/ void onSwipeItemStarted(RecyclerViewSwipeManager manager, RecyclerView.ViewHolder holder) {
if (LOCAL_LOGD) {
Log.d(TAG, "onSwipeItemStarted(holder = " + holder + ")");
}
mSwipingItemPosition = holder.getPosition();
notifyDataSetChanged();
}
// NOTE: This method is called from RecyclerViewDragDropManager
/*package*/
@SuppressWarnings("unchecked")
int onSwipeItemFinished(RecyclerView.ViewHolder holder, int result) {
if (LOCAL_LOGD) {
Log.d(TAG, "onSwipeItemFinished(holder = " + holder + ", result = " + result + ")");
}
mSwipingItemPosition = RecyclerView.NO_POSITION;
return mSwipeableItemAdapter.onSwipeItem(holder, result);
}
/*package*/
@SuppressWarnings("unchecked")
void onSwipeItemFinished2(RecyclerView.ViewHolder holder, int result, int afterReaction) {
((SwipeableItemViewHolder) holder).setSwipeResult(result);
((SwipeableItemViewHolder) holder).setAfterSwipeReaction(afterReaction);
((SwipeableItemViewHolder) holder).setSwipeItemSlideAmount(getSwipeAmountFromAfterReaction(result, afterReaction));
mSwipeableItemAdapter.onPerformAfterSwipeReaction(holder, 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);
}
}
@@ -1,100 +0,0 @@
/*
* 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 Interpolator RUBBER_BAND_INTERPOLATOR = new RubberBandInterpolator(RUBBER_BAND_LIMIT);
private RecyclerViewSwipeManager mSwipeManager;
private RecyclerView.ViewHolder mSwipingItem;
private View mSwipingItemContainerView;
private int mLeftSwipeReactionType;
private int mRightSwipeReactionType;
private int mSwipingItemWidth;
private float mInvSwipingItemWidth;
private int mSwipeDistance;
private float mPrevTranslateAmount;
public SwipingItemOperator(RecyclerViewSwipeManager manager, RecyclerView.ViewHolder swipingItem, int swipeReactionType) {
mSwipeManager = manager;
mSwipingItem = swipingItem;
mLeftSwipeReactionType = SwipeReactionUtils.extractLeftReaction(swipeReactionType);
mRightSwipeReactionType = SwipeReactionUtils.extractRightReaction(swipeReactionType);
mSwipingItemContainerView = ((SwipeableItemViewHolder) swipingItem).getSwipeableContainerView();
mSwipingItemWidth = mSwipingItemContainerView.getWidth();
mInvSwipingItemWidth = (mSwipingItemWidth != 0) ? (1.0f / mSwipingItemWidth) : 0.0f;
}
@SuppressWarnings("EmptyMethod")
public void start() {
// nothing to do here.
}
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;
mSwipingItemContainerView = null;
}
public void update(int swipeDistance) {
if (mSwipeDistance == swipeDistance) {
return;
}
mSwipeDistance = swipeDistance;
final int reactionType = (mSwipeDistance > 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(mSwipeDistance), mSwipingItemWidth) * mInvSwipingItemWidth;
translateAmount = Math.signum(mSwipeDistance) * RUBBER_BAND_INTERPOLATOR.getInterpolation(proportion);
break;
case REACTION_CAN_SWIPE:
translateAmount = Math.min(Math.max((mSwipeDistance * mInvSwipingItemWidth), -1.0f), 1.0f);
break;
}
mSwipeManager.applySlideItem(mSwipingItem, mPrevTranslateAmount, translateAmount, false);
mPrevTranslateAmount = translateAmount;
}
}
@@ -1,188 +0,0 @@
/*
* 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;
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;
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);
}
};
}
public boolean isReleased() {
return (mInternalUseOnItemTouchListener == null);
}
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();
}
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;
}
public void setEnabled(boolean enabled) {
if (mEnabled == enabled) {
return;
}
mEnabled = enabled;
if (!mEnabled) {
handleActionUpOrCancel();
}
}
public boolean isEnabled() {
return mEnabled;
}
public void setInterceptVerticalScrollingWhileAnimationRunning(boolean value) {
mInterceptScrollingWhileAnimationRunning = value;
}
public boolean isInterceptScrollingWhileAnimationRunning() {
return mInterceptScrollingWhileAnimationRunning;
}
}
@@ -1,40 +0,0 @@
/*
* 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;
}
}
@@ -1,87 +0,0 @@
/*
* 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;
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;
}
}
@@ -1,88 +0,0 @@
/*
* 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) {
}
}
@@ -1,40 +0,0 @@
/*
* 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;
}
}
@@ -1,75 +0,0 @@
/*
* 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;
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;
}
}
@@ -1,227 +0,0 @@
/*
* 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);
}
}
}
}
@@ -1,121 +0,0 @@
/*
* 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;
}
}
}
@@ -1,53 +0,0 @@
/*
* 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);
}
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry combineaccessrules="false" kind="src" path="/google-android-support-v4"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
@@ -0,0 +1 @@
/bin/
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>google-android-support-v13</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,11 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.6
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
<!--
Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,10 +14,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.support.v13" >
<resources>
<uses-sdk android:minSdkVersion="13" />
<!-- Since SwitchCompat can use optical insets on v18+, reset the manual padding -->
<dimen name="abc_switch_padding">0px</dimen>
<application />
</resources>
</manifest>
@@ -0,0 +1,15 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-22
android.library=true
@@ -0,0 +1,2 @@
Manifest-Version: 1.0
@@ -0,0 +1,78 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 android.support.v13.app;
import android.app.Fragment;
/**
* Helper for accessing features in {@link Fragment} introduced after
* API level 13 in a backwards compatible fashion.
*/
public class FragmentCompat {
interface FragmentCompatImpl {
void setMenuVisibility(Fragment f, boolean visible);
void setUserVisibleHint(Fragment f, boolean deferStart);
}
static class BaseFragmentCompatImpl implements FragmentCompatImpl {
public void setMenuVisibility(Fragment f, boolean visible) {
}
public void setUserVisibleHint(Fragment f, boolean deferStart) {
}
}
static class ICSFragmentCompatImpl extends BaseFragmentCompatImpl {
@Override
public void setMenuVisibility(Fragment f, boolean visible) {
FragmentCompatICS.setMenuVisibility(f, visible);
}
}
static class ICSMR1FragmentCompatImpl extends ICSFragmentCompatImpl {
@Override
public void setUserVisibleHint(Fragment f, boolean deferStart) {
FragmentCompatICSMR1.setUserVisibleHint(f, deferStart);
}
}
static final FragmentCompatImpl IMPL;
static {
if (android.os.Build.VERSION.SDK_INT >= 15) {
IMPL = new ICSMR1FragmentCompatImpl();
} else if (android.os.Build.VERSION.SDK_INT >= 14) {
IMPL = new ICSFragmentCompatImpl();
} else {
IMPL = new BaseFragmentCompatImpl();
}
}
/**
* Call {@link Fragment#setMenuVisibility(boolean) Fragment.setMenuVisibility(boolean)}
* if running on an appropriate version of the platform.
*/
public static void setMenuVisibility(Fragment f, boolean visible) {
IMPL.setMenuVisibility(f, visible);
}
/**
* Call {@link Fragment#setUserVisibleHint(boolean) setUserVisibleHint(boolean)}
* if running on an appropriate version of the platform.
*/
public static void setUserVisibleHint(Fragment f, boolean deferStart) {
IMPL.setUserVisibleHint(f, deferStart);
}
}
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 android.support.v13.app;
import android.app.Fragment;
class FragmentCompatICS {
public static void setMenuVisibility(Fragment f, boolean visible) {
f.setMenuVisibility(visible);
}
}
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 android.support.v13.app;
import android.app.Fragment;
class FragmentCompatICSMR1 {
public static void setUserVisibleHint(Fragment f, boolean isVisible) {
f.setUserVisibleHint(isVisible);
}
}
@@ -0,0 +1,179 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 android.support.v13.app;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Parcelable;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
/**
* Implementation of {@link android.support.v4.view.PagerAdapter} that
* represents each page as a {@link android.app.Fragment} that is persistently
* kept in the fragment manager as long as the user can return to the page.
*
* <p>This version of the pager is best for use when there are a handful of
* typically more static fragments to be paged through, such as a set of tabs.
* The fragment of each page the user visits will be kept in memory, though its
* view hierarchy may be destroyed when not visible. This can result in using
* a significant amount of memory since fragment instances can hold on to an
* arbitrary amount of state. For larger sets of pages, consider
* {@link FragmentStatePagerAdapter}.
*
* <p>When using FragmentPagerAdapter the host ViewPager must have a
* valid ID set.</p>
*
* <p>Subclasses only need to implement {@link #getItem(int)}
* and {@link #getCount()} to have a working adapter.
*
* <p>Here is an example implementation of a pager containing fragments of
* lists:
*
* {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentPagerSupport.java
* complete}
*
* <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
*
* {@sample development/samples/Support13Demos/res/layout/fragment_pager.xml
* complete}
*
* <p>The <code>R.layout.fragment_pager_list</code> resource containing each
* individual fragment's layout is:
*
* {@sample development/samples/Support13Demos/res/layout/fragment_pager_list.xml
* complete}
*/
public abstract class FragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = false;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;
public FragmentPagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
@Override
public void startUpdate(ViewGroup container) {
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
FragmentCompat.setMenuVisibility(fragment, false);
FragmentCompat.setUserVisibleHint(fragment, false);
}
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ " v=" + ((Fragment)object).getView());
mCurTransaction.detach((Fragment)object);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
FragmentCompat.setMenuVisibility(mCurrentPrimaryItem, false);
FragmentCompat.setUserVisibleHint(mCurrentPrimaryItem, false);
}
if (fragment != null) {
FragmentCompat.setMenuVisibility(fragment, true);
FragmentCompat.setUserVisibleHint(fragment, true);
}
mCurrentPrimaryItem = fragment;
}
}
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
mFragmentManager.executePendingTransactions();
}
}
@Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment)object).getView() == view;
}
@Override
public Parcelable saveState() {
return null;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
}
/**
* Return a unique identifier for the item at the given position.
*
* <p>The default implementation returns the given position.
* Subclasses should override this method if the positions of items can change.</p>
*
* @param position Position within this adapter
* @return Unique identifier for the item at position
*/
public long getItemId(int position) {
return position;
}
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
}
@@ -0,0 +1,229 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 android.support.v13.app;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
/**
* Implementation of {@link android.support.v4.view.PagerAdapter} that
* uses a {@link Fragment} to manage each page. This class also handles
* saving and restoring of fragment's state.
*
* <p>This version of the pager is more useful when there are a large number
* of pages, working more like a list view. When pages are not visible to
* the user, their entire fragment may be destroyed, only keeping the saved
* state of that fragment. This allows the pager to hold on to much less
* memory associated with each visited page as compared to
* {@link FragmentPagerAdapter} at the cost of potentially more overhead when
* switching between pages.
*
* <p>When using FragmentPagerAdapter the host ViewPager must have a
* valid ID set.</p>
*
* <p>Subclasses only need to implement {@link #getItem(int)}
* and {@link #getCount()} to have a working adapter.
*
* <p>Here is an example implementation of a pager containing fragments of
* lists:
*
* {@sample development/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentStatePagerSupport.java
* complete}
*
* <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
*
* {@sample development/samples/Support4Demos/res/layout/fragment_pager.xml
* complete}
*
* <p>The <code>R.layout.fragment_pager_list</code> resource containing each
* individual fragment's layout is:
*
* {@sample development/samples/Support4Demos/res/layout/fragment_pager_list.xml
* complete}
*/
public abstract class FragmentStatePagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentStatePagerAdapter";
private static final boolean DEBUG = false;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
private Fragment mCurrentPrimaryItem = null;
public FragmentStatePagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
@Override
public void startUpdate(ViewGroup container) {
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
while (mFragments.size() <= position) {
mFragments.add(null);
}
FragmentCompat.setMenuVisibility(fragment, false);
FragmentCompat.setUserVisibleHint(fragment, false);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
mFragments.set(position, null);
mCurTransaction.remove(fragment);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
FragmentCompat.setMenuVisibility(mCurrentPrimaryItem, false);
FragmentCompat.setUserVisibleHint(mCurrentPrimaryItem, false);
}
if (fragment != null) {
FragmentCompat.setMenuVisibility(fragment, true);
FragmentCompat.setUserVisibleHint(fragment, true);
}
mCurrentPrimaryItem = fragment;
}
}
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
mFragmentManager.executePendingTransactions();
}
}
@Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment)object).getView() == view;
}
@Override
public Parcelable saveState() {
Bundle state = null;
if (mSavedState.size() > 0) {
state = new Bundle();
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
mSavedState.toArray(fss);
state.putParcelableArray("states", fss);
}
for (int i=0; i<mFragments.size(); i++) {
Fragment f = mFragments.get(i);
if (f != null && f.isAdded()) {
if (state == null) {
state = new Bundle();
}
String key = "f" + i;
mFragmentManager.putFragment(state, key, f);
}
}
return state;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
if (state != null) {
Bundle bundle = (Bundle)state;
bundle.setClassLoader(loader);
Parcelable[] fss = bundle.getParcelableArray("states");
mSavedState.clear();
mFragments.clear();
if (fss != null) {
for (int i=0; i<fss.length; i++) {
mSavedState.add((Fragment.SavedState)fss[i]);
}
}
Iterable<String> keys = bundle.keySet();
for (String key: keys) {
if (key.startsWith("f")) {
int index = Integer.parseInt(key.substring(1));
Fragment f = mFragmentManager.getFragment(bundle, key);
if (f != null) {
while (mFragments.size() <= index) {
mFragments.add(null);
}
FragmentCompat.setMenuVisibility(f, false);
mFragments.set(index, f);
} else {
Log.w(TAG, "Bad fragment at key " + key);
}
}
}
}
}
}
@@ -0,0 +1,347 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 android.support.v13.app;
import java.util.ArrayList;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TabHost;
import android.widget.TabWidget;
/**
* Version of {@link android.support.v4.app.FragmentTabHost} that can be
* used with the platform {@link android.app.Fragment} APIs. You will not
* normally use this, instead using action bar tabs.
*/
public class FragmentTabHost extends TabHost
implements TabHost.OnTabChangeListener {
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
private FrameLayout mRealTabContent;
private Context mContext;
private FragmentManager mFragmentManager;
private int mContainerId;
private TabHost.OnTabChangeListener mOnTabChangeListener;
private TabInfo mLastTab;
private boolean mAttached;
static final class TabInfo {
private final String tag;
private final Class<?> clss;
private final Bundle args;
private Fragment fragment;
TabInfo(String _tag, Class<?> _class, Bundle _args) {
tag = _tag;
clss = _class;
args = _args;
}
}
static class DummyTabFactory implements TabHost.TabContentFactory {
private final Context mContext;
public DummyTabFactory(Context context) {
mContext = context;
}
@Override
public View createTabContent(String tag) {
View v = new View(mContext);
v.setMinimumWidth(0);
v.setMinimumHeight(0);
return v;
}
}
static class SavedState extends BaseSavedState {
String curTab;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
curTab = in.readString();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeString(curTab);
}
@Override
public String toString() {
return "FragmentTabHost.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " curTab=" + curTab + "}";
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
public FragmentTabHost(Context context) {
// Note that we call through to the version that takes an AttributeSet,
// because the simple Context construct can result in a broken object!
super(context, null);
initFragmentTabHost(context, null);
}
public FragmentTabHost(Context context, AttributeSet attrs) {
super(context, attrs);
initFragmentTabHost(context, attrs);
}
private void initFragmentTabHost(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs,
new int[] { android.R.attr.inflatedId }, 0, 0);
mContainerId = a.getResourceId(0, 0);
a.recycle();
super.setOnTabChangedListener(this);
}
private void ensureHierarchy(Context context) {
// If owner hasn't made its own view hierarchy, then as a convenience
// we will construct a standard one here.
if (findViewById(android.R.id.tabs) == null) {
LinearLayout ll = new LinearLayout(context);
ll.setOrientation(LinearLayout.VERTICAL);
addView(ll, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT));
TabWidget tw = new TabWidget(context);
tw.setId(android.R.id.tabs);
tw.setOrientation(TabWidget.HORIZONTAL);
ll.addView(tw, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0));
FrameLayout fl = new FrameLayout(context);
fl.setId(android.R.id.tabcontent);
ll.addView(fl, new LinearLayout.LayoutParams(0, 0, 0));
mRealTabContent = fl = new FrameLayout(context);
mRealTabContent.setId(mContainerId);
ll.addView(fl, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT, 0, 1));
}
}
/**
* @deprecated Don't call the original TabHost setup, you must instead
* call {@link #setup(Context, FragmentManager)} or
* {@link #setup(Context, FragmentManager, int)}.
*/
@Override @Deprecated
public void setup() {
throw new IllegalStateException(
"Must call setup() that takes a Context and FragmentManager");
}
public void setup(Context context, FragmentManager manager) {
ensureHierarchy(context); // Ensure views required by super.setup()
super.setup();
mContext = context;
mFragmentManager = manager;
ensureContent();
}
public void setup(Context context, FragmentManager manager, int containerId) {
ensureHierarchy(context); // Ensure views required by super.setup()
super.setup();
mContext = context;
mFragmentManager = manager;
mContainerId = containerId;
ensureContent();
mRealTabContent.setId(containerId);
// We must have an ID to be able to save/restore our state. If
// the owner hasn't set one at this point, we will set it ourself.
if (getId() == View.NO_ID) {
setId(android.R.id.tabhost);
}
}
private void ensureContent() {
if (mRealTabContent == null) {
mRealTabContent = (FrameLayout)findViewById(mContainerId);
if (mRealTabContent == null) {
throw new IllegalStateException(
"No tab content FrameLayout found for id " + mContainerId);
}
}
}
@Override
public void setOnTabChangedListener(OnTabChangeListener l) {
mOnTabChangeListener = l;
}
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
tabSpec.setContent(new DummyTabFactory(mContext));
String tag = tabSpec.getTag();
TabInfo info = new TabInfo(tag, clss, args);
if (mAttached) {
// If we are already attached to the window, then check to make
// sure this tab's fragment is inactive if it exists. This shouldn't
// normally happen.
info.fragment = mFragmentManager.findFragmentByTag(tag);
if (info.fragment != null && !info.fragment.isDetached()) {
FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.detach(info.fragment);
ft.commit();
}
}
mTabs.add(info);
addTab(tabSpec);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
String currentTab = getCurrentTabTag();
// Go through all tabs and make sure their fragments match
// the correct state.
FragmentTransaction ft = null;
for (int i=0; i<mTabs.size(); i++) {
TabInfo tab = mTabs.get(i);
tab.fragment = mFragmentManager.findFragmentByTag(tab.tag);
if (tab.fragment != null && !tab.fragment.isDetached()) {
if (tab.tag.equals(currentTab)) {
// The fragment for this tab is already there and
// active, and it is what we really want to have
// as the current tab. Nothing to do.
mLastTab = tab;
} else {
// This fragment was restored in the active state,
// but is not the current tab. Deactivate it.
if (ft == null) {
ft = mFragmentManager.beginTransaction();
}
ft.detach(tab.fragment);
}
}
}
// We are now ready to go. Make sure we are switched to the
// correct tab.
mAttached = true;
ft = doTabChanged(currentTab, ft);
if (ft != null) {
ft.commit();
mFragmentManager.executePendingTransactions();
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mAttached = false;
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.curTab = getCurrentTabTag();
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState)state;
super.onRestoreInstanceState(ss.getSuperState());
setCurrentTabByTag(ss.curTab);
}
@Override
public void onTabChanged(String tabId) {
if (mAttached) {
FragmentTransaction ft = doTabChanged(tabId, null);
if (ft != null) {
ft.commit();
}
}
if (mOnTabChangeListener != null) {
mOnTabChangeListener.onTabChanged(tabId);
}
}
private FragmentTransaction doTabChanged(String tabId, FragmentTransaction ft) {
TabInfo newTab = null;
for (int i=0; i<mTabs.size(); i++) {
TabInfo tab = mTabs.get(i);
if (tab.tag.equals(tabId)) {
newTab = tab;
}
}
if (newTab == null) {
throw new IllegalStateException("No tab known for tag " + tabId);
}
if (mLastTab != newTab) {
if (ft == null) {
ft = mFragmentManager.beginTransaction();
}
if (mLastTab != null) {
if (mLastTab.fragment != null) {
ft.detach(mLastTab.fragment);
}
}
if (newTab != null) {
if (newTab.fragment == null) {
newTab.fragment = Fragment.instantiate(mContext,
newTab.clss.getName(), newTab.args);
ft.add(mContainerId, newTab.fragment, newTab.tag);
} else {
ft.attach(newTab.fragment);
}
}
mLastTab = newTab;
}
return ft;
}
}
@@ -0,0 +1,5 @@
<body>
Support classes to access some of the android.app package features introduced after API level 13 in a backwards compatible fashion.
</body>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="var" path="ANDROID_HOME/extras/android/m2repository/com/android/support/support-annotations/22.2.1/support-annotations-22.2.1.jar"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
@@ -0,0 +1 @@
/bin/
+33
View File
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>google-android-support-v4</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,12 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.7
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
<!--
Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,9 +14,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.support.v4" >
<resources>
<uses-sdk android:minSdkVersion="4" />
<bool name="abc_action_bar_embed_tabs">false</bool>
<application />
</resources>
</manifest>
@@ -1,7 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2012, The Android Open Source Project
/* Copyright 2014, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
@@ -15,11 +12,7 @@
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
<resources>
<!-- see comment in values/config.xml -->
<dimen name="abc_config_prefDialogWidth">580dp</dimen>
</resources>
package android.support.v4.media;
parcelable MediaMetadataCompat;
@@ -0,0 +1,18 @@
/* Copyright 2014, The Android Open Source Project
**
** 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 android.support.v4.media;
parcelable RatingCompat;
@@ -0,0 +1,20 @@
/* Copyright 2014, The Android Open Source Project
**
** 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 android.support.v4.media.session;
parcelable MediaSessionCompat.Token;
parcelable MediaSessionCompat.QueueItem;
parcelable MediaSessionCompat.ResultReceiverWrapper;
@@ -0,0 +1,18 @@
/* Copyright 2014, The Android Open Source Project
**
** 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 android.support.v4.media.session;
parcelable ParcelableVolumeInfo;
@@ -0,0 +1,18 @@
/* Copyright 2014, The Android Open Source Project
**
** 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 android.support.v4.media.session;
parcelable PlaybackStateCompat;
@@ -0,0 +1,15 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-22
android.library=true
@@ -0,0 +1,2 @@
Manifest-Version: 1.0
@@ -0,0 +1,451 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 android.support.v4.accessibilityservice;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.view.View;
/**
* Helper for accessing features in {@link android.accessibilityservice.AccessibilityService}
* introduced after API level 4 in a backwards compatible fashion.
*/
public class AccessibilityServiceInfoCompat {
static interface AccessibilityServiceInfoVersionImpl {
public String getId(AccessibilityServiceInfo info);
public ResolveInfo getResolveInfo(AccessibilityServiceInfo info);
public boolean getCanRetrieveWindowContent(AccessibilityServiceInfo info);
public String getDescription(AccessibilityServiceInfo info);
public String getSettingsActivityName(AccessibilityServiceInfo info);
public int getCapabilities(AccessibilityServiceInfo info);
}
static class AccessibilityServiceInfoStubImpl implements AccessibilityServiceInfoVersionImpl {
public boolean getCanRetrieveWindowContent(AccessibilityServiceInfo info) {
return false;
}
public String getDescription(AccessibilityServiceInfo info) {
return null;
}
public String getId(AccessibilityServiceInfo info) {
return null;
}
public ResolveInfo getResolveInfo(AccessibilityServiceInfo info) {
return null;
}
public String getSettingsActivityName(AccessibilityServiceInfo info) {
return null;
}
public int getCapabilities(AccessibilityServiceInfo info) {
return 0;
}
}
static class AccessibilityServiceInfoIcsImpl extends AccessibilityServiceInfoStubImpl {
@Override
public boolean getCanRetrieveWindowContent(AccessibilityServiceInfo info) {
return AccessibilityServiceInfoCompatIcs.getCanRetrieveWindowContent(info);
}
@Override
public String getDescription(AccessibilityServiceInfo info) {
return AccessibilityServiceInfoCompatIcs.getDescription(info);
}
@Override
public String getId(AccessibilityServiceInfo info) {
return AccessibilityServiceInfoCompatIcs.getId(info);
}
@Override
public ResolveInfo getResolveInfo(AccessibilityServiceInfo info) {
return AccessibilityServiceInfoCompatIcs.getResolveInfo(info);
}
@Override
public String getSettingsActivityName(AccessibilityServiceInfo info) {
return AccessibilityServiceInfoCompatIcs.getSettingsActivityName(info);
}
@Override
public int getCapabilities(AccessibilityServiceInfo info) {
if (getCanRetrieveWindowContent(info)) {
return CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT;
}
return 0;
}
}
static class AccessibilityServiceInfoJellyBeanMr2 extends AccessibilityServiceInfoIcsImpl {
@Override
public int getCapabilities(AccessibilityServiceInfo info) {
return AccessibilityServiceInfoCompatJellyBeanMr2.getCapabilities(info);
}
}
static {
if (Build.VERSION.SDK_INT >= 18) { // JellyBean MR2
IMPL = new AccessibilityServiceInfoJellyBeanMr2();
} else if (Build.VERSION.SDK_INT >= 14) { // ICS
IMPL = new AccessibilityServiceInfoIcsImpl();
} else {
IMPL = new AccessibilityServiceInfoStubImpl();
}
}
// Capabilities
private static final AccessibilityServiceInfoVersionImpl IMPL;
/**
* Capability: This accessibility service can retrieve the active window content.
*/
public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 0x00000001;
/**
* Capability: This accessibility service can request touch exploration mode in which
* touched items are spoken aloud and the UI can be explored via gestures.
*/
public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 0x00000002;
/**
* Capability: This accessibility service can request enhanced web accessibility
* enhancements. For example, installing scripts to make app content more accessible.
*/
public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000004;
/**
* Capability: This accessibility service can filter the key event stream.
*/
public static final int CAPABILITY_CAN_FILTER_KEY_EVENTS = 0x00000008;
// Feedback types
/**
* Denotes braille feedback.
*/
public static final int FEEDBACK_BRAILLE = 0x0000020;
/**
* Mask for all feedback types.
*
* @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
* @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
* @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
* @see AccessibilityServiceInfo#FEEDBACK_VISUAL
* @see AccessibilityServiceInfo#FEEDBACK_GENERIC
* @see FEEDBACK_BRAILLE
*/
public static final int FEEDBACK_ALL_MASK = 0xFFFFFFFF;
// Flags
/**
* If an {@link AccessibilityService} is the default for a given type.
* Default service is invoked only if no package specific one exists. In case of
* more than one package specific service only the earlier registered is notified.
*/
public static final int DEFAULT = 0x0000001;
/**
* If this flag is set the system will regard views that are not important
* for accessibility in addition to the ones that are important for accessibility.
* That is, views that are marked as not important for accessibility via
* {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO} or
* {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS} and views that are
* marked as potentially important for accessibility via
* {@link View#IMPORTANT_FOR_ACCESSIBILITY_AUTO} for which the system has determined
* that are not important for accessibility, are both reported while querying the
* window content and also the accessibility service will receive accessibility events
* from them.
* <p>
* <strong>Note:</strong> For accessibility services targeting API version
* {@link Build.VERSION_CODES#JELLY_BEAN} or higher this flag has to be explicitly
* set for the system to regard views that are not important for accessibility. For
* accessibility services targeting API version lower than
* {@link Build.VERSION_CODES#JELLY_BEAN} this flag is ignored and all views are
* regarded for accessibility purposes.
* </p>
* <p>
* Usually views not important for accessibility are layout managers that do not
* react to user actions, do not draw any content, and do not have any special
* semantics in the context of the screen content. For example, a three by three
* grid can be implemented as three horizontal linear layouts and one vertical,
* or three vertical linear layouts and one horizontal, or one grid layout, etc.
* In this context the actual layout mangers used to achieve the grid configuration
* are not important, rather it is important that there are nine evenly distributed
* elements.
* </p>
*/
public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x0000002;
/**
* This flag requests that the system gets into touch exploration mode.
* In this mode a single finger moving on the screen behaves as a mouse
* pointer hovering over the user interface. The system will also detect
* certain gestures performed on the touch screen and notify this service.
* The system will enable touch exploration mode if there is at least one
* accessibility service that has this flag set. Hence, clearing this
* flag does not guarantee that the device will not be in touch exploration
* mode since there may be another enabled service that requested it.
* <p>
* For accessibility services targeting API version higher than
* {@link Build.VERSION_CODES#JELLY_BEAN_MR1} that want to set
* this flag have to declare this capability in their meta-data by setting
* the attribute canRequestTouchExplorationMode to true, otherwise this flag
* will be ignored. For how to declare the meta-data of a service refer to
* {@value AccessibilityService#SERVICE_META_DATA}.
* </p>
* <p>
* Services targeting API version equal to or lower than
* {@link Build.VERSION_CODES#JELLY_BEAN_MR1} will work normally, i.e.
* the first time they are run, if this flag is specified, a dialog is
* shown to the user to confirm enabling explore by touch.
* </p>
*/
public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 0x0000004;
/**
* This flag requests from the system to enable web accessibility enhancing
* extensions. Such extensions aim to provide improved accessibility support
* for content presented in a {@link android.webkit.WebView}. An example of such
* an extension is injecting JavaScript from a secure source. The system will enable
* enhanced web accessibility if there is at least one accessibility service
* that has this flag set. Hence, clearing this flag does not guarantee that the
* device will not have enhanced web accessibility enabled since there may be
* another enabled service that requested it.
* <p>
* Services that want to set this flag have to declare this capability
* in their meta-data by setting the attribute canRequestEnhancedWebAccessibility
* to true, otherwise this flag will be ignored. For how to declare the meta-data
* of a service refer to {@value AccessibilityService#SERVICE_META_DATA}.
* </p>
*/
public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000008;
/**
* This flag requests that the AccessibilityNodeInfos obtained
* by an {@link AccessibilityService} contain the id of the source view.
* The source view id will be a fully qualified resource name of the
* form "package:id/name", for example "foo.bar:id/my_list", and it is
* useful for UI test automation. This flag is not set by default.
*/
public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
/**
* This flag requests from the system to filter key events. If this flag
* is set the accessibility service will receive the key events before
* applications allowing it implement global shortcuts. Setting this flag
* does not guarantee that this service will filter key events since only
* one service can do so at any given time. This avoids user confusion due
* to behavior change in case different key filtering services are enabled.
* If there is already another key filtering service enabled, this one will
* not receive key events.
* <p>
* Services that want to set this flag have to declare this capability
* in their meta-data by setting the attribute canRequestFilterKeyEvents
* to true, otherwise this flag will be ignored. For how to declare the meta
* -data of a service refer to {@value AccessibilityService#SERVICE_META_DATA}.
* </p>
*/
public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 0x00000020;
/*
* Hide constructor
*/
private AccessibilityServiceInfoCompat() {
}
/**
* The accessibility service id.
* <p>
* <strong>Generated by the system.</strong>
* </p>
*
* @return The id.
*/
public static String getId(AccessibilityServiceInfo info) {
return IMPL.getId(info);
}
/**
* The service {@link ResolveInfo}.
* <p>
* <strong>Generated by the system.</strong>
* </p>
*
* @return The info.
*/
public static ResolveInfo getResolveInfo(AccessibilityServiceInfo info) {
return IMPL.getResolveInfo(info);
}
/**
* The settings activity name.
* <p>
* <strong>Statically set from {@link AccessibilityService#SERVICE_META_DATA
* meta-data}.</strong>
* </p>
*
* @return The settings activity name.
*/
public static String getSettingsActivityName(AccessibilityServiceInfo info) {
return IMPL.getSettingsActivityName(info);
}
/**
* Whether this service can retrieve the current window's content.
* <p>
* <strong>Statically set from {@link AccessibilityService#SERVICE_META_DATA
* meta-data}.</strong>
* </p>
*
* @return True window content can be retrieved.
*/
public static boolean getCanRetrieveWindowContent(AccessibilityServiceInfo info) {
return IMPL.getCanRetrieveWindowContent(info);
}
/**
* Description of the accessibility service.
* <p>
* <strong>Statically set from {@link AccessibilityService#SERVICE_META_DATA
* meta-data}.</strong>
* </p>
*
* @return The description.
*/
public static String getDescription(AccessibilityServiceInfo info) {
return IMPL.getDescription(info);
}
/**
* Returns the string representation of a feedback type. For example,
* {@link AccessibilityServiceInfo#FEEDBACK_SPOKEN} is represented by the
* string FEEDBACK_SPOKEN.
*
* @param feedbackType The feedback type.
* @return The string representation.
*/
public static String feedbackTypeToString(int feedbackType) {
StringBuilder builder = new StringBuilder();
builder.append("[");
while (feedbackType > 0) {
final int feedbackTypeFlag = 1 << Integer.numberOfTrailingZeros(feedbackType);
feedbackType &= ~feedbackTypeFlag;
if (builder.length() > 1) {
builder.append(", ");
}
switch (feedbackTypeFlag) {
case AccessibilityServiceInfo.FEEDBACK_AUDIBLE:
builder.append("FEEDBACK_AUDIBLE");
break;
case AccessibilityServiceInfo.FEEDBACK_HAPTIC:
builder.append("FEEDBACK_HAPTIC");
break;
case AccessibilityServiceInfo.FEEDBACK_GENERIC:
builder.append("FEEDBACK_GENERIC");
break;
case AccessibilityServiceInfo.FEEDBACK_SPOKEN:
builder.append("FEEDBACK_SPOKEN");
break;
case AccessibilityServiceInfo.FEEDBACK_VISUAL:
builder.append("FEEDBACK_VISUAL");
break;
}
}
builder.append("]");
return builder.toString();
}
/**
* Returns the string representation of a flag. For example,
* {@link AccessibilityServiceInfo#DEFAULT} is represented by the
* string DEFAULT.
*
* @param flag The flag.
* @return The string representation.
*/
public static String flagToString(int flag) {
switch (flag) {
case DEFAULT:
return "DEFAULT";
case FLAG_INCLUDE_NOT_IMPORTANT_VIEWS:
return "FLAG_INCLUDE_NOT_IMPORTANT_VIEWS";
case FLAG_REQUEST_TOUCH_EXPLORATION_MODE:
return "FLAG_REQUEST_TOUCH_EXPLORATION_MODE";
case FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
return "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
case FLAG_REPORT_VIEW_IDS:
return "FLAG_REPORT_VIEW_IDS";
case FLAG_REQUEST_FILTER_KEY_EVENTS:
return "FLAG_REQUEST_FILTER_KEY_EVENTS";
default:
return null;
}
}
/**
* Returns the bit mask of capabilities this accessibility service has such as
* being able to retrieve the active window content, etc.
*
* @param info The service info whose capabilities to get.
* @return The capability bit mask.
*
* @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
* @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
* @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
* @see #CAPABILITY_CAN_FILTER_KEY_EVENTS
*/
public static int getCapabilities(AccessibilityServiceInfo info) {
return IMPL.getCapabilities(info);
}
/**
* Returns the string representation of a capability. For example,
* {@link #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT} is represented
* by the string CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT.
*
* @param capability The capability.
* @return The string representation.
*/
public static String capabilityToString(int capability) {
switch (capability) {
case CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT:
return "CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT";
case CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION:
return "CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION";
case CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
return "CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
case CAPABILITY_CAN_FILTER_KEY_EVENTS:
return "CAPABILITY_CAN_FILTER_KEY_EVENTS";
default:
return "UNKNOWN";
}
}
}
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 android.support.v4.accessibilityservice;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.pm.ResolveInfo;
/**
* ICS implementation of the new APIs in AccessibilityServiceInfo.
*/
class AccessibilityServiceInfoCompatIcs {
public static boolean getCanRetrieveWindowContent(AccessibilityServiceInfo info) {
return info.getCanRetrieveWindowContent();
}
public static String getDescription(AccessibilityServiceInfo info) {
return info.getDescription();
}
public static String getId(AccessibilityServiceInfo info) {
return info.getId();
}
public static ResolveInfo getResolveInfo(AccessibilityServiceInfo info) {
return info.getResolveInfo();
}
public static String getSettingsActivityName(AccessibilityServiceInfo info) {
return info.getSettingsActivityName();
}
}
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 android.support.v4.accessibilityservice;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.pm.ResolveInfo;
/**
* ICS implementation of the new APIs in AccessibilityServiceInfo.
*/
class AccessibilityServiceInfoCompatJellyBeanMr2 {
public static int getCapabilities(AccessibilityServiceInfo info) {
return info.getCapabilities();
}
}
@@ -0,0 +1,6 @@
<body>
Support android.accessibilityservice classes to assist with development of applications for
android API level 4 or later.
</body>
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 android.support.v4.animation;
import android.os.Build;
abstract public class AnimatorCompatHelper {
static AnimatorProvider IMPL;
static {
if (Build.VERSION.SDK_INT >= 12) {
IMPL = new HoneycombMr1AnimatorCompatProvider();
} else {
IMPL = new DonutAnimatorCompatProvider();
}
}
public static ValueAnimatorCompat emptyValueAnimator() {
return IMPL.emptyValueAnimator();
}
AnimatorCompatHelper() {
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 android.support.v4.animation;
/**
* <p>An animation listener that receives notifications from an animation.
* Notifications indicate animation related events, such as the end or the
* repetition of the animation.</p>
*
* @hide
*/
public interface AnimatorListenerCompat {
/**
* <p>Notifies the start of the animation.</p>
*
* @param animation The started animation.
*/
void onAnimationStart(ValueAnimatorCompat animation);
/**
* <p>Notifies the end of the animation. This callback is not invoked
* for animations with repeat count set to INFINITE.</p>
*
* @param animation The animation which reached its end.
*/
void onAnimationEnd(ValueAnimatorCompat animation);
/**
* <p>Notifies the cancellation of the animation. This callback is not invoked
* for animations with repeat count set to INFINITE.</p>
*
* @param animation The animation which was canceled.
*/
void onAnimationCancel(ValueAnimatorCompat animation);
/**
* <p>Notifies the repetition of the animation.</p>
*
* @param animation The animation which was repeated.
*/
void onAnimationRepeat(ValueAnimatorCompat animation);
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 android.support.v4.animation;
/**
* A simple interface to do things in animation pulse.
* <p>
* Before Honeycomb, it uses a simple Handler to mimic animation callback.
* <p>
* This is only a minimal implementation which is why this class is hidden.
*
* @hide
*/
interface AnimatorProvider {
/**
* Provides a simple ValueAnimator w/o any start or end values. It provides the same
* Animator callback interface.
*/
ValueAnimatorCompat emptyValueAnimator();
}
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 android.support.v4.animation;
/**
* Implementors of this interface can add themselves as update listeners
* to a <code>ValueAnimator</code> instance to receive callbacks on every animation
* frame, after the current frame's values have been calculated for that
* <code>ValueAnimator</code>.
*
* @hide
*/
public interface AnimatorUpdateListenerCompat {
/**
* <p>Notifies the occurrence of another frame of the animation.</p>
*
* @param animation The animation which was repeated.
*/
void onAnimationUpdate(ValueAnimatorCompat animation);
}
@@ -0,0 +1,151 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 android.support.v4.animation;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/**
* Provides similar functionality to Animators on platforms prior to Honeycomb.
* <p>
* This is not a fully implemented API which is why it is not public.
*
* @hide
*/
class DonutAnimatorCompatProvider implements AnimatorProvider {
@Override
public ValueAnimatorCompat emptyValueAnimator() {
return new DonutFloatValueAnimator();
}
private static class DonutFloatValueAnimator implements ValueAnimatorCompat {
List<AnimatorListenerCompat> mListeners = new ArrayList<AnimatorListenerCompat>();
List<AnimatorUpdateListenerCompat> mUpdateListeners
= new ArrayList<AnimatorUpdateListenerCompat>();
View mTarget;
private long mStartTime;
private long mDuration = 200;
private float mFraction = 0f;
private boolean mStarted = false;
private boolean mEnded = false;
public DonutFloatValueAnimator() {
}
private Runnable mLoopRunnable = new Runnable() {
@Override
public void run() {
long dt = getTime() - mStartTime;
float fraction = dt * 1f / mDuration;
if (fraction > 1f || mTarget.getParent() == null) {
fraction = 1f;
}
mFraction = fraction;
notifyUpdateListeners();
if (mFraction >= 1f) {
dispatchEnd();
} else {
mTarget.postDelayed(mLoopRunnable, 16);
}
}
};
private void notifyUpdateListeners() {
for (int i = mUpdateListeners.size() - 1; i >= 0; i--) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
@Override
public void setTarget(View view) {
mTarget = view;
}
@Override
public void addListener(AnimatorListenerCompat listener) {
mListeners.add(listener);
}
@Override
public void setDuration(long duration) {
if (!mStarted) {
mDuration = duration;
}
}
@Override
public void start() {
if (mStarted) {
return;
}
mStarted = true;
dispatchStart();
mFraction = 0f;
mStartTime = getTime();
mTarget.postDelayed(mLoopRunnable, 16);
}
private long getTime() {
return mTarget.getDrawingTime();
}
private void dispatchStart() {
for (int i = mListeners.size() - 1; i >= 0; i--) {
mListeners.get(i).onAnimationStart(this);
}
}
private void dispatchEnd() {
for (int i = mListeners.size() - 1; i >= 0; i--) {
mListeners.get(i).onAnimationEnd(this);
}
}
private void dispatchCancel() {
for (int i = mListeners.size() - 1; i >= 0; i--) {
mListeners.get(i).onAnimationCancel(this);
}
}
@Override
public void cancel() {
if (mEnded) {
return;
}
mEnded = true;
if (mStarted) {
dispatchCancel();
}
dispatchEnd();
}
@Override
public void addUpdateListener(AnimatorUpdateListenerCompat animatorUpdateListener) {
mUpdateListeners.add(animatorUpdateListener);
}
@Override
public float getAnimatedFraction() {
return mFraction;
}
}
}
@@ -0,0 +1,124 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 android.support.v4.animation;
import android.animation.Animator;
import android.animation.PropertyValuesHolder;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.view.View;
/**
* Uses framework Animators to provide ValueAnimatorCompat interface.
* <p>
* This is not a fully implemented API which is why it is not public.
*
* @hide
*/
class HoneycombMr1AnimatorCompatProvider implements AnimatorProvider {
@Override
public ValueAnimatorCompat emptyValueAnimator() {
return new HoneycombValueAnimatorCompat(ValueAnimator.ofFloat(0f, 1f));
}
static class HoneycombValueAnimatorCompat implements ValueAnimatorCompat {
final Animator mWrapped;
public HoneycombValueAnimatorCompat(Animator wrapped) {
mWrapped = wrapped;
}
@Override
public void setTarget(View view) {
mWrapped.setTarget(view);
}
@Override
public void addListener(AnimatorListenerCompat listener) {
mWrapped.addListener(new AnimatorListenerCompatWrapper(listener, this));
}
@Override
public void setDuration(long duration) {
mWrapped.setDuration(duration);
}
@Override
public void start() {
mWrapped.start();
}
@Override
public void cancel() {
mWrapped.cancel();
}
@Override
public void addUpdateListener(final AnimatorUpdateListenerCompat animatorUpdateListener) {
if (mWrapped instanceof ValueAnimator) {
((ValueAnimator) mWrapped).addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
animatorUpdateListener
.onAnimationUpdate(HoneycombValueAnimatorCompat.this);
}
});
}
}
@Override
public float getAnimatedFraction() {
return ((ValueAnimator) mWrapped).getAnimatedFraction();
}
}
static class AnimatorListenerCompatWrapper implements Animator.AnimatorListener {
final AnimatorListenerCompat mWrapped;
final ValueAnimatorCompat mValueAnimatorCompat;
public AnimatorListenerCompatWrapper(
AnimatorListenerCompat wrapped, ValueAnimatorCompat valueAnimatorCompat) {
mWrapped = wrapped;
mValueAnimatorCompat = valueAnimatorCompat;
}
@Override
public void onAnimationStart(Animator animation) {
mWrapped.onAnimationStart(mValueAnimatorCompat);
}
@Override
public void onAnimationEnd(Animator animation) {
mWrapped.onAnimationEnd(mValueAnimatorCompat);
}
@Override
public void onAnimationCancel(Animator animation) {
mWrapped.onAnimationCancel(mValueAnimatorCompat);
}
@Override
public void onAnimationRepeat(Animator animation) {
mWrapped.onAnimationRepeat(mValueAnimatorCompat);
}
}
}
Loaded 100 of 655 files, more files were not shown because too many files have changed in this diff. Show more