aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2020-02-07 20:01:19 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2020-02-07 20:01:19 +0000
commitb33d46b14166c25a7b8ca81c01334a6dbc7582eb (patch)
tree4d861e241f9c3ca10a5d38f9b146c6730d39c631
parent7639caa34a0ac21f64327ecbc3ce1a7da8730c09 (diff)
parent4c715c2f54bc171e7850b86f86015c05d6d8a5e3 (diff)
downloadsupport-b33d46b14166c25a7b8ca81c01334a6dbc7582eb.tar.gz
Merge "RecyclerView MergeAdapter" into androidx-master-dev
-rw-r--r--leanback/leanback-preference/src/main/java/androidx/leanback/preference/LeanbackListPreferenceDialogFragment.java4
-rw-r--r--leanback/leanback-preference/src/main/java/androidx/leanback/preference/LeanbackListPreferenceDialogFragmentCompat.java4
-rw-r--r--leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridActivity.java4
-rw-r--r--leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java8
-rw-r--r--leanback/leanback/src/main/java/androidx/leanback/app/DetailsFragment.java2
-rw-r--r--leanback/leanback/src/main/java/androidx/leanback/app/DetailsSupportFragment.java2
-rw-r--r--leanback/leanback/src/main/java/androidx/leanback/widget/GridLayoutManager.java4
-rw-r--r--leanback/leanback/src/main/java/androidx/leanback/widget/RecyclerViewParallax.java2
-rw-r--r--recyclerview/recyclerview/api/1.2.0-alpha01.txt39
-rw-r--r--recyclerview/recyclerview/api/current.txt39
-rw-r--r--recyclerview/recyclerview/api/public_plus_experimental_1.2.0-alpha01.txt39
-rw-r--r--recyclerview/recyclerview/api/public_plus_experimental_current.txt39
-rw-r--r--recyclerview/recyclerview/api/restricted_1.2.0-alpha01.txt39
-rw-r--r--recyclerview/recyclerview/api/restricted_current.txt39
-rw-r--r--recyclerview/recyclerview/res/values/ids.xml2
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/BaseRecyclerViewInstrumentationTest.java8
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/GridLayoutManagerCacheTest.java2
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/GridLayoutManagerTest.java15
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/ItemTouchHelperTest.java4
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerBaseConfigSetTest.java4
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerCacheTest.java8
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java12
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/MergeAdapterSubject.kt129
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/MergeAdapterTest.kt1296
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewAnimationsTest.java4
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewCacheTest.java10
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java14
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewPrefetchTest.java2
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java4
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerCacheTest.java2
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerTest.java59
-rw-r--r--recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java11
-rw-r--r--recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/MergeAdapter.java348
-rw-r--r--recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/MergeAdapterController.java464
-rw-r--r--recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/NestedAdapterWrapper.java191
-rw-r--r--recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java253
-rw-r--r--recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ViewTypeStorage.java197
-rw-r--r--samples/Support7Demos/src/main/java/com/example/android/supportv7/util/SortedListActivity.java2
-rw-r--r--samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/AnimatedRecyclerView.java2
-rw-r--r--samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/BaseLayoutManagerActivity.java2
-rw-r--r--samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/GridLayoutManagerActivity.java2
-rw-r--r--samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/NestedRecyclerViewActivity.java2
-rw-r--r--samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/RecyclerViewActivity.java2
-rw-r--r--samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/StableIdActivity.java2
-rw-r--r--samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyItemHolder.java2
-rw-r--r--samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/DemoHolder.java2
-rw-r--r--samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/touch/ItemTouchHelperActivity.java3
-rw-r--r--samples/SupportCarDemos/src/main/java/com/example/androidx/car/PagedListViewActivity.java2
-rw-r--r--samples/SupportTransitionDemos/src/main/java/com/example/android/support/transition/widget/RecyclerViewUsage.java2
-rw-r--r--viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/ParallelNestedScrollingActivity.kt6
50 files changed, 3180 insertions, 154 deletions
diff --git a/leanback/leanback-preference/src/main/java/androidx/leanback/preference/LeanbackListPreferenceDialogFragment.java b/leanback/leanback-preference/src/main/java/androidx/leanback/preference/LeanbackListPreferenceDialogFragment.java
index 5f774ae3da7..73df6275b47 100644
--- a/leanback/leanback-preference/src/main/java/androidx/leanback/preference/LeanbackListPreferenceDialogFragment.java
+++ b/leanback/leanback-preference/src/main/java/androidx/leanback/preference/LeanbackListPreferenceDialogFragment.java
@@ -237,7 +237,7 @@ public class LeanbackListPreferenceDialogFragment extends LeanbackPreferenceDial
@Override
public void onItemClick(ViewHolder viewHolder) {
- final int index = viewHolder.getAdapterPosition();
+ final int index = viewHolder.getAbsoluteAdapterPosition();
if (index == RecyclerView.NO_POSITION) {
return;
}
@@ -298,7 +298,7 @@ public class LeanbackListPreferenceDialogFragment extends LeanbackPreferenceDial
@Override
public void onItemClick(ViewHolder viewHolder) {
- final int index = viewHolder.getAdapterPosition();
+ final int index = viewHolder.getAbsoluteAdapterPosition();
if (index == RecyclerView.NO_POSITION) {
return;
}
diff --git a/leanback/leanback-preference/src/main/java/androidx/leanback/preference/LeanbackListPreferenceDialogFragmentCompat.java b/leanback/leanback-preference/src/main/java/androidx/leanback/preference/LeanbackListPreferenceDialogFragmentCompat.java
index 88fd21db81e..5dbf27dba56 100644
--- a/leanback/leanback-preference/src/main/java/androidx/leanback/preference/LeanbackListPreferenceDialogFragmentCompat.java
+++ b/leanback/leanback-preference/src/main/java/androidx/leanback/preference/LeanbackListPreferenceDialogFragmentCompat.java
@@ -240,7 +240,7 @@ public class LeanbackListPreferenceDialogFragmentCompat extends
@Override
public void onItemClick(ViewHolder viewHolder) {
- final int index = viewHolder.getAdapterPosition();
+ final int index = viewHolder.getAbsoluteAdapterPosition();
if (index == RecyclerView.NO_POSITION) {
return;
}
@@ -295,7 +295,7 @@ public class LeanbackListPreferenceDialogFragmentCompat extends
@Override
public void onItemClick(ViewHolder viewHolder) {
- final int index = viewHolder.getAdapterPosition();
+ final int index = viewHolder.getAbsoluteAdapterPosition();
if (index == RecyclerView.NO_POSITION) {
return;
}
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridActivity.java b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridActivity.java
index 408ef9495f6..34764dadf91 100644
--- a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridActivity.java
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridActivity.java
@@ -237,7 +237,7 @@ public class GridActivity extends Activity {
}
if (mRequestLayoutOnFocus) {
RecyclerView.ViewHolder vh = mGridView.getChildViewHolder(v);
- int position = vh.getAdapterPosition();
+ int position = vh.getAbsoluteAdapterPosition();
updateSize(v, position);
}
}
@@ -393,7 +393,7 @@ public class GridActivity extends Activity {
if (mRequestLayoutOnFocus) {
if (v == view) {
RecyclerView.ViewHolder vh = mGridView.getChildViewHolder(v);
- int position = vh.getAdapterPosition();
+ int position = vh.getAbsoluteAdapterPosition();
updateSize(v, position);
}
view.requestLayout();
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
index a14925b8eaa..8b44059add3 100644
--- a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
@@ -1581,7 +1581,7 @@ public class GridWidgetTest {
int scrollPos = 0;
while (true) {
final View view = mGridView.getChildAt(mGridView.getChildCount() - 1);
- final int pos = mGridView.getChildViewHolder(view).getAdapterPosition();
+ final int pos = mGridView.getChildViewHolder(view).getAbsoluteAdapterPosition();
if (scrollPos != pos) {
scrollPos = pos;
mActivityTestRule.runOnUiThread(new Runnable() {
@@ -2509,7 +2509,8 @@ public class GridWidgetTest {
@Override
public void run() {
final int removeIndex = mGridView.getChildViewHolder(
- mGridView.getChildAt(mGridView.getChildCount() - 1)).getAdapterPosition();
+ mGridView.getChildAt(
+ mGridView.getChildCount() - 1)).getAbsoluteAdapterPosition();
mActivity.removeItems(removeIndex, 1);
}
});
@@ -2682,7 +2683,8 @@ public class GridWidgetTest {
@Override
public void run() {
final int removeIndex = mGridView.getChildViewHolder(
- mGridView.getChildAt(mGridView.getChildCount() - 1)).getAdapterPosition();
+ mGridView.getChildAt(
+ mGridView.getChildCount() - 1)).getAbsoluteAdapterPosition();
mActivity.removeItems(removeIndex, 1);
}
});
diff --git a/leanback/leanback/src/main/java/androidx/leanback/app/DetailsFragment.java b/leanback/leanback/src/main/java/androidx/leanback/app/DetailsFragment.java
index 36cac480503..8e2d5bdb958 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/app/DetailsFragment.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/app/DetailsFragment.java
@@ -667,7 +667,7 @@ public class DetailsFragment extends BaseFragment {
RowPresenter rowPresenter = (RowPresenter) bridgeViewHolder.getPresenter();
onSetRowStatus(rowPresenter,
rowPresenter.getRowViewHolder(bridgeViewHolder.getViewHolder()),
- bridgeViewHolder.getAdapterPosition(),
+ bridgeViewHolder.getAbsoluteAdapterPosition(),
selectedPosition, selectedSubPosition);
}
}
diff --git a/leanback/leanback/src/main/java/androidx/leanback/app/DetailsSupportFragment.java b/leanback/leanback/src/main/java/androidx/leanback/app/DetailsSupportFragment.java
index 9b0fae62d0c..e889c3cd6e1 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/app/DetailsSupportFragment.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/app/DetailsSupportFragment.java
@@ -662,7 +662,7 @@ public class DetailsSupportFragment extends BaseSupportFragment {
RowPresenter rowPresenter = (RowPresenter) bridgeViewHolder.getPresenter();
onSetRowStatus(rowPresenter,
rowPresenter.getRowViewHolder(bridgeViewHolder.getViewHolder()),
- bridgeViewHolder.getAdapterPosition(),
+ bridgeViewHolder.getAbsoluteAdapterPosition(),
selectedPosition, selectedSubPosition);
}
}
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/GridLayoutManager.java b/leanback/leanback/src/main/java/androidx/leanback/widget/GridLayoutManager.java
index 379587baa3e..de3270ca55d 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/GridLayoutManager.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/GridLayoutManager.java
@@ -2120,7 +2120,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
int totalItems = 0;
for (int i = 0; i < scrapSize; i++) {
- int pos = scrapList.get(i).getAdapterPosition();
+ int pos = scrapList.get(i).getAbsoluteAdapterPosition();
if (pos >= 0) {
mDisappearingPositions[totalItems++] = pos;
}
@@ -3658,7 +3658,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
void onChildRecycled(RecyclerView.ViewHolder holder) {
- final int position = holder.getAdapterPosition();
+ final int position = holder.getAbsoluteAdapterPosition();
if (position != NO_POSITION) {
mChildrenStates.saveOffscreenView(holder.itemView, position);
}
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/RecyclerViewParallax.java b/leanback/leanback/src/main/java/androidx/leanback/widget/RecyclerViewParallax.java
index 8a1fd146da3..b36f1590c84 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/RecyclerViewParallax.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/RecyclerViewParallax.java
@@ -155,7 +155,7 @@ public class RecyclerViewParallax extends Parallax<RecyclerViewParallax.ChildPos
}
View firstChild = recyclerView.getLayoutManager().getChildAt(0);
ViewHolder vh = recyclerView.findContainingViewHolder(firstChild);
- int firstPosition = vh.getAdapterPosition();
+ int firstPosition = vh.getAbsoluteAdapterPosition();
if (firstPosition < mAdapterPosition) {
source.setIntPropertyValue(getIndex(), IntProperty.UNKNOWN_AFTER);
} else {
diff --git a/recyclerview/recyclerview/api/1.2.0-alpha01.txt b/recyclerview/recyclerview/api/1.2.0-alpha01.txt
index 89f44795785..344616858bd 100644
--- a/recyclerview/recyclerview/api/1.2.0-alpha01.txt
+++ b/recyclerview/recyclerview/api/1.2.0-alpha01.txt
@@ -323,6 +323,35 @@ package androidx.recyclerview.widget {
method public void onRemoved(int, int);
}
+ public final class MergeAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder> {
+ ctor @java.lang.SafeVarargs public MergeAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!...);
+ ctor @java.lang.SafeVarargs public MergeAdapter(androidx.recyclerview.widget.MergeAdapter.Config, androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!...);
+ ctor public MergeAdapter(java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!>);
+ ctor public MergeAdapter(androidx.recyclerview.widget.MergeAdapter.Config, java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!>);
+ method public boolean addAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public boolean addAdapter(int, androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!> getCopyOfAdapters();
+ method public int getItemCount();
+ method public void onBindViewHolder(androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+ method public boolean onFailedToRecycleView(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewAttachedToWindow(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewDetachedFromWindow(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewRecycled(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public boolean removeAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ }
+
+ public static class MergeAdapter.Config {
+ field public static final androidx.recyclerview.widget.MergeAdapter.Config DEFAULT;
+ field public final boolean isolateViewTypes;
+ }
+
+ public static class MergeAdapter.Config.Builder {
+ ctor public MergeAdapter.Config.Builder();
+ method public androidx.recyclerview.widget.MergeAdapter.Config build();
+ method public androidx.recyclerview.widget.MergeAdapter.Config.Builder setIsolateViewTypes(boolean);
+ }
+
public abstract class OrientationHelper {
method public static androidx.recyclerview.widget.OrientationHelper! createHorizontalHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
method public static androidx.recyclerview.widget.OrientationHelper! createOrientationHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int);
@@ -469,6 +498,7 @@ package androidx.recyclerview.widget {
ctor public RecyclerView.Adapter();
method public final void bindViewHolder(VH, int);
method public final VH createViewHolder(android.view.ViewGroup, int);
+ method public int findRelativeAdapterPositionIn(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>, androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
method public abstract int getItemCount();
method public long getItemId(int);
method public int getItemViewType(int);
@@ -760,7 +790,9 @@ package androidx.recyclerview.widget {
ctor public RecyclerView.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
ctor public RecyclerView.LayoutParams(android.view.ViewGroup.LayoutParams!);
ctor public RecyclerView.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
- method public int getViewAdapterPosition();
+ method public int getAbsoluteAdapterPosition();
+ method public int getBindingAdapterPosition();
+ method @Deprecated public int getViewAdapterPosition();
method public int getViewLayoutPosition();
method @Deprecated public int getViewPosition();
method public boolean isItemChanged();
@@ -888,7 +920,10 @@ package androidx.recyclerview.widget {
public abstract static class RecyclerView.ViewHolder {
ctor public RecyclerView.ViewHolder(android.view.View);
- method public final int getAdapterPosition();
+ method public final int getAbsoluteAdapterPosition();
+ method @Deprecated public final int getAdapterPosition();
+ method public final androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>? getBindingAdapter();
+ method public final int getBindingAdapterPosition();
method public final long getItemId();
method public final int getItemViewType();
method public final int getLayoutPosition();
diff --git a/recyclerview/recyclerview/api/current.txt b/recyclerview/recyclerview/api/current.txt
index 89f44795785..344616858bd 100644
--- a/recyclerview/recyclerview/api/current.txt
+++ b/recyclerview/recyclerview/api/current.txt
@@ -323,6 +323,35 @@ package androidx.recyclerview.widget {
method public void onRemoved(int, int);
}
+ public final class MergeAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder> {
+ ctor @java.lang.SafeVarargs public MergeAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!...);
+ ctor @java.lang.SafeVarargs public MergeAdapter(androidx.recyclerview.widget.MergeAdapter.Config, androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!...);
+ ctor public MergeAdapter(java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!>);
+ ctor public MergeAdapter(androidx.recyclerview.widget.MergeAdapter.Config, java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!>);
+ method public boolean addAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public boolean addAdapter(int, androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!> getCopyOfAdapters();
+ method public int getItemCount();
+ method public void onBindViewHolder(androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+ method public boolean onFailedToRecycleView(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewAttachedToWindow(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewDetachedFromWindow(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewRecycled(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public boolean removeAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ }
+
+ public static class MergeAdapter.Config {
+ field public static final androidx.recyclerview.widget.MergeAdapter.Config DEFAULT;
+ field public final boolean isolateViewTypes;
+ }
+
+ public static class MergeAdapter.Config.Builder {
+ ctor public MergeAdapter.Config.Builder();
+ method public androidx.recyclerview.widget.MergeAdapter.Config build();
+ method public androidx.recyclerview.widget.MergeAdapter.Config.Builder setIsolateViewTypes(boolean);
+ }
+
public abstract class OrientationHelper {
method public static androidx.recyclerview.widget.OrientationHelper! createHorizontalHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
method public static androidx.recyclerview.widget.OrientationHelper! createOrientationHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int);
@@ -469,6 +498,7 @@ package androidx.recyclerview.widget {
ctor public RecyclerView.Adapter();
method public final void bindViewHolder(VH, int);
method public final VH createViewHolder(android.view.ViewGroup, int);
+ method public int findRelativeAdapterPositionIn(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>, androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
method public abstract int getItemCount();
method public long getItemId(int);
method public int getItemViewType(int);
@@ -760,7 +790,9 @@ package androidx.recyclerview.widget {
ctor public RecyclerView.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
ctor public RecyclerView.LayoutParams(android.view.ViewGroup.LayoutParams!);
ctor public RecyclerView.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
- method public int getViewAdapterPosition();
+ method public int getAbsoluteAdapterPosition();
+ method public int getBindingAdapterPosition();
+ method @Deprecated public int getViewAdapterPosition();
method public int getViewLayoutPosition();
method @Deprecated public int getViewPosition();
method public boolean isItemChanged();
@@ -888,7 +920,10 @@ package androidx.recyclerview.widget {
public abstract static class RecyclerView.ViewHolder {
ctor public RecyclerView.ViewHolder(android.view.View);
- method public final int getAdapterPosition();
+ method public final int getAbsoluteAdapterPosition();
+ method @Deprecated public final int getAdapterPosition();
+ method public final androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>? getBindingAdapter();
+ method public final int getBindingAdapterPosition();
method public final long getItemId();
method public final int getItemViewType();
method public final int getLayoutPosition();
diff --git a/recyclerview/recyclerview/api/public_plus_experimental_1.2.0-alpha01.txt b/recyclerview/recyclerview/api/public_plus_experimental_1.2.0-alpha01.txt
index 89f44795785..344616858bd 100644
--- a/recyclerview/recyclerview/api/public_plus_experimental_1.2.0-alpha01.txt
+++ b/recyclerview/recyclerview/api/public_plus_experimental_1.2.0-alpha01.txt
@@ -323,6 +323,35 @@ package androidx.recyclerview.widget {
method public void onRemoved(int, int);
}
+ public final class MergeAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder> {
+ ctor @java.lang.SafeVarargs public MergeAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!...);
+ ctor @java.lang.SafeVarargs public MergeAdapter(androidx.recyclerview.widget.MergeAdapter.Config, androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!...);
+ ctor public MergeAdapter(java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!>);
+ ctor public MergeAdapter(androidx.recyclerview.widget.MergeAdapter.Config, java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!>);
+ method public boolean addAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public boolean addAdapter(int, androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!> getCopyOfAdapters();
+ method public int getItemCount();
+ method public void onBindViewHolder(androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+ method public boolean onFailedToRecycleView(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewAttachedToWindow(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewDetachedFromWindow(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewRecycled(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public boolean removeAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ }
+
+ public static class MergeAdapter.Config {
+ field public static final androidx.recyclerview.widget.MergeAdapter.Config DEFAULT;
+ field public final boolean isolateViewTypes;
+ }
+
+ public static class MergeAdapter.Config.Builder {
+ ctor public MergeAdapter.Config.Builder();
+ method public androidx.recyclerview.widget.MergeAdapter.Config build();
+ method public androidx.recyclerview.widget.MergeAdapter.Config.Builder setIsolateViewTypes(boolean);
+ }
+
public abstract class OrientationHelper {
method public static androidx.recyclerview.widget.OrientationHelper! createHorizontalHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
method public static androidx.recyclerview.widget.OrientationHelper! createOrientationHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int);
@@ -469,6 +498,7 @@ package androidx.recyclerview.widget {
ctor public RecyclerView.Adapter();
method public final void bindViewHolder(VH, int);
method public final VH createViewHolder(android.view.ViewGroup, int);
+ method public int findRelativeAdapterPositionIn(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>, androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
method public abstract int getItemCount();
method public long getItemId(int);
method public int getItemViewType(int);
@@ -760,7 +790,9 @@ package androidx.recyclerview.widget {
ctor public RecyclerView.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
ctor public RecyclerView.LayoutParams(android.view.ViewGroup.LayoutParams!);
ctor public RecyclerView.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
- method public int getViewAdapterPosition();
+ method public int getAbsoluteAdapterPosition();
+ method public int getBindingAdapterPosition();
+ method @Deprecated public int getViewAdapterPosition();
method public int getViewLayoutPosition();
method @Deprecated public int getViewPosition();
method public boolean isItemChanged();
@@ -888,7 +920,10 @@ package androidx.recyclerview.widget {
public abstract static class RecyclerView.ViewHolder {
ctor public RecyclerView.ViewHolder(android.view.View);
- method public final int getAdapterPosition();
+ method public final int getAbsoluteAdapterPosition();
+ method @Deprecated public final int getAdapterPosition();
+ method public final androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>? getBindingAdapter();
+ method public final int getBindingAdapterPosition();
method public final long getItemId();
method public final int getItemViewType();
method public final int getLayoutPosition();
diff --git a/recyclerview/recyclerview/api/public_plus_experimental_current.txt b/recyclerview/recyclerview/api/public_plus_experimental_current.txt
index 89f44795785..344616858bd 100644
--- a/recyclerview/recyclerview/api/public_plus_experimental_current.txt
+++ b/recyclerview/recyclerview/api/public_plus_experimental_current.txt
@@ -323,6 +323,35 @@ package androidx.recyclerview.widget {
method public void onRemoved(int, int);
}
+ public final class MergeAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder> {
+ ctor @java.lang.SafeVarargs public MergeAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!...);
+ ctor @java.lang.SafeVarargs public MergeAdapter(androidx.recyclerview.widget.MergeAdapter.Config, androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!...);
+ ctor public MergeAdapter(java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!>);
+ ctor public MergeAdapter(androidx.recyclerview.widget.MergeAdapter.Config, java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!>);
+ method public boolean addAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public boolean addAdapter(int, androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!> getCopyOfAdapters();
+ method public int getItemCount();
+ method public void onBindViewHolder(androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+ method public boolean onFailedToRecycleView(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewAttachedToWindow(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewDetachedFromWindow(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewRecycled(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public boolean removeAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ }
+
+ public static class MergeAdapter.Config {
+ field public static final androidx.recyclerview.widget.MergeAdapter.Config DEFAULT;
+ field public final boolean isolateViewTypes;
+ }
+
+ public static class MergeAdapter.Config.Builder {
+ ctor public MergeAdapter.Config.Builder();
+ method public androidx.recyclerview.widget.MergeAdapter.Config build();
+ method public androidx.recyclerview.widget.MergeAdapter.Config.Builder setIsolateViewTypes(boolean);
+ }
+
public abstract class OrientationHelper {
method public static androidx.recyclerview.widget.OrientationHelper! createHorizontalHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
method public static androidx.recyclerview.widget.OrientationHelper! createOrientationHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int);
@@ -469,6 +498,7 @@ package androidx.recyclerview.widget {
ctor public RecyclerView.Adapter();
method public final void bindViewHolder(VH, int);
method public final VH createViewHolder(android.view.ViewGroup, int);
+ method public int findRelativeAdapterPositionIn(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>, androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
method public abstract int getItemCount();
method public long getItemId(int);
method public int getItemViewType(int);
@@ -760,7 +790,9 @@ package androidx.recyclerview.widget {
ctor public RecyclerView.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
ctor public RecyclerView.LayoutParams(android.view.ViewGroup.LayoutParams!);
ctor public RecyclerView.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
- method public int getViewAdapterPosition();
+ method public int getAbsoluteAdapterPosition();
+ method public int getBindingAdapterPosition();
+ method @Deprecated public int getViewAdapterPosition();
method public int getViewLayoutPosition();
method @Deprecated public int getViewPosition();
method public boolean isItemChanged();
@@ -888,7 +920,10 @@ package androidx.recyclerview.widget {
public abstract static class RecyclerView.ViewHolder {
ctor public RecyclerView.ViewHolder(android.view.View);
- method public final int getAdapterPosition();
+ method public final int getAbsoluteAdapterPosition();
+ method @Deprecated public final int getAdapterPosition();
+ method public final androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>? getBindingAdapter();
+ method public final int getBindingAdapterPosition();
method public final long getItemId();
method public final int getItemViewType();
method public final int getLayoutPosition();
diff --git a/recyclerview/recyclerview/api/restricted_1.2.0-alpha01.txt b/recyclerview/recyclerview/api/restricted_1.2.0-alpha01.txt
index 9a8954b2d5c..4d87eb87c6d 100644
--- a/recyclerview/recyclerview/api/restricted_1.2.0-alpha01.txt
+++ b/recyclerview/recyclerview/api/restricted_1.2.0-alpha01.txt
@@ -323,6 +323,35 @@ package androidx.recyclerview.widget {
method public void onRemoved(int, int);
}
+ public final class MergeAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder> {
+ ctor @java.lang.SafeVarargs public MergeAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!...);
+ ctor @java.lang.SafeVarargs public MergeAdapter(androidx.recyclerview.widget.MergeAdapter.Config, androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!...);
+ ctor public MergeAdapter(java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!>);
+ ctor public MergeAdapter(androidx.recyclerview.widget.MergeAdapter.Config, java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!>);
+ method public boolean addAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public boolean addAdapter(int, androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!> getCopyOfAdapters();
+ method public int getItemCount();
+ method public void onBindViewHolder(androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+ method public boolean onFailedToRecycleView(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewAttachedToWindow(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewDetachedFromWindow(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewRecycled(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public boolean removeAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ }
+
+ public static class MergeAdapter.Config {
+ field public static final androidx.recyclerview.widget.MergeAdapter.Config DEFAULT;
+ field public final boolean isolateViewTypes;
+ }
+
+ public static class MergeAdapter.Config.Builder {
+ ctor public MergeAdapter.Config.Builder();
+ method public androidx.recyclerview.widget.MergeAdapter.Config build();
+ method public androidx.recyclerview.widget.MergeAdapter.Config.Builder setIsolateViewTypes(boolean);
+ }
+
public abstract class OrientationHelper {
method public static androidx.recyclerview.widget.OrientationHelper! createHorizontalHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
method public static androidx.recyclerview.widget.OrientationHelper! createOrientationHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!, @androidx.recyclerview.widget.RecyclerView.Orientation int);
@@ -469,6 +498,7 @@ package androidx.recyclerview.widget {
ctor public RecyclerView.Adapter();
method public final void bindViewHolder(VH, int);
method public final VH createViewHolder(android.view.ViewGroup, int);
+ method public int findRelativeAdapterPositionIn(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>, androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
method public abstract int getItemCount();
method public long getItemId(int);
method public int getItemViewType(int);
@@ -760,7 +790,9 @@ package androidx.recyclerview.widget {
ctor public RecyclerView.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
ctor public RecyclerView.LayoutParams(android.view.ViewGroup.LayoutParams!);
ctor public RecyclerView.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
- method public int getViewAdapterPosition();
+ method public int getAbsoluteAdapterPosition();
+ method public int getBindingAdapterPosition();
+ method @Deprecated public int getViewAdapterPosition();
method public int getViewLayoutPosition();
method @Deprecated public int getViewPosition();
method public boolean isItemChanged();
@@ -891,7 +923,10 @@ package androidx.recyclerview.widget {
public abstract static class RecyclerView.ViewHolder {
ctor public RecyclerView.ViewHolder(android.view.View);
- method public final int getAdapterPosition();
+ method public final int getAbsoluteAdapterPosition();
+ method @Deprecated public final int getAdapterPosition();
+ method public final androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>? getBindingAdapter();
+ method public final int getBindingAdapterPosition();
method public final long getItemId();
method public final int getItemViewType();
method public final int getLayoutPosition();
diff --git a/recyclerview/recyclerview/api/restricted_current.txt b/recyclerview/recyclerview/api/restricted_current.txt
index 9a8954b2d5c..4d87eb87c6d 100644
--- a/recyclerview/recyclerview/api/restricted_current.txt
+++ b/recyclerview/recyclerview/api/restricted_current.txt
@@ -323,6 +323,35 @@ package androidx.recyclerview.widget {
method public void onRemoved(int, int);
}
+ public final class MergeAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder> {
+ ctor @java.lang.SafeVarargs public MergeAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!...);
+ ctor @java.lang.SafeVarargs public MergeAdapter(androidx.recyclerview.widget.MergeAdapter.Config, androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!...);
+ ctor public MergeAdapter(java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!>);
+ ctor public MergeAdapter(androidx.recyclerview.widget.MergeAdapter.Config, java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!>);
+ method public boolean addAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public boolean addAdapter(int, androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public java.util.List<androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>!> getCopyOfAdapters();
+ method public int getItemCount();
+ method public void onBindViewHolder(androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+ method public boolean onFailedToRecycleView(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewAttachedToWindow(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewDetachedFromWindow(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onViewRecycled(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public boolean removeAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ }
+
+ public static class MergeAdapter.Config {
+ field public static final androidx.recyclerview.widget.MergeAdapter.Config DEFAULT;
+ field public final boolean isolateViewTypes;
+ }
+
+ public static class MergeAdapter.Config.Builder {
+ ctor public MergeAdapter.Config.Builder();
+ method public androidx.recyclerview.widget.MergeAdapter.Config build();
+ method public androidx.recyclerview.widget.MergeAdapter.Config.Builder setIsolateViewTypes(boolean);
+ }
+
public abstract class OrientationHelper {
method public static androidx.recyclerview.widget.OrientationHelper! createHorizontalHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
method public static androidx.recyclerview.widget.OrientationHelper! createOrientationHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!, @androidx.recyclerview.widget.RecyclerView.Orientation int);
@@ -469,6 +498,7 @@ package androidx.recyclerview.widget {
ctor public RecyclerView.Adapter();
method public final void bindViewHolder(VH, int);
method public final VH createViewHolder(android.view.ViewGroup, int);
+ method public int findRelativeAdapterPositionIn(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>, androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
method public abstract int getItemCount();
method public long getItemId(int);
method public int getItemViewType(int);
@@ -760,7 +790,9 @@ package androidx.recyclerview.widget {
ctor public RecyclerView.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
ctor public RecyclerView.LayoutParams(android.view.ViewGroup.LayoutParams!);
ctor public RecyclerView.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
- method public int getViewAdapterPosition();
+ method public int getAbsoluteAdapterPosition();
+ method public int getBindingAdapterPosition();
+ method @Deprecated public int getViewAdapterPosition();
method public int getViewLayoutPosition();
method @Deprecated public int getViewPosition();
method public boolean isItemChanged();
@@ -891,7 +923,10 @@ package androidx.recyclerview.widget {
public abstract static class RecyclerView.ViewHolder {
ctor public RecyclerView.ViewHolder(android.view.View);
- method public final int getAdapterPosition();
+ method public final int getAbsoluteAdapterPosition();
+ method @Deprecated public final int getAdapterPosition();
+ method public final androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>? getBindingAdapter();
+ method public final int getBindingAdapterPosition();
method public final long getItemId();
method public final int getItemViewType();
method public final int getLayoutPosition();
diff --git a/recyclerview/recyclerview/res/values/ids.xml b/recyclerview/recyclerview/res/values/ids.xml
index fba1db4d74e..4ed0ecd89a7 100644
--- a/recyclerview/recyclerview/res/values/ids.xml
+++ b/recyclerview/recyclerview/res/values/ids.xml
@@ -16,4 +16,4 @@
<resources>
<!-- ItemTouchHelper uses this id to save a View's original elevation. -->
<item type="id" name="item_touch_helper_previous_elevation"/>
-</resources>
+</resources> \ No newline at end of file
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/BaseRecyclerViewInstrumentationTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/BaseRecyclerViewInstrumentationTest.java
index 0ed5a9f8bb9..16e231735d8 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/BaseRecyclerViewInstrumentationTest.java
@@ -381,6 +381,7 @@ abstract public class BaseRecyclerViewInstrumentationTest {
@Override
public void putRecycledView(RecyclerView.ViewHolder scrap) {
assertNull(scrap.mOwnerRecyclerView);
+ assertNull(scrap.getBindingAdapter());
super.putRecycledView(scrap);
}
};
@@ -395,7 +396,7 @@ abstract public class BaseRecyclerViewInstrumentationTest {
if (!vh.isRemoved()) {
assertNotSame("If getItemOffsets is called, child should have a valid"
+ " adapter position unless it is removed : " + vh,
- vh.getAdapterPosition(), RecyclerView.NO_POSITION);
+ vh.getAbsoluteAdapterPosition(), RecyclerView.NO_POSITION);
}
}
});
@@ -911,7 +912,8 @@ abstract public class BaseRecyclerViewInstrumentationTest {
@Override
public void onBindViewHolder(@NonNull TestViewHolder holder, int position) {
assertNotNull(holder.mOwnerRecyclerView);
- assertEquals(position, holder.getAdapterPosition());
+ assertSame(this, holder.getBindingAdapter());
+ assertEquals(position, holder.getAbsoluteAdapterPosition());
final Item item = mItems.get(position);
getTextViewInHolder(holder).setText(item.getDisplayText());
holder.itemView.setBackgroundColor(position % 2 == 0 ? 0xFFFF0000 : 0xFF0000FF);
@@ -932,7 +934,7 @@ abstract public class BaseRecyclerViewInstrumentationTest {
@Override
public void onViewRecycled(@NonNull TestViewHolder holder) {
super.onViewRecycled(holder);
- final int adapterPosition = holder.getAdapterPosition();
+ final int adapterPosition = holder.getAbsoluteAdapterPosition();
final boolean shouldHavePosition = !holder.isRemoved() && holder.isBound() &&
!holder.isAdapterPositionUnknown() && !holder.isInvalid();
String log = "Position check for " + holder.toString();
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/GridLayoutManagerCacheTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/GridLayoutManagerCacheTest.java
index 8306bf22165..94db40d6d68 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/GridLayoutManagerCacheTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/GridLayoutManagerCacheTest.java
@@ -70,7 +70,7 @@ public class GridLayoutManagerCacheTest extends BaseGridLayoutManagerTest {
private boolean cachedViewsContains(int position) {
// Note: can't make assumptions about order here, so just check all cached views
for (int i = 0; i < cachedViews().size(); i++) {
- if (cachedViews().get(i).getAdapterPosition() == position) return true;
+ if (cachedViews().get(i).getAbsoluteAdapterPosition() == position) return true;
}
return false;
}
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/GridLayoutManagerTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/GridLayoutManagerTest.java
index 3ca041c05fb..d667e964065 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/GridLayoutManagerTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/GridLayoutManagerTest.java
@@ -138,6 +138,7 @@ public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
RecyclerView mAttachedRv;
@Override
+ @SuppressWarnings("deprecation") // used for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
@@ -148,7 +149,6 @@ public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation using this for kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -177,7 +177,8 @@ public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
waitForIdleScroll(recyclerView);
focusedView = recyclerView.getFocusedChild();
assertEquals(Math.min(pos + 3, mAdapter.getItemCount() - 1),
- recyclerView.getChildViewHolder(focusedView).getAdapterPosition());
+ recyclerView
+ .getChildViewHolder(focusedView).getAbsoluteAdapterPosition());
pos += 3;
}
}
@@ -335,6 +336,7 @@ public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
RecyclerView mAttachedRv;
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
@@ -343,7 +345,6 @@ public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation using this for kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -425,6 +426,7 @@ public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
RecyclerView mAttachedRv;
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
@@ -433,7 +435,6 @@ public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation using this for kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -520,6 +521,7 @@ public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
new GridTestAdapter(itemCount, 1) {
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
@@ -528,7 +530,6 @@ public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation using this for kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -611,6 +612,7 @@ public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
.orientation(HORIZONTAL).reverseLayout(false),
new GridTestAdapter(itemCount, 1) {
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
@@ -619,7 +621,6 @@ public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation using this for kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -1158,7 +1159,7 @@ public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
for (int i = 0; i < childCount; i++) {
View child = mGlm.getChildAt(i);
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(child);
- if (holder.getAdapterPosition() == removePos) {
+ if (holder.getAbsoluteAdapterPosition() == removePos) {
toBeRemoved = holder;
} else {
toBeMoved.add(holder);
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/ItemTouchHelperTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/ItemTouchHelperTest.java
index 90e7ee27103..eb54e5411b4 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/ItemTouchHelperTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/ItemTouchHelperTest.java
@@ -433,8 +433,8 @@ public class ItemTouchHelperTest extends BaseRecyclerViewInstrumentationTest {
MoveRecord(RecyclerView.ViewHolder from, RecyclerView.ViewHolder to) {
this.from = from;
this.to = to;
- fromPos = from.getAdapterPosition();
- toPos = to.getAdapterPosition();
+ fromPos = from.getAbsoluteAdapterPosition();
+ toPos = to.getAbsoluteAdapterPosition();
}
}
}
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerBaseConfigSetTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerBaseConfigSetTest.java
index 70d87ff3a99..d5a7937f459 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerBaseConfigSetTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerBaseConfigSetTest.java
@@ -259,7 +259,7 @@ public class LinearLayoutManagerBaseConfigSetTest extends BaseLinearLayoutManage
scrollBy(size * 2);
assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView))));
assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView));
- assertThat(vh.getAdapterPosition(), is(500));
+ assertThat(vh.getAbsoluteAdapterPosition(), is(500));
scrollBy(size * 2);
assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView)));
}
@@ -293,7 +293,7 @@ public class LinearLayoutManagerBaseConfigSetTest extends BaseLinearLayoutManage
scrollBy(-size * 2);
assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView))));
assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView));
- assertThat(vh.getAdapterPosition(), is(500));
+ assertThat(vh.getAbsoluteAdapterPosition(), is(500));
scrollBy(-size * 2);
assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView)));
}
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerCacheTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerCacheTest.java
index 6a79e4ef099..bc34cb9b46a 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerCacheTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerCacheTest.java
@@ -109,12 +109,14 @@ public class LinearLayoutManagerCacheTest extends BaseLinearLayoutManagerTest {
int lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();
int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
assertEquals(1, cachedViews().size());
- int prefetchedPosition = cachedViews().get(0).getAdapterPosition();
+ int prefetchedPosition = cachedViews().get(0).getAbsoluteAdapterPosition();
if (mConfig.mReverseLayout == reverseScroll) {
- // Pos scroll on pos layout, or reverse scroll on reverse layout = toward last
+ // Pos scroll on pos layout, or reverse scroll on reverse layout = toward
+ // last
assertEquals(lastVisibleItemPosition + 1, prefetchedPosition);
} else {
- // Pos scroll on reverse layout, or reverse scroll on pos layout = toward first
+ // Pos scroll on reverse layout, or reverse scroll on pos layout = toward
+ // first
assertEquals(firstVisibleItemPosition - 1, prefetchedPosition);
}
}
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java
index 42f95207915..9a497d83852 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java
@@ -209,6 +209,7 @@ public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
RecyclerView mAttachedRv;
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
// Good to have colors for debugging
@@ -216,7 +217,6 @@ public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation used to support kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -295,6 +295,7 @@ public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
RecyclerView mAttachedRv;
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
// Good to have colors for debugging
@@ -302,7 +303,6 @@ public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation used to support kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -387,6 +387,7 @@ public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
RecyclerView mAttachedRv;
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
// Good to have colors for debugging
@@ -394,7 +395,6 @@ public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation used to support kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -482,6 +482,7 @@ public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
RecyclerView mAttachedRv;
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
// Good to have colors for debugging
@@ -489,7 +490,6 @@ public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation used to support kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -576,6 +576,7 @@ public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
RecyclerView mAttachedRv;
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
// Good to have colors for debugging
@@ -583,7 +584,6 @@ public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation used to support kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -772,7 +772,7 @@ public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
for (int i = 0; i < childCount; i++) {
View child = mLayoutManager.getChildAt(i);
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(child);
- if (holder.getAdapterPosition() == removePos) {
+ if (holder.getAbsoluteAdapterPosition() == removePos) {
toBeRemoved = holder;
} else {
toBeMoved.add(holder);
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/MergeAdapterSubject.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/MergeAdapterSubject.kt
new file mode 100644
index 00000000000..f884f95e473
--- /dev/null
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/MergeAdapterSubject.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2020 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 androidx.recyclerview.widget
+
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth.assertAbout
+import com.google.common.truth.Truth.assertThat
+
+/**
+ * Helper subject to write nicer looking MergeAdapter tests.
+ */
+internal class MergeAdapterSubject(
+ metadata: FailureMetadata,
+ private val adapter: MergeAdapter
+) : Subject(
+ metadata,
+ adapter
+) {
+ fun hasItemCount(itemCount: Int) {
+ assertThat(adapter.itemCount).isEqualTo(itemCount)
+ }
+
+ fun hasStateRestorationStrategy(strategy: RecyclerView.Adapter.StateRestorationStrategy) {
+ assertThat(adapter.stateRestorationStrategy).isEqualTo(strategy)
+ }
+
+ fun bindView(
+ recyclerView: RecyclerView,
+ globalPosition: Int
+ ): BindingSubject {
+ if (recyclerView.adapter == null) {
+ recyclerView.adapter = adapter
+ } else {
+ check(recyclerView.adapter == adapter) {
+ "recyclerview is bound to another adapter"
+ }
+ }
+ // clear state
+ recyclerView.mState.apply {
+ mItemCount = adapter.itemCount
+ mLayoutStep = RecyclerView.State.STEP_LAYOUT
+ }
+ return assertAbout(
+ BindingSubject.Factory(
+ recyclerView = recyclerView
+ )
+ ).that(globalPosition)
+ }
+
+ fun canRestoreState() {
+ assertThat(adapter.canRestoreState()).isTrue()
+ }
+
+ fun cannotRestoreState() {
+ assertThat(adapter.canRestoreState()).isFalse()
+ }
+
+ object Factory : Subject.Factory<MergeAdapterSubject, MergeAdapter> {
+ override fun createSubject(metadata: FailureMetadata, actual: MergeAdapter):
+ MergeAdapterSubject {
+ return MergeAdapterSubject(
+ metadata = metadata,
+ adapter = actual
+ )
+ }
+ }
+
+ companion object {
+ fun assertThat(mergeAdapter: MergeAdapter) =
+ assertAbout(Factory).that(mergeAdapter)
+ }
+
+ class BindingSubject(
+ metadata: FailureMetadata,
+ recyclerView: RecyclerView,
+ globalPosition: Int
+ ) : Subject(
+ metadata,
+ globalPosition
+ ) {
+ private val viewHolder by lazy {
+ val view = recyclerView.mRecycler.getViewForPosition(globalPosition)
+ val layoutParams = view.layoutParams
+ check(layoutParams is RecyclerView.LayoutParams)
+ val viewHolder = layoutParams.mViewHolder
+ viewHolder as MergeAdapterTest.MergeAdapterViewHolder
+ }
+
+ internal fun verifyBoundTo(
+ adapter: MergeAdapterTest.NestedTestAdapter,
+ localPosition: Int
+ ) {
+ assertThat(viewHolder.boundItem()).isEqualTo(adapter.getItemAt(localPosition))
+ assertThat(viewHolder.boundLocalPosition()).isEqualTo(localPosition)
+ assertThat(viewHolder.boundAdapter()).isSameInstanceAs(adapter)
+ }
+
+ class Factory(
+ private val recyclerView: RecyclerView
+ ) : Subject.Factory<BindingSubject, Int> {
+ override fun createSubject(
+ metadata: FailureMetadata,
+ globalPosition: Int
+ ):
+ BindingSubject {
+ return BindingSubject(
+ metadata = metadata,
+ recyclerView = recyclerView,
+ globalPosition = globalPosition
+ )
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/MergeAdapterTest.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/MergeAdapterTest.kt
new file mode 100644
index 00000000000..ab179d78086
--- /dev/null
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/MergeAdapterTest.kt
@@ -0,0 +1,1296 @@
+/*
+ * Copyright 2020 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 androidx.recyclerview.widget
+
+import android.content.Context
+import android.view.View
+import android.view.View.MeasureSpec.AT_MOST
+import android.view.ViewGroup
+import androidx.recyclerview.widget.MergeAdapterSubject.Companion.assertThat
+import androidx.recyclerview.widget.MergeAdapterTest.LoggingAdapterObserver.Event.Changed
+import androidx.recyclerview.widget.MergeAdapterTest.LoggingAdapterObserver.Event.DataSetChanged
+import androidx.recyclerview.widget.MergeAdapterTest.LoggingAdapterObserver.Event.Inserted
+import androidx.recyclerview.widget.MergeAdapterTest.LoggingAdapterObserver.Event.Moved
+import androidx.recyclerview.widget.MergeAdapterTest.LoggingAdapterObserver.Event.Removed
+import androidx.recyclerview.widget.MergeAdapterTest.LoggingAdapterObserver.Event.StateRestorationStrategy
+import androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationStrategy.ALLOW
+import androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationStrategy.PREVENT
+import androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationStrategy.PREVENT_WHEN_EMPTY
+import androidx.recyclerview.widget.RecyclerView.LayoutParams
+import androidx.recyclerview.widget.RecyclerView.LayoutParams.MATCH_PARENT
+import androidx.recyclerview.widget.RecyclerView.NO_POSITION
+import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class MergeAdapterTest {
+ private lateinit var recyclerView: RecyclerView
+
+ @Before
+ fun init() {
+ val context = ApplicationProvider.getApplicationContext<Context>()
+ recyclerView = RecyclerView(
+ context
+ ).also {
+ it.layoutManager = LinearLayoutManager(context)
+ it.itemAnimator = null
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ fun attachAndDetachAll() {
+ val merge = MergeAdapter()
+ val adapter1 = NestedTestAdapter(10,
+ getLayoutParams = {
+ LayoutParams(MATCH_PARENT, 3)
+ })
+ merge.addAdapter(adapter1)
+ recyclerView.adapter = merge
+ measureAndLayout(100, 50)
+ assertThat(recyclerView.childCount).isEqualTo(10)
+ assertThat(adapter1.attachedViewHolders()).hasSize(10)
+ measureAndLayout(100, 0)
+ assertThat(recyclerView.childCount).isEqualTo(0)
+ assertThat(adapter1.attachedViewHolders()).isEmpty()
+
+ val adapter2 = NestedTestAdapter(5,
+ getLayoutParams = {
+ LayoutParams(MATCH_PARENT, 3)
+ })
+ merge.addAdapter(adapter2)
+ assertThat(recyclerView.isLayoutRequested).isTrue()
+ measureAndLayout(100, 200)
+ assertThat(recyclerView.childCount).isEqualTo(15)
+ assertThat(adapter1.attachedViewHolders()).hasSize(10)
+ assertThat(adapter2.attachedViewHolders()).hasSize(5)
+ merge.removeAdapter(adapter1)
+ assertThat(recyclerView.isLayoutRequested).isTrue()
+ measureAndLayout(100, 200)
+ assertThat(recyclerView.childCount).isEqualTo(5)
+ assertThat(adapter1.attachedViewHolders()).isEmpty()
+ assertThat(adapter2.attachedViewHolders()).hasSize(5)
+ measureAndLayout(100, 0)
+ assertThat(adapter2.attachedViewHolders()).isEmpty()
+ }
+
+ @Test
+ @UiThreadTest
+ fun mergeInsideMerge() {
+ val merge = MergeAdapter()
+ val adapter1 = NestedTestAdapter(10)
+ merge.addAdapter(adapter1)
+ recyclerView.adapter = merge
+ measureAndLayout(100, 100)
+ assertThat(recyclerView.childCount).isEqualTo(10)
+ merge.removeAdapter(adapter1)
+ assertThat(recyclerView.isLayoutRequested).isTrue()
+ measureAndLayout(100, 100)
+ assertThat(adapter1.attachedViewHolders()).isEmpty()
+ }
+
+ @UiThreadTest
+ @Test
+ fun recycleOnRemoval() {
+ val merge = MergeAdapter()
+ val adapter1 = NestedTestAdapter(10)
+ merge.addAdapter(adapter1)
+ recyclerView.adapter = merge
+ measureAndLayout(100, 100)
+ assertThat(recyclerView.childCount).isEqualTo(10)
+ adapter1.removeItems(3, 2)
+ assertThat(recyclerView.isLayoutRequested).isTrue()
+ measureAndLayout(100, 100)
+ assertThat(adapter1.recycledViewHolders()).hasSize(2)
+ assertThat(adapter1.attachedViewHolders()).hasSize(8)
+ assertThat(adapter1.attachedViewHolders()).containsNoneIn(adapter1.recycledViewHolders())
+ }
+
+ @UiThreadTest
+ @Test
+ fun checkAttachDetach_adapterAdditions() {
+ val merge = MergeAdapter()
+ val adapter1 = NestedTestAdapter(0)
+ merge.addAdapter(adapter1)
+ recyclerView.adapter = merge
+ measureAndLayout(100, 100)
+ adapter1.addItems(0, 3)
+ assertThat(recyclerView.isLayoutRequested).isTrue()
+ measureAndLayout(100, 100)
+ assertThat(adapter1.attachedViewHolders()).hasSize(3)
+ assertThat(adapter1.recycledViewHolders()).hasSize(0)
+ }
+
+ @UiThreadTest
+ @Test
+ fun failedToRecycleTest() {
+ val adapter1 = NestedTestAdapter(10)
+ val adapter2 = NestedTestAdapter(5)
+ val merge = MergeAdapter(adapter1, adapter2)
+ recyclerView.adapter = merge
+ measureAndLayout(100, 200)
+ val viewHolder = recyclerView.findViewHolderForAdapterPosition(12)
+ check(viewHolder != null) {
+ "should have that view holder for position 12"
+ }
+ assertThat(adapter2.attachedViewHolders()).contains(viewHolder)
+ // give it transient state so that it won't be recycled
+ viewHolder.itemView.setHasTransientState(true)
+ adapter2.removeItems(2, 2)
+ assertThat(recyclerView.isLayoutRequested).isTrue()
+ measureAndLayout(100, 200)
+ assertThat(adapter2.attachedViewHolders()).hasSize(3)
+ assertThat(adapter2.failedToRecycleViewHolders()).contains(viewHolder)
+ assertThat(adapter2.failedToRecycleViewHolders()).hasSize(1)
+ assertThat(adapter2.attachedViewHolders()).doesNotContain(viewHolder)
+ }
+
+ @Suppress("DEPRECATION")
+ @UiThreadTest
+ @Test
+ fun localAdapterPositions() {
+ val adapter1 = NestedTestAdapter(10)
+ val adapter2 = NestedTestAdapter(4)
+ val adapter3 = NestedTestAdapter(8)
+ val merge = MergeAdapter(adapter1, adapter2, adapter3)
+ recyclerView.adapter = merge
+ measureAndLayout(100, 100)
+ assertThat(recyclerView.childCount).isEqualTo(22)
+ (0 until 22).forEach {
+ val viewHolder = checkNotNull(recyclerView.findViewHolderForAdapterPosition(it))
+ assertThat(recyclerView.getChildAdapterPosition(viewHolder.itemView)).isEqualTo(it)
+ assertThat(viewHolder.absoluteAdapterPosition).isEqualTo(it)
+ }
+ (0 until 10).forEach {
+ val viewHolder = checkNotNull(recyclerView.findViewHolderForAdapterPosition(it))
+ assertThat(viewHolder.bindingAdapterPosition).isEqualTo(it)
+ assertThat(viewHolder.bindingAdapter).isSameInstanceAs(adapter1)
+ }
+
+ (10 until 14).forEach {
+ val viewHolder = checkNotNull(recyclerView.findViewHolderForAdapterPosition(it))
+ assertThat(viewHolder.bindingAdapterPosition).isEqualTo(it - 10)
+ assertThat(viewHolder.adapterPosition).isEqualTo(it - 10)
+ assertThat(viewHolder.bindingAdapter).isSameInstanceAs(adapter2)
+ }
+
+ (14 until 22).forEach {
+ val viewHolder = checkNotNull(recyclerView.findViewHolderForAdapterPosition(it))
+ assertThat(viewHolder.bindingAdapterPosition).isEqualTo(it - 14)
+ assertThat(viewHolder.adapterPosition).isEqualTo(it - 14)
+ assertThat(viewHolder.bindingAdapter).isSameInstanceAs(adapter3)
+ }
+ }
+
+ @Suppress("LocalVariableName")
+ @UiThreadTest
+ @Test
+ fun localAdapterPositions_nested() {
+ val adapter1_1 = NestedTestAdapter(10)
+ val adapter1_2 = NestedTestAdapter(5)
+ val adapter1 = MergeAdapter(adapter1_1, adapter1_2)
+ val adapter2_1 = NestedTestAdapter(3)
+ val adapter2_2 = NestedTestAdapter(6)
+ val adapter2 = MergeAdapter(adapter2_1, adapter2_2)
+ val merge = MergeAdapter(adapter1, adapter2)
+ recyclerView.adapter = merge
+ measureAndLayout(100, 100)
+ assertThat(recyclerView.childCount).isEqualTo(24)
+ (0 until 24).forEach {
+ val viewHolder = checkNotNull(recyclerView.findViewHolderForAdapterPosition(it))
+ assertThat(viewHolder.absoluteAdapterPosition).isEqualTo(it)
+ assertThat(recyclerView.getChildAdapterPosition(viewHolder.itemView)).isEqualTo(it)
+ }
+ (0 until 10).forEach {
+ val viewHolder = checkNotNull(recyclerView.findViewHolderForAdapterPosition(it))
+ assertThat(viewHolder.bindingAdapterPosition).isEqualTo(it)
+ assertThat(viewHolder.bindingAdapter).isSameInstanceAs(adapter1_1)
+ }
+ (10 until 15).forEach {
+ val viewHolder = checkNotNull(recyclerView.findViewHolderForAdapterPosition(it))
+ assertThat(viewHolder.bindingAdapterPosition).isEqualTo(it - 10)
+ assertThat(viewHolder.bindingAdapter).isSameInstanceAs(adapter1_2)
+ }
+ (15 until 18).forEach {
+ val viewHolder = checkNotNull(recyclerView.findViewHolderForAdapterPosition(it))
+ assertThat(viewHolder.bindingAdapterPosition).isEqualTo(it - 15)
+ assertThat(viewHolder.bindingAdapter).isSameInstanceAs(adapter2_1)
+ }
+ (18 until 24).forEach {
+ val viewHolder = checkNotNull(recyclerView.findViewHolderForAdapterPosition(it))
+ assertThat(viewHolder.bindingAdapterPosition).isEqualTo(it - 18)
+ assertThat(viewHolder.bindingAdapter).isSameInstanceAs(adapter2_2)
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ fun localAdapterPositions_notIncluded() {
+ val adapter1 = NestedTestAdapter(10)
+ val merge = MergeAdapter(adapter1)
+ recyclerView.adapter = merge
+ measureAndLayout(100, 100)
+ assertThat(recyclerView.childCount).isEqualTo(10)
+ val vh = checkNotNull(recyclerView.findViewHolderForAdapterPosition(3))
+ assertThat(vh.bindingAdapterPosition).isEqualTo(3)
+
+ val toBeRemoved = checkNotNull(recyclerView.findViewHolderForAdapterPosition(4))
+ adapter1.removeItems(4, 1)
+ assertThat(toBeRemoved.bindingAdapterPosition).isEqualTo(NO_POSITION)
+ assertThat(toBeRemoved.absoluteAdapterPosition).isEqualTo(NO_POSITION)
+ measureAndLayout(100, 100)
+ assertThat(toBeRemoved.bindingAdapter).isNull()
+
+ recyclerView.adapter = null
+ measureAndLayout(100, 100)
+ assertThat(vh.bindingAdapterPosition).isEqualTo(NO_POSITION)
+ assertThat(vh.absoluteAdapterPosition).isEqualTo(NO_POSITION)
+ assertThat(vh.bindingAdapter).isNull()
+ }
+
+ @UiThreadTest
+ @Test
+ fun attachDetachTest() {
+ val adapter1 = NestedTestAdapter(10)
+ val adapter2 = NestedTestAdapter(5)
+ val merge = MergeAdapter(adapter1, adapter2)
+ recyclerView.adapter = merge
+ assertThat(adapter1.attachedRecyclerViews()).containsExactly(recyclerView)
+ assertThat(adapter2.attachedRecyclerViews()).containsExactly(recyclerView)
+ val adapter3 = NestedTestAdapter(3)
+ merge.addAdapter(adapter3)
+ assertThat(adapter3.attachedRecyclerViews()).containsExactly(recyclerView)
+ merge.removeAdapter(adapter3)
+ assertThat(adapter3.attachedRecyclerViews()).isEmpty()
+ recyclerView.adapter = null
+ assertThat(adapter1.attachedRecyclerViews()).isEmpty()
+ assertThat(adapter2.attachedRecyclerViews()).isEmpty()
+ }
+
+ @UiThreadTest
+ @Test
+ fun attachDetachTest_multipleRecyclerViews() {
+ val recyclerView2 = RecyclerView(ApplicationProvider.getApplicationContext())
+ val adapter1 = NestedTestAdapter(10)
+ val adapter2 = NestedTestAdapter(5)
+ val merge = MergeAdapter(adapter1, adapter2)
+ recyclerView.adapter = merge
+ recyclerView2.adapter = merge
+ assertThat(adapter1.attachedRecyclerViews()).containsExactly(recyclerView, recyclerView2)
+ assertThat(adapter2.attachedRecyclerViews()).containsExactly(recyclerView, recyclerView2)
+ val adapter3 = NestedTestAdapter(3)
+ merge.addAdapter(adapter3)
+ assertThat(adapter3.attachedRecyclerViews()).containsExactly(recyclerView, recyclerView2)
+ merge.removeAdapter(adapter3)
+ assertThat(adapter3.attachedRecyclerViews()).isEmpty()
+ recyclerView.adapter = null
+ assertThat(adapter1.attachedRecyclerViews()).containsExactly(recyclerView2)
+ assertThat(adapter2.attachedRecyclerViews()).containsExactly(recyclerView2)
+ recyclerView2.adapter = null
+ assertThat(adapter1.attachedRecyclerViews()).isEmpty()
+ assertThat(adapter2.attachedRecyclerViews()).isEmpty()
+ assertThat(adapter3.attachedRecyclerViews()).isEmpty()
+ }
+
+ @Test
+ @UiThreadTest
+ fun adapterRemoval() {
+ val adapter1 = NestedTestAdapter(3)
+ val adapter2 = NestedTestAdapter(5)
+ val merge = MergeAdapter(adapter1, adapter2)
+ recyclerView.adapter = merge
+ measureAndLayout(100, 100)
+ assertThat(recyclerView.childCount).isEqualTo(8)
+ assertThat(merge.removeAdapter(adapter1)).isTrue()
+ measureAndLayout(100, 100)
+ assertThat(recyclerView.childCount).isEqualTo(5)
+ assertThat(merge.removeAdapter(adapter1)).isFalse()
+ assertThat(merge.removeAdapter(adapter2)).isTrue()
+ measureAndLayout(100, 100)
+ assertThat(recyclerView.childCount).isEqualTo(0)
+ }
+
+ @Test
+ @UiThreadTest
+ fun boundAdapter() {
+ val adapter1 = NestedTestAdapter(3)
+ val adapter2 = NestedTestAdapter(5)
+ val merge = MergeAdapter(adapter1, adapter2)
+ recyclerView.adapter = merge
+ measureAndLayout(100, 100)
+ assertThat(recyclerView.childCount).isEqualTo(8)
+ val adapter1ViewHolders = (0 until 3).map {
+ checkNotNull(recyclerView.findViewHolderForAdapterPosition(it))
+ }
+ val adapter2ViewHolders = (3 until 8).map {
+ checkNotNull(recyclerView.findViewHolderForAdapterPosition(it))
+ }
+ adapter1ViewHolders.forEach {
+ assertThat(it.bindingAdapter).isSameInstanceAs(adapter1)
+ }
+ adapter2ViewHolders.forEach {
+ assertThat(it.bindingAdapter).isSameInstanceAs(adapter2)
+ }
+ assertThat(merge.removeAdapter(adapter1)).isTrue()
+ // even when position is invalid, we should still be able to find the bound adapter
+ adapter1ViewHolders.forEach {
+ assertThat(it.bindingAdapter).isSameInstanceAs(adapter1)
+ }
+ measureAndLayout(100, 100)
+ assertThat(recyclerView.childCount).isEqualTo(5)
+ adapter1ViewHolders.forEach {
+ assertThat(it.bindingAdapter).isNull()
+ }
+ assertThat(merge.removeAdapter(adapter1)).isFalse()
+ assertThat(merge.removeAdapter(adapter2)).isTrue()
+ measureAndLayout(100, 100)
+ assertThat(recyclerView.childCount).isEqualTo(0)
+ adapter2ViewHolders.forEach {
+ assertThat(it.bindingAdapter).isNull()
+ }
+ }
+
+ private fun measureAndLayout(@Suppress("SameParameterValue") width: Int, height: Int) {
+ measure(width, height)
+ layout(width, height)
+ }
+
+ private fun measure(width: Int, height: Int) {
+ recyclerView.measure(AT_MOST or width, AT_MOST or height)
+ }
+
+ private fun layout(width: Int, height: Int) {
+ recyclerView.layout(0, 0, width, height)
+ }
+
+ @Test
+ fun size() {
+ val merge = MergeAdapter()
+ val observer = LoggingAdapterObserver(merge)
+ assertThat(merge).hasItemCount(0)
+ merge.addAdapter(NestedTestAdapter(0))
+ observer.assertEventsAndClear(
+ "Empty adapter shouldn't cause notify"
+ )
+
+ val adapter1 = NestedTestAdapter(3)
+ merge.addAdapter(adapter1)
+ assertThat(merge).hasItemCount(3)
+ observer.assertEventsAndClear(
+ "adapter with count should trigger notify",
+ Inserted(
+ positionStart = 0,
+ itemCount = 3
+ )
+ )
+
+ val adapter2 = NestedTestAdapter(5)
+ merge.addAdapter(adapter2)
+ assertThat(merge).hasItemCount(8)
+ observer.assertEventsAndClear(
+ "appended non-empty adapter should trigger insert event",
+ Inserted(
+ positionStart = 3,
+ itemCount = 5
+ )
+ )
+
+ val adapter3 = NestedTestAdapter(2)
+ merge.addAdapter(2, adapter3)
+ assertThat(merge).hasItemCount(10)
+ observer.assertEventsAndClear(
+ "appended non-empty adapter should trigger insert event in right index",
+ Inserted(
+ positionStart = 3,
+ itemCount = 2
+ )
+ )
+
+ merge.addAdapter(NestedTestAdapter(0))
+ assertThat(merge).hasItemCount(10)
+ observer.assertEventsAndClear(
+ "empty new adapter shouldn't trigger events"
+ )
+ }
+
+ @Test
+ fun nested_addition() {
+ val merge = MergeAdapter()
+ val observer = LoggingAdapterObserver(merge)
+
+ val adapter1 = NestedTestAdapter(0)
+ merge.addAdapter(adapter1)
+ observer.assertEventsAndClear("empty adapter triggers no events")
+
+ adapter1.addItems(positionStart = 0, itemCount = 3)
+ observer.assertEventsAndClear(
+ "non-empty adapter triggers an event",
+ Inserted(
+ positionStart = 0,
+ itemCount = 3
+ )
+ )
+ assertThat(merge).hasItemCount(3)
+ adapter1.addItems(positionStart = 1, itemCount = 2)
+ observer.assertEventsAndClear(
+ "inner adapter change should trigger an event",
+ Inserted(
+ positionStart = 1,
+ itemCount = 2
+ )
+ )
+ assertThat(merge).hasItemCount(5)
+ val adapter2 = NestedTestAdapter(2)
+ merge.addAdapter(adapter2)
+ observer.assertEventsAndClear(
+ "added adapter should trigger an event",
+ Inserted(
+ positionStart = 5,
+ itemCount = 2
+ )
+ )
+ assertThat(merge).hasItemCount(7)
+
+ adapter2.addItems(positionStart = 0, itemCount = 3)
+ observer.assertEventsAndClear(
+ "nested adapter prepends data",
+ Inserted(
+ positionStart = 5,
+ itemCount = 3
+ )
+ )
+ assertThat(merge).hasItemCount(10)
+
+ adapter2.addItems(positionStart = 2, itemCount = 4)
+ observer.assertEventsAndClear(
+ "nested adapter adds items with inner offset",
+ Inserted(
+ positionStart = 7,
+ itemCount = 4
+ )
+ )
+ assertThat(merge).hasItemCount(14)
+ }
+
+ @Test
+ fun nested_removal() {
+ val adapter1 = NestedTestAdapter(10)
+ val adapter2 = NestedTestAdapter(15)
+ val adapter3 = NestedTestAdapter(20)
+
+ val merge = MergeAdapter(adapter1, adapter2, adapter3)
+ val observer = LoggingAdapterObserver(merge)
+ assertThat(merge).hasItemCount(45)
+
+ adapter1.removeItems(positionStart = 0, itemCount = 2)
+ observer.assertEventsAndClear(
+ "removal from first adapter top",
+ Removed(
+ positionStart = 0,
+ itemCount = 2
+ )
+ )
+ assertThat(merge).hasItemCount(43)
+ adapter1.removeItems(positionStart = 2, itemCount = 1)
+ observer.assertEventsAndClear(
+ "removal from first adapter inner",
+ Removed(
+ positionStart = 2,
+ itemCount = 1
+ )
+ )
+ assertThat(merge).hasItemCount(42)
+ // now first adapter has size 7
+ adapter2.removeItems(positionStart = 0, itemCount = 3)
+ observer.assertEventsAndClear(
+ "removal from second adapter should be offset",
+ Removed(
+ positionStart = adapter1.itemCount,
+ itemCount = 3
+ )
+ )
+ assertThat(merge).hasItemCount(39)
+ adapter2.removeItems(positionStart = 6, itemCount = 4)
+ observer.assertEventsAndClear(
+ "inner item removal from middle adapter should be offset",
+ Removed(
+ positionStart = adapter1.itemCount + 6,
+ itemCount = 4
+ )
+ )
+ assertThat(merge).hasItemCount(35)
+
+ adapter3.removeItems(positionStart = 0, itemCount = 3)
+ observer.assertEventsAndClear(
+ "removal from last adapter should be offset by adapter 1 and 2",
+ Removed(
+ positionStart = adapter1.itemCount + adapter2.itemCount,
+ itemCount = 3
+ )
+ )
+
+ adapter3.removeItems(positionStart = 2, itemCount = 5)
+ observer.assertEventsAndClear(
+ "removal from inner items from last adapter should be offset by adapter 1 & 2",
+ Removed(
+ positionStart = adapter1.itemCount + adapter2.itemCount + 2,
+ itemCount = 5
+ )
+ )
+
+ merge.removeAdapter(adapter2)
+ observer.assertEventsAndClear(
+ "removing an adapter should trigger removal",
+ Removed(
+ positionStart = adapter1.itemCount,
+ itemCount = adapter2.itemCount
+ )
+ )
+ assertThat(merge).hasItemCount(adapter1.itemCount + adapter3.itemCount)
+ merge.removeAdapter(adapter1)
+ observer.assertEventsAndClear(
+ "removing first adapter should trigger removal",
+ Removed(
+ positionStart = 0,
+ itemCount = adapter1.itemCount
+ )
+ )
+ assertThat(merge).hasItemCount(adapter3.itemCount)
+ merge.removeAdapter(adapter3)
+ observer.assertEventsAndClear(
+ "removing last adapter should trigger a removal",
+ Removed(
+ positionStart = 0,
+ itemCount = adapter3.itemCount
+ )
+ )
+ assertThat(merge).hasItemCount(0)
+ }
+
+ @Test
+ fun nested_move() {
+ val adapter1 = NestedTestAdapter(10)
+ val adapter2 = NestedTestAdapter(15)
+ val adapter3 = NestedTestAdapter(20)
+ val merge = MergeAdapter(adapter1, adapter2, adapter3)
+ val observer = LoggingAdapterObserver(merge)
+ adapter1.moveItem(fromPosition = 3, toPosition = 5)
+ observer.assertEventsAndClear(
+ "move from first adapter should come as is",
+ Moved(
+ fromPosition = 3,
+ toPosition = 5
+ )
+ )
+ assertThat(merge).hasItemCount(45)
+ adapter2.moveItem(fromPosition = 2, toPosition = 4)
+ observer.assertEventsAndClear(
+ "move in adapter 2 should be offset",
+ Moved(
+ fromPosition = adapter1.itemCount + 2,
+ toPosition = adapter1.itemCount + 4
+ )
+ )
+ adapter3.moveItem(fromPosition = 7, toPosition = 2)
+ observer.assertEventsAndClear(
+ "move in adapter 3 should be offset by adapter 1 & 2",
+ Moved(
+ fromPosition = adapter1.itemCount + adapter2.itemCount + 7,
+ toPosition = adapter1.itemCount + adapter2.itemCount + 2
+ )
+ )
+ assertThat(merge).hasItemCount(45)
+ }
+
+ @Test
+ fun nested_itemChange_withPayload() = nested_itemChange("payload")
+
+ @Test
+ fun nested_itemChange_withoutPayload() = nested_itemChange(null)
+
+ fun nested_itemChange(payload: Any? = null) {
+ val adapter1 = NestedTestAdapter(10)
+ val adapter2 = NestedTestAdapter(15)
+ val adapter3 = NestedTestAdapter(20)
+ val merge = MergeAdapter(adapter1, adapter2, adapter3)
+ val observer = LoggingAdapterObserver(merge)
+
+ adapter1.changeItems(positionStart = 3, itemCount = 5, payload = payload)
+ observer.assertEventsAndClear(
+ "change from first adapter should come as is",
+ Changed(
+ positionStart = 3,
+ itemCount = 5,
+ payload = payload
+ )
+ )
+ assertThat(merge).hasItemCount(45)
+ adapter2.changeItems(positionStart = 2, itemCount = 4, payload = payload)
+ observer.assertEventsAndClear(
+ "change in adapter 2 should be offset",
+ Changed(
+ positionStart = adapter1.itemCount + 2,
+ itemCount = 4,
+ payload = payload
+ )
+ )
+ adapter3.changeItems(positionStart = 7, itemCount = 2, payload = payload)
+ observer.assertEventsAndClear(
+ "change in adapter 3 should be offset by adapter 1 & 2",
+ Changed(
+ positionStart = adapter1.itemCount + adapter2.itemCount + 7,
+ itemCount = 2,
+ payload = payload
+ )
+ )
+ assertThat(merge).hasItemCount(45)
+ }
+
+ @Test
+ fun notifyDataSetChanged() {
+ // we could add some logic to make data set changes add/remove/itemChange events yet
+ // it is very hard to get right and might cause very undesired animations. Not doing it
+ // for V1.
+ val adapter1 = NestedTestAdapter(10)
+ val adapter2 = NestedTestAdapter(15)
+ val adapter3 = NestedTestAdapter(20)
+ val merge = MergeAdapter(adapter1, adapter2, adapter3)
+ val observer = LoggingAdapterObserver(merge)
+
+ adapter1.changeDataSet(3)
+ observer.assertEventsAndClear(
+ "data set change should come as is",
+ DataSetChanged
+ )
+ assertThat(merge).hasItemCount(38)
+ adapter2.changeDataSet(20)
+ observer.assertEventsAndClear(
+ "data set change in adapter 2 should become full data set change",
+ DataSetChanged
+ )
+ assertThat(merge).hasItemCount(43)
+ adapter3.changeDataSet(newSize = 0)
+ observer.assertEventsAndClear(
+ """when an adapter changes size to 0, it should still come as 0 as we cannot
+ |rely on itemCount changing immediately. In theory we would but adapter might be
+ |faulty and not update its size immediately, which would work fine in RV because
+ |everything is delayed but not here if we immediately read the item count
+ """.trimMargin(),
+ DataSetChanged
+ )
+ assertThat(merge).hasItemCount(23)
+ }
+
+ @Test
+ fun viewTypeMapping_allViewsHaveDifferentTypes() {
+ val adapter1 = NestedTestAdapter(10) { _, position ->
+ position
+ }
+ val merge = MergeAdapter(adapter1)
+ val adapter1ViewTypes = (0 until 10).map {
+ merge.getItemViewType(it)
+ }.toSet()
+
+ assertWithMessage("all items have unique types")
+ .that(adapter1ViewTypes).hasSize(10)
+ repeat(adapter1.itemCount) {
+ assertThat(merge).bindView(recyclerView, it).verifyBoundTo(
+ adapter = adapter1,
+ localPosition = it
+ )
+ }
+ val adapter2 = NestedTestAdapter(5) { _, position ->
+ position
+ }
+ merge.addAdapter(adapter2)
+ repeat(adapter2.itemCount) {
+ assertThat(merge).bindView(recyclerView, adapter1.itemCount + it).verifyBoundTo(
+ adapter = adapter2,
+ localPosition = it
+ )
+ }
+
+ merge.removeAdapter(adapter1)
+ repeat(adapter2.itemCount) {
+ assertThat(merge).bindView(recyclerView, it).verifyBoundTo(
+ adapter = adapter2,
+ localPosition = it
+ )
+ }
+ }
+
+ @Test
+ fun viewTypeMapping_shareTypesWithinAdapter() {
+ val adapter1 = NestedTestAdapter(10) { item, _ ->
+ item.id % 3
+ }
+ val adapter2 = NestedTestAdapter(20) { item, _ ->
+ item.id % 4
+ }
+ val merge = MergeAdapter(adapter1, adapter2)
+ val adapter1Types = (0 until adapter1.itemCount).map {
+ merge.getItemViewType(it)
+ }.toSet()
+ assertThat(adapter1Types).hasSize(3)
+ val adapter2Types = (adapter1.itemCount until adapter2.itemCount).map {
+ merge.getItemViewType(it)
+ }.toSet()
+ assertThat(adapter2Types).hasSize(4)
+ adapter2Types.forEach {
+ assertThat(adapter1Types).doesNotContain(it)
+ }
+ (0 until adapter1.itemCount).forEach {
+ assertThat(merge).bindView(recyclerView, it)
+ .verifyBoundTo(
+ adapter = adapter1,
+ localPosition = it
+ )
+ }
+
+ (0 until adapter2.itemCount).forEach {
+ assertThat(merge).bindView(recyclerView, adapter1.itemCount + it)
+ .verifyBoundTo(
+ adapter = adapter2,
+ localPosition = it
+ )
+ }
+
+ merge.removeAdapter(adapter1)
+ repeat(adapter2.itemCount) {
+ assertThat(merge).bindView(recyclerView, it).verifyBoundTo(
+ adapter = adapter2,
+ localPosition = it
+ )
+ }
+ }
+
+ @Test(
+ expected = IllegalArgumentException::class
+ )
+ fun stableIdTest() {
+ val merge = MergeAdapter()
+ merge.setHasStableIds(true)
+ }
+
+ @Test
+ fun stateRestrorationTest_callingPublicMerthodIsIgnored() {
+ val adapter = NestedTestAdapter(3).also {
+ it.stateRestorationStrategy = PREVENT
+ }
+ val merge = MergeAdapter(adapter)
+ assertThat(merge).hasStateRestorationStrategy(PREVENT)
+ merge.stateRestorationStrategy = ALLOW
+ assertThat(merge).hasStateRestorationStrategy(PREVENT)
+ merge.stateRestorationStrategy = PREVENT_WHEN_EMPTY
+ assertThat(merge).hasStateRestorationStrategy(PREVENT)
+ adapter.stateRestorationStrategy = ALLOW
+ assertThat(merge).hasStateRestorationStrategy(ALLOW)
+ }
+
+ @Test
+ fun stateRestoration_subAdapterAllowsNonEmpty() {
+ val adapter1 = NestedTestAdapter(1).also {
+ it.stateRestorationStrategy = ALLOW
+ }
+ val adapter2 = NestedTestAdapter(0).also {
+ it.stateRestorationStrategy = PREVENT_WHEN_EMPTY
+ }
+ val merge = MergeAdapter(adapter1, adapter2)
+ assertThat(merge).cannotRestoreState()
+ adapter2.addItems(0, 1)
+ assertThat(merge).canRestoreState()
+ adapter2.removeItems(0, 1)
+ assertThat(merge).cannotRestoreState()
+ }
+
+ @Test
+ fun stateRestoration_subAdapterAllowsNonEmpty_viaNotifyChange() {
+ val adapter1 = NestedTestAdapter(1).also {
+ it.stateRestorationStrategy = ALLOW
+ }
+ val adapter2 = NestedTestAdapter(0).also {
+ it.stateRestorationStrategy = PREVENT_WHEN_EMPTY
+ }
+ val merge = MergeAdapter(adapter1, adapter2)
+ assertThat(merge).cannotRestoreState()
+ adapter2.changeDataSet(1)
+ assertThat(merge).canRestoreState()
+ adapter2.changeDataSet(0)
+ assertThat(merge).cannotRestoreState()
+ }
+
+ @Test
+ fun stateRestoration() {
+ val adapter1 = NestedTestAdapter(10)
+ val adapter2 = NestedTestAdapter(5)
+ val adapter3 = NestedTestAdapter(20)
+ val merge = MergeAdapter(adapter1, adapter2, adapter3)
+ assertThat(merge).hasStateRestorationStrategy(ALLOW)
+ adapter2.stateRestorationStrategy = PREVENT
+ assertThat(merge).hasStateRestorationStrategy(PREVENT)
+
+ adapter3.stateRestorationStrategy = PREVENT_WHEN_EMPTY
+ assertThat(merge).hasStateRestorationStrategy(PREVENT)
+
+ adapter2.stateRestorationStrategy = ALLOW
+ assertThat(merge).hasStateRestorationStrategy(ALLOW)
+
+ merge.removeAdapter(adapter3)
+ assertThat(merge).hasStateRestorationStrategy(ALLOW)
+
+ val adapter4 = NestedTestAdapter(3).also {
+ it.stateRestorationStrategy = PREVENT
+ merge.addAdapter(it)
+ }
+ assertThat(merge).hasStateRestorationStrategy(PREVENT)
+ adapter4.stateRestorationStrategy = PREVENT_WHEN_EMPTY
+ assertThat(merge).hasStateRestorationStrategy(ALLOW)
+ merge.removeAdapter(adapter1)
+ assertThat(merge).hasStateRestorationStrategy(ALLOW)
+ adapter4.stateRestorationStrategy = ALLOW
+ assertThat(merge).hasStateRestorationStrategy(ALLOW)
+ }
+
+ @Test
+ fun disposal() {
+ val adapter1 = NestedTestAdapter(10)
+ val adapter2 = NestedTestAdapter(5)
+ val merge = MergeAdapter(adapter1, adapter2)
+ assertThat(adapter1.observerCount()).isEqualTo(1)
+ assertThat(adapter2.observerCount()).isEqualTo(1)
+ merge.removeAdapter(adapter1)
+ assertThat(adapter1.observerCount()).isEqualTo(0)
+ assertThat(adapter2.observerCount()).isEqualTo(1)
+
+ val adapter3 = NestedTestAdapter(2)
+ merge.addAdapter(adapter3)
+ assertThat(adapter3.observerCount()).isEqualTo(1)
+ merge.copyOfAdapters.forEach {
+ merge.removeAdapter(it)
+ }
+ listOf(adapter1, adapter2, adapter3).forEachIndexed { index, adapter ->
+ assertWithMessage("adapter ${index + 1}").apply {
+ that(adapter.observerCount()).isEqualTo(0)
+ that(adapter.attachedRecyclerViews()).isEmpty()
+ }
+ }
+ }
+
+ /**
+ * Running only on 26 due to the getParameters method call and this is not API version
+ * dependent test so it is fine to only run it on new devices.
+ */
+ @SdkSuppress(minSdkVersion = 26)
+ @Test
+ fun overrideTest() {
+ // custom method instead of using toGenericString to avoid having class name
+ fun Method.describe() = """
+ $name(${parameters.map {
+ it.type.canonicalName
+ }}) : ${returnType.canonicalName}
+ """.trimIndent()
+
+ val excludedMethods = setOf(
+ "getItemId([int]) : long",
+ "registerAdapterDataObserver(" +
+ "[androidx.recyclerview.widget.RecyclerView.AdapterDataObserver]) : void",
+ "unregisterAdapterDataObserver(" +
+ "[androidx.recyclerview.widget.RecyclerView.AdapterDataObserver]) : void",
+ "canRestoreState([]) : boolean",
+ "onBindViewHolder([androidx.recyclerview.widget.RecyclerView.ViewHolder, int, " +
+ "java.util.List]) : void"
+ )
+ val adapterMethods = RecyclerView.Adapter::class.java.declaredMethods.filterNot {
+ Modifier.isPrivate(it.modifiers) || Modifier.isFinal(it.modifiers)
+ }.map {
+ it.describe()
+ }.filterNot {
+ excludedMethods.contains(it)
+ }
+ val mergeAdapterMethods = MergeAdapter::class.java.declaredMethods.map {
+ it.describe()
+ }
+ assertWithMessage(
+ """
+ MergeAdapter should override all methods in RecyclerView.Adapter for future
+ compatibility. If you want to exclude a method, update the test.
+ """.trimIndent()
+ ).that(mergeAdapterMethods).containsAtLeastElementsIn(adapterMethods)
+ }
+
+ @Test
+ fun getAdapters() {
+ val adapter1 = NestedTestAdapter(1)
+ val adapter2 = NestedTestAdapter(2)
+ val merge = MergeAdapter(adapter1, adapter2)
+ assertThat(merge.copyOfAdapters).isEqualTo(listOf(adapter1, adapter2))
+ merge.removeAdapter(adapter1)
+ assertThat(merge.copyOfAdapters).isEqualTo(listOf(adapter2))
+ }
+
+ @Test
+ fun sharedTypes() {
+ val adapter1 = NestedTestAdapter(3) { _, pos ->
+ pos % 2
+ }
+ val adapter2 = NestedTestAdapter(3) { _, pos ->
+ pos % 3
+ }
+ val merge = MergeAdapter(
+ MergeAdapter.Config.Builder()
+ .setIsolateViewTypes(false)
+ .build(), adapter1, adapter2
+ )
+ assertThat(merge).bindView(recyclerView, 2)
+ .verifyBoundTo(adapter1, 2)
+ assertThat(merge).bindView(recyclerView, 3)
+ .verifyBoundTo(adapter2, 0)
+ assertThat(merge.getItemViewType(0)).isEqualTo(0)
+ assertThat(merge.getItemViewType(1)).isEqualTo(1)
+ assertThat(merge.getItemViewType(2)).isEqualTo(0)
+ // notice that it resets to 0 because type is based on position
+ assertThat(merge.getItemViewType(3)).isEqualTo(0)
+ assertThat(merge.getItemViewType(4)).isEqualTo(1)
+ assertThat(merge.getItemViewType(5)).isEqualTo(2)
+ // ensure we bind via the correct adapter when a type is limited to a specific adapter
+ assertThat(merge).bindView(recyclerView, 5)
+ .verifyBoundTo(adapter2, 2)
+ }
+
+ @Test
+ fun sharedTypes_allUnique() {
+ val adapter1 = NestedTestAdapter(3) { item, _ ->
+ item.id
+ }
+ val adapter2 = NestedTestAdapter(3) { item, _ ->
+ item.id
+ }
+ val merge = MergeAdapter(
+ MergeAdapter.Config.Builder()
+ .setIsolateViewTypes(false)
+ .build(), adapter1, adapter2
+ )
+ assertThat(merge).bindView(recyclerView, 0)
+ .verifyBoundTo(adapter1, 0)
+ assertThat(merge).bindView(recyclerView, 1)
+ .verifyBoundTo(adapter1, 1)
+ assertThat(merge).bindView(recyclerView, 2)
+ .verifyBoundTo(adapter1, 2)
+ assertThat(merge).bindView(recyclerView, 3)
+ .verifyBoundTo(adapter2, 0)
+ assertThat(merge).bindView(recyclerView, 4)
+ .verifyBoundTo(adapter2, 1)
+ assertThat(merge).bindView(recyclerView, 5)
+ .verifyBoundTo(adapter2, 2)
+ }
+
+ private var itemCounter = 0
+ private fun produceItem(): TestItem = (itemCounter++).let {
+ TestItem(id = it, value = it)
+ }
+
+ internal inner class NestedTestAdapter(
+ count: Int = 0,
+ val getLayoutParams: ((MergeAdapterViewHolder) -> LayoutParams)? = null,
+ val itemTypeLookup: ((TestItem, position: Int) -> Int)? = null
+ ) : RecyclerView.Adapter<MergeAdapterViewHolder>() {
+ private val attachedViewHolders = mutableListOf<MergeAdapterViewHolder>()
+ private val recycledViewHolders = mutableListOf<MergeAdapterViewHolder>()
+ private val failedToRecycleViewHolders = mutableListOf<MergeAdapterViewHolder>()
+ private var attachedRecyclerViews = mutableListOf<RecyclerView>()
+ private var observers = mutableListOf<RecyclerView.AdapterDataObserver>()
+
+ private val items = mutableListOf<TestItem>().also { list ->
+ repeat(count) {
+ list.add(produceItem())
+ }
+ }
+
+ fun attachedViewHolders(): List<MergeAdapterViewHolder> = attachedViewHolders
+
+ override fun onViewAttachedToWindow(holder: MergeAdapterViewHolder) {
+ assertThat(attachedViewHolders).doesNotContain(holder)
+ attachedViewHolders.add(holder)
+ }
+
+ override fun onViewDetachedFromWindow(holder: MergeAdapterViewHolder) {
+ assertThat(attachedViewHolders).contains(holder)
+ attachedViewHolders.remove(holder)
+ }
+
+ override fun registerAdapterDataObserver(observer: RecyclerView.AdapterDataObserver) {
+ assertThat(observers).doesNotContain(observer)
+ observers.add(observer)
+ super.registerAdapterDataObserver(observer)
+ }
+
+ override fun unregisterAdapterDataObserver(observer: RecyclerView.AdapterDataObserver) {
+ assertThat(observers).contains(observer)
+ observers.remove(observer)
+ super.unregisterAdapterDataObserver(observer)
+ }
+
+ fun observerCount() = observers.size
+
+ override fun getItemViewType(position: Int): Int {
+ itemTypeLookup?.let {
+ return it(items[position], position)
+ }
+ return super.getItemViewType(position)
+ }
+
+ override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
+ assertThat(attachedRecyclerViews).doesNotContain(recyclerView)
+ attachedRecyclerViews.add(recyclerView)
+ }
+
+ override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
+ assertThat(attachedRecyclerViews).contains(recyclerView)
+ attachedRecyclerViews.remove(recyclerView)
+ }
+
+ fun attachedRecyclerViews(): List<RecyclerView> = attachedRecyclerViews
+
+ fun addItems(positionStart: Int, itemCount: Int = 1) {
+ require(itemCount > 0)
+ require(positionStart >= 0 && positionStart <= items.size)
+ val newItems = (0 until itemCount).map {
+ produceItem()
+ }
+ items.addAll(positionStart, newItems)
+ notifyItemRangeInserted(positionStart, itemCount)
+ }
+
+ fun removeItems(positionStart: Int, itemCount: Int = 1) {
+ require(positionStart >= 0)
+ require(positionStart + itemCount <= items.size)
+ require(itemCount > 0)
+ repeat(itemCount) {
+ items.removeAt(positionStart)
+ }
+ notifyItemRangeRemoved(positionStart, itemCount)
+ }
+
+ fun moveItem(fromPosition: Int, toPosition: Int) {
+ require(fromPosition >= 0 && fromPosition < items.size)
+ require(toPosition >= 0 && toPosition < items.size)
+ if (fromPosition == toPosition) return
+ items.add(toPosition, items.removeAt(fromPosition))
+ notifyItemMoved(fromPosition, toPosition)
+ }
+
+ fun changeDataSet(newSize: Int = items.size) {
+ require(newSize >= 0)
+ val newItems = (0 until newSize).map {
+ produceItem()
+ }
+ items.clear()
+ items.addAll(newItems)
+ notifyDataSetChanged()
+ }
+
+ fun changeItems(positionStart: Int, itemCount: Int, payload: Any? = null) {
+ require(positionStart >= 0 && positionStart < items.size)
+ require(positionStart + itemCount <= items.size)
+ (positionStart until positionStart + itemCount).forEach {
+ val prev = items[it]
+ items[it] = prev.copy(value = prev.value + 1)
+ }
+ if (payload == null) {
+ notifyItemRangeChanged(positionStart, itemCount)
+ } else {
+ notifyItemRangeChanged(positionStart, itemCount, payload)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MergeAdapterViewHolder {
+ return MergeAdapterViewHolder(parent.context, viewType).also { holder ->
+ getLayoutParams?.invoke(holder)?.let {
+ holder.itemView.layoutParams = it
+ }
+ }
+ }
+
+ override fun onBindViewHolder(holder: MergeAdapterViewHolder, position: Int) {
+ assertThat(getItemViewType(position)).isEqualTo(holder.localViewType)
+ holder.bindTo(this, items[position], position)
+ }
+
+ override fun onViewRecycled(holder: MergeAdapterViewHolder) {
+ recycledViewHolders.add(holder)
+ holder.onRecycled()
+ }
+
+ override fun getItemCount() = items.size
+
+ override fun onFailedToRecycleView(holder: MergeAdapterViewHolder): Boolean {
+ failedToRecycleViewHolders.add(holder)
+ return super.onFailedToRecycleView(holder)
+ }
+
+ fun getItemAt(localPosition: Int) = items[localPosition]
+ fun recycledViewHolders(): List<MergeAdapterViewHolder> = recycledViewHolders
+ fun failedToRecycleViewHolders(): List<MergeAdapterViewHolder> = failedToRecycleViewHolders
+ }
+
+ class MergeAdapterViewHolder(
+ context: Context,
+ val localViewType: Int
+ ) : RecyclerView.ViewHolder(View(context)) {
+ private var boundItem: Any? = null
+ private var boundAdapter: RecyclerView.Adapter<*>? = null
+ private var boundPosition: Int? = null
+ fun bindTo(adapter: RecyclerView.Adapter<*>, item: Any, position: Int) {
+ boundAdapter = adapter
+ boundPosition = position
+ boundItem = item
+ }
+
+ fun boundItem() = boundItem
+ fun boundLocalPosition() = boundPosition
+ fun boundAdapter() = boundAdapter
+ fun onRecycled() {
+ boundItem = null
+ boundPosition = -1
+ boundAdapter = null
+ }
+ }
+
+ class LoggingAdapterObserver(
+ private val src: RecyclerView.Adapter<*>
+ ) : RecyclerView.AdapterDataObserver() {
+ init {
+ src.registerAdapterDataObserver(this)
+ }
+
+ private val events = mutableListOf<Event>()
+
+ fun assertEventsAndClear(
+ message: String,
+ vararg expected: Event
+ ) {
+ assertWithMessage(message).that(events).isEqualTo(expected.toList())
+ events.clear()
+ }
+
+ override fun onChanged() {
+ events.add(DataSetChanged)
+ }
+
+ override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
+ events.add(
+ Changed(
+ positionStart = positionStart,
+ itemCount = itemCount
+ )
+ )
+ }
+
+ override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
+ events.add(
+ Changed(
+ positionStart = positionStart,
+ itemCount = itemCount,
+ payload = payload
+ )
+ )
+ }
+
+ override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
+ events.add(
+ Inserted(
+ positionStart = positionStart,
+ itemCount = itemCount
+ )
+ )
+ }
+
+ override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
+ events.add(
+ Removed(
+ positionStart = positionStart,
+ itemCount = itemCount
+ )
+ )
+ }
+
+ override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
+ require(itemCount == 1) {
+ "RV does not support moving more than 1 item at a time"
+ }
+ events.add(
+ Moved(
+ fromPosition = fromPosition,
+ toPosition = toPosition
+ )
+ )
+ }
+
+ override fun onStateRestorationStrategyChanged() {
+ events.add(
+ StateRestorationStrategy(
+ newValue = src.stateRestorationStrategy
+ )
+ )
+ }
+
+ sealed class Event {
+ object DataSetChanged : Event()
+ data class Changed(
+ val positionStart: Int,
+ val itemCount: Int,
+ val payload: Any? = null
+ ) : Event()
+
+ data class Inserted(
+ val positionStart: Int,
+ val itemCount: Int
+ ) : Event()
+
+ data class Removed(
+ val positionStart: Int,
+ val itemCount: Int
+ ) : Event()
+
+ data class Moved(
+ val fromPosition: Int,
+ val toPosition: Int
+ ) : Event()
+
+ data class StateRestorationStrategy(
+ val newValue: RecyclerView.Adapter.StateRestorationStrategy
+ ) : Event()
+ }
+ }
+
+ internal data class TestItem(
+ val id: Int,
+ val value: Int,
+ val viewType: Int = 0
+ )
+} \ No newline at end of file
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewAnimationsTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewAnimationsTest.java
index 77246c58f43..7843a3cb862 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewAnimationsTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewAnimationsTest.java
@@ -243,7 +243,7 @@ public class RecyclerViewAnimationsTest extends BaseRecyclerViewAnimationsTest {
RecyclerView.State state) {
mLayoutItemCount = 7;
View targetView = recycler
- .getViewForPosition(target.getAdapterPosition());
+ .getViewForPosition(target.getAbsoluteAdapterPosition());
assertSame(targetView, target.itemView);
super.beforePostLayout(recycler, layoutManager, state);
}
@@ -479,7 +479,7 @@ public class RecyclerViewAnimationsTest extends BaseRecyclerViewAnimationsTest {
mLayoutManager.expectLayouts(1);
target.mBoundItem.mType += 2;
mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 9;
- mTestAdapter.changeAndNotify(target.getAdapterPosition(), 1);
+ mTestAdapter.changeAndNotify(target.getAbsoluteAdapterPosition(), 1);
requestLayoutOnUIThread(mRecyclerView);
mLayoutManager.waitForLayout(2);
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewCacheTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewCacheTest.java
index bec138e4621..330f659e700 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewCacheTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewCacheTest.java
@@ -494,7 +494,7 @@ public class RecyclerViewCacheTest {
@Override
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
// verify unbound view doesn't get
- assertNotEquals(RecyclerView.NO_POSITION, holder.getAdapterPosition());
+ assertNotEquals(RecyclerView.NO_POSITION, holder.getAbsoluteAdapterPosition());
}
};
mRecyclerView.setAdapter(adapter);
@@ -519,7 +519,7 @@ public class RecyclerViewCacheTest {
assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1);
RecyclerView.ViewHolder pooledHolder = mRecyclerView.getRecycledViewPool()
.mScrap.get(0).mScrapHeap.get(0);
- assertEquals(RecyclerView.NO_POSITION, pooledHolder.getAdapterPosition());
+ assertEquals(RecyclerView.NO_POSITION, pooledHolder.getAbsoluteAdapterPosition());
}
@Test
@@ -853,7 +853,7 @@ public class RecyclerViewCacheTest {
@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
- mSavedStates.set(holder.getAdapterPosition(),
+ mSavedStates.set(holder.getAbsoluteAdapterPosition(),
holder.mRecyclerView.getLayoutManager().onSaveInstanceState());
}
@@ -1194,8 +1194,8 @@ public class RecyclerViewCacheTest {
@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
- if (holder.getAdapterPosition() >= 0) {
- mSavedStates.set(holder.getAdapterPosition(),
+ if (holder.getAbsoluteAdapterPosition() >= 0) {
+ mSavedStates.set(holder.getAbsoluteAdapterPosition(),
holder.mRecyclerView.getLayoutManager().onSaveInstanceState());
}
}
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
index 94ead611b57..9a86462b193 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
@@ -513,7 +513,7 @@ public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest
if (mRecyclerView.mRecycler.mCachedViews.contains(holder)) {
assertThat("ViewHolder's getAdapterPosition should be "
+ "RecyclerView.NO_POSITION",
- holder.getAdapterPosition(),
+ holder.getAbsoluteAdapterPosition(),
is(RecyclerView.NO_POSITION));
cachedRecycleCount.incrementAndGet();
}
@@ -2388,6 +2388,7 @@ public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest
assertThat(recycledViewCount.get(), is(9));
assertTrue(failedToRecycle.get());
assertNull(vh.mOwnerRecyclerView);
+ assertNull(vh.getBindingAdapter());
checkForMainThreadException();
}
@@ -2422,6 +2423,7 @@ public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest
});
assertTrue(animationsLatch.await(2, TimeUnit.SECONDS));
assertNull(vh.mOwnerRecyclerView);
+ assertNull(vh.getBindingAdapter());
checkForMainThreadException();
}
@@ -2476,7 +2478,7 @@ public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest
for (int i = 0; i < recyclerView.getChildCount(); i ++) {
View view = recyclerView.getChildAt(i);
RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view);
- if (vh.getAdapterPosition() == 2) {
+ if (vh.getAbsoluteAdapterPosition() == 2) {
if (mRecyclerView.mChildHelper.isHidden(view)) {
assertThat(hidden, CoreMatchers.nullValue());
hidden = vh;
@@ -2600,7 +2602,7 @@ public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest
assertEquals("should be able to find VH with adapter position " + index, vh,
recyclerView.findViewHolderForAdapterPosition(index));
assertEquals("get adapter position should return correct index", index,
- vh.getAdapterPosition());
+ vh.getAbsoluteAdapterPosition());
layoutPositions.put(view, vh.mPosition);
}
if (adapterChanges != null) {
@@ -2616,7 +2618,7 @@ public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest
recyclerView.findViewHolderForAdapterPosition(index));
}
assertSame("get adapter position should return correct index", index,
- vh.getAdapterPosition());
+ vh.getAbsoluteAdapterPosition());
assertSame("should be able to find view with layout position",
vh, mRecyclerView.findViewHolderForLayoutPosition(
layoutPositions.get(view)));
@@ -3891,7 +3893,7 @@ public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest
try {
TestViewHolder tvh = (TestViewHolder) parent.getChildViewHolder(view);
Object data = tvh.getData();
- int adapterPos = tvh.getAdapterPosition();
+ int adapterPos = tvh.getAbsoluteAdapterPosition();
assertThat(adapterPos, is(not(NO_POSITION)));
if (state.isPreLayout()) {
preLayoutData.put(adapterPos, data);
@@ -5458,7 +5460,7 @@ public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest
if (pendingScrollPosition != NO_POSITION) {
assertEquals(pendingScrollPosition,
getChildViewHolderInt(getChildAt(0))
- .getAdapterPosition());
+ .getAbsoluteAdapterPosition());
}
action.jumpTo(pendingScrollPosition + 2);
}
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewPrefetchTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewPrefetchTest.java
index 38a0f4e36fa..6ad0fc89095 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewPrefetchTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewPrefetchTest.java
@@ -107,6 +107,6 @@ public class RecyclerViewPrefetchTest extends BaseRecyclerViewInstrumentationTes
layout.waitForPrefetch(10);
assertThat(cachedViews().size(), is(1));
- assertThat(cachedViews().get(0).getAdapterPosition(), is(6));
+ assertThat(cachedViews().get(0).getAbsoluteAdapterPosition(), is(6));
}
} \ No newline at end of file
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
index 800248bd210..47aecfb1893 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
@@ -774,7 +774,7 @@ public class StaggeredGridLayoutManagerBaseConfigSetTest
scrollBy(size * 2);
assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView))));
assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView));
- assertThat(vh.getAdapterPosition(), is(pos));
+ assertThat(vh.getAbsoluteAdapterPosition(), is(pos));
scrollBy(size * 2);
assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView)));
}
@@ -811,7 +811,7 @@ public class StaggeredGridLayoutManagerBaseConfigSetTest
scrollBy(-size * 2);
assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView))));
assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView));
- assertThat(vh.getAdapterPosition(), is(pos));
+ assertThat(vh.getAbsoluteAdapterPosition(), is(pos));
scrollBy(-size * 2);
assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView)));
}
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerCacheTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerCacheTest.java
index 1545d5880f8..dd7781b4449 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerCacheTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerCacheTest.java
@@ -71,7 +71,7 @@ public class StaggeredGridLayoutManagerCacheTest extends BaseStaggeredGridLayout
private boolean cachedViewsContains(int position) {
// Note: can't make assumptions about order here, so just check all cached views
for (int i = 0; i < cachedViews().size(); i++) {
- if (cachedViews().get(i).getAdapterPosition() == position) return true;
+ if (cachedViews().get(i).getAbsoluteAdapterPosition() == position) return true;
}
return false;
}
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerTest.java
index 888d2e54d4c..d298a916091 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerTest.java
@@ -17,8 +17,7 @@
package androidx.recyclerview.widget;
import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
-import static androidx.recyclerview.widget.StaggeredGridLayoutManager
- .GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
+import static androidx.recyclerview.widget.StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
import static androidx.recyclerview.widget.StaggeredGridLayoutManager.GAP_HANDLING_NONE;
import static androidx.recyclerview.widget.StaggeredGridLayoutManager.HORIZONTAL;
import static androidx.recyclerview.widget.StaggeredGridLayoutManager.LayoutParams;
@@ -353,6 +352,7 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
}
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public void onBindViewHolder(@NonNull TestViewHolder holder, int position) {
Item item = mItems.get(position);
holder.mBoundItem = item;
@@ -363,7 +363,6 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation using this for kitkat tests
holder.itemView.setBackgroundDrawable(stl);
if (mOnBindCallback != null) {
mOnBindCallback.onBoundItem(holder, position);
@@ -392,9 +391,10 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
RecyclerView.ViewHolder containingViewHolder = mRecyclerView.findContainingViewHolder(
focusedChild);
assertTrue("new focused view should have a larger position "
- + lastViewHolder.getAdapterPosition() + " vs "
- + containingViewHolder.getAdapterPosition(),
- lastViewHolder.getAdapterPosition() < containingViewHolder.getAdapterPosition());
+ + lastViewHolder.getAbsoluteAdapterPosition() + " vs "
+ + containingViewHolder.getAbsoluteAdapterPosition(),
+ lastViewHolder.getAbsoluteAdapterPosition()
+ < containingViewHolder.getAbsoluteAdapterPosition());
}
public void focusSearchFailure(boolean scrollDown) throws Throwable {
@@ -404,6 +404,7 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
RecyclerView mAttachedRv;
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
@@ -414,7 +415,6 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation used to support kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -460,23 +460,27 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
focusSearchAndWaitForScroll(focusedView, focusDir);
focusedView = mRecyclerView.getFocusedChild();
assertEquals(pos + 3,
- mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition());
+ mRecyclerView.getChildViewHolder(
+ focusedView).getAbsoluteAdapterPosition());
pos += 3;
}
for (int i : new int[]{18, 19, 20, 21, 23, 24}) {
focusSearchAndWaitForScroll(focusedView, focusDir);
focusedView = mRecyclerView.getFocusedChild();
- assertEquals(i, mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition());
+ assertEquals(i, mRecyclerView.getChildViewHolder(
+ focusedView).getAbsoluteAdapterPosition());
}
// now move right
focusSearch(focusedView, View.FOCUS_RIGHT);
waitForIdleScroll(mRecyclerView);
focusedView = mRecyclerView.getFocusedChild();
- assertEquals(25, mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition());
+ assertEquals(25,
+ mRecyclerView.getChildViewHolder(focusedView).getAbsoluteAdapterPosition());
for (int i : new int[]{28, 30}) {
focusSearchAndWaitForScroll(focusedView, focusDir);
focusedView = mRecyclerView.getFocusedChild();
- assertEquals(i, mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition());
+ assertEquals(i, mRecyclerView.getChildViewHolder(
+ focusedView).getAbsoluteAdapterPosition());
}
}
@@ -497,6 +501,7 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
RecyclerView mAttachedRv;
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
@@ -507,7 +512,6 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation used to support kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -576,7 +580,8 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
for (int i : new int[]{4, 6}) {
focusSearchAndWaitForScroll(focusedView, View.FOCUS_UP);
focusedView = mRecyclerView.getFocusedChild();
- actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
+ actualFocusIndex = mRecyclerView.getChildViewHolder(
+ focusedView).getAbsoluteAdapterPosition();
assertEquals("Focused view should be at adapter position " + i + " whereas it's at "
+ actualFocusIndex, i, actualFocusIndex);
}
@@ -587,7 +592,9 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
for (int i : new int[]{9, 11, 11, 11}) {
focusSearchAndWaitForScroll(focusedView, View.FOCUS_UP);
focusedView = mRecyclerView.getFocusedChild();
- actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
+ actualFocusIndex =
+ mRecyclerView.getChildViewHolder(
+ focusedView).getAbsoluteAdapterPosition();
toVisible = mRecyclerView.findViewHolderForAdapterPosition(i);
assertEquals("Focused view should not be changed, whereas it's now at "
@@ -611,6 +618,7 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
RecyclerView mAttachedRv;
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
@@ -621,7 +629,6 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation used to support kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -689,7 +696,8 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
for (int i : new int[]{4, 6}) {
focusSearchAndWaitForScroll(focusedView, View.FOCUS_DOWN);
focusedView = mRecyclerView.getFocusedChild();
- actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
+ actualFocusIndex = mRecyclerView.getChildViewHolder(
+ focusedView).getAbsoluteAdapterPosition();
assertEquals("Focused view should be at adapter position " + i + " whereas it's at "
+ actualFocusIndex, i, actualFocusIndex);
}
@@ -700,7 +708,8 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
for (int i : new int[]{9, 11, 11, 11}) {
focusSearchAndWaitForScroll(focusedView, View.FOCUS_DOWN);
focusedView = mRecyclerView.getFocusedChild();
- actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
+ actualFocusIndex = mRecyclerView.getChildViewHolder(
+ focusedView).getAbsoluteAdapterPosition();
toVisible = mRecyclerView.findViewHolderForAdapterPosition(i);
assertEquals("Focused view should not be changed, whereas it's now at "
@@ -729,6 +738,7 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
new GridTestAdapter(18, 1) {
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
@@ -739,7 +749,6 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation used to support kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -806,7 +815,8 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
for (int i : new int[]{4, 6}) {
focusSearchAndWaitForScroll(focusedView, View.FOCUS_LEFT);
focusedView = mRecyclerView.getFocusedChild();
- actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
+ actualFocusIndex = mRecyclerView.getChildViewHolder(
+ focusedView).getAbsoluteAdapterPosition();
assertEquals("Focused view should be at adapter position " + i + " whereas it's at "
+ actualFocusIndex, i, actualFocusIndex);
}
@@ -817,7 +827,8 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
for (int i : new int[]{9, 11, 11, 11}) {
focusSearchAndWaitForScroll(focusedView, View.FOCUS_LEFT);
focusedView = mRecyclerView.getFocusedChild();
- actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
+ actualFocusIndex = mRecyclerView.getChildViewHolder(
+ focusedView).getAbsoluteAdapterPosition();
toVisible = mRecyclerView.findViewHolderForAdapterPosition(i);
assertEquals("Focused view should not be changed, whereas it's now at "
@@ -845,6 +856,7 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
new GridTestAdapter(18, 1) {
@Override
+ @SuppressWarnings("deprecated") // using this for kitkat tests
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
int viewType) {
TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
@@ -855,7 +867,6 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
stl.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.RED));
stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation used to support kitkat tests
testViewHolder.itemView.setBackgroundDrawable(stl);
return testViewHolder;
}
@@ -923,7 +934,8 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
for (int i : new int[]{4, 6}) {
focusSearchAndWaitForScroll(focusedView, View.FOCUS_RIGHT);
focusedView = mRecyclerView.getFocusedChild();
- actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
+ actualFocusIndex = mRecyclerView.getChildViewHolder(
+ focusedView).getAbsoluteAdapterPosition();
assertEquals("Focused view should be at adapter position " + i + " whereas it's at "
+ actualFocusIndex, i, actualFocusIndex);
}
@@ -934,7 +946,8 @@ public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManag
for (int i : new int[]{9, 11, 11, 11}) {
focusSearchAndWaitForScroll(focusedView, View.FOCUS_RIGHT);
focusedView = mRecyclerView.getFocusedChild();
- actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
+ actualFocusIndex = mRecyclerView.getChildViewHolder(
+ focusedView).getAbsoluteAdapterPosition();
toVisible = mRecyclerView.findViewHolderForAdapterPosition(i);
assertEquals("Focused view should not be changed, whereas it's now at "
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java
index eacf384ad86..e08fdd158f8 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java
@@ -705,7 +705,8 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
public void run() {
if (mRecyclerView != null && mRecyclerView.isAttachedToWindow()
&& !anim.mOverridden
- && anim.mViewHolder.getAdapterPosition() != RecyclerView.NO_POSITION) {
+ && anim.mViewHolder.getAbsoluteAdapterPosition()
+ != RecyclerView.NO_POSITION) {
final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator();
// if animator is running or we have other active recover animations, we try
// not to call onSwiped because DefaultItemAnimator is not good at merging
@@ -879,8 +880,8 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
mDistances.clear();
return;
}
- final int toPosition = target.getAdapterPosition();
- final int fromPosition = viewHolder.getAdapterPosition();
+ final int toPosition = target.getAbsoluteAdapterPosition();
+ final int fromPosition = viewHolder.getAbsoluteAdapterPosition();
if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
// keep target visible
mCallback.onMoved(mRecyclerView, viewHolder, fromPosition,
@@ -1635,8 +1636,8 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
* <p>
* If this method returns true, ItemTouchHelper assumes {@code viewHolder} has been moved
* to the adapter position of {@code target} ViewHolder
- * ({@link ViewHolder#getAdapterPosition()
- * ViewHolder#getAdapterPosition()}).
+ * ({@link ViewHolder#getAbsoluteAdapterPosition()
+ * ViewHolder#getAdapterPositionInRecyclerView()}).
* <p>
* If you don't support drag & drop, this method will never be called.
*
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/MergeAdapter.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/MergeAdapter.java
new file mode 100644
index 00000000000..2362a449cb2
--- /dev/null
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/MergeAdapter.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2020 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 androidx.recyclerview.widget;
+
+import android.util.Log;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Preconditions;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * An {@link Adapter} implementation that presents the contents of multiple adapters in sequence.
+ *
+ * <pre>
+ * MyAdapter adapter1 = ...;
+ * AnotherAdapter adapter2 = ...;
+ * MergeAdapter merged = new MergeAdapter(adapter1, adapter2);
+ * recyclerView.setAdapter(mergedAdapter);
+ * </pre>
+ * <p>
+ * By default, {@link MergeAdapter} isolates view types of nested adapters from each other such that
+ * it will change the view type before reporting it back to the {@link RecyclerView} to avoid any
+ * conflicts between the view types of added adapters. This also means each added adapter will have
+ * its own isolated pool of {@link ViewHolder}s, with no re-use in between added adapters.
+ * <p>
+ * If your {@link Adapter}s share the same view types, and can support sharing {@link ViewHolder}
+ * s between added adapters, provide an instance of {@link Config} where you set
+ * {@link Config#isolateViewTypes} to {@code false}. A common usage pattern for this is to return
+ * the {@code R.layout.<layout_name>} from the {@link Adapter#getItemViewType(int)} method.
+ * <p>
+ * When an added adapter calls one of the {@code notify} methods, {@link MergeAdapter} properly
+ * offsets values before reporting it back to the {@link RecyclerView}.
+ * If an adapter calls {@link Adapter#notifyDataSetChanged()}, {@link MergeAdapter} also calls
+ * {@link Adapter#notifyDataSetChanged()} as calling
+ * {@link Adapter#notifyItemRangeChanged(int, int)} will confuse the {@link RecyclerView}.
+ * You are highly encouraged to to use {@link SortedList} or {@link ListAdapter} to avoid
+ * calling {@link Adapter#notifyDataSetChanged()}.
+ * <p>
+ * {@link MergeAdapter} does not yet support stable ids. Even if one of the added adapters have
+ * stable ids, {@link MergeAdapter} will not use it.
+ * Calling {@link Adapter#setHasStableIds(boolean)} with {@code true} on a {@link MergeAdapter}
+ * will result in an {@link IllegalArgumentException}.
+ * Similar to the case above, you are highly encouraged to use {@link ListAdapter}, which will
+ * automatically calculate the changes in the data set for you.
+ * <p>
+ * It is common to find the adapter position of a {@link ViewHolder} to handle user action on the
+ * {@link ViewHolder}. For those cases, instead of calling {@link ViewHolder#getAdapterPosition()},
+ * use {@link ViewHolder#getBindingAdapterPosition()}. If your adapters share {@link ViewHolder}s,
+ * you can use the {@link ViewHolder#getBindingAdapter()} method to find the adapter which last
+ * bound that {@link ViewHolder}.
+ */
+@SuppressWarnings("unchecked")
+public final class MergeAdapter extends Adapter<ViewHolder> {
+ private static final String TAG = "MergeAdapter";
+ /**
+ * Bulk of the logic is in the controller to keep this class isolated to the public API.
+ */
+ private final MergeAdapterController mController;
+
+ /**
+ * Creates a MergeAdapter with {@link Config#DEFAULT} and the given adapters in the given order.
+ *
+ * @param adapters The list of adapters to add
+ */
+ @SafeVarargs
+ public MergeAdapter(@NonNull Adapter<? extends ViewHolder>... adapters) {
+ this(Config.DEFAULT, adapters);
+ }
+
+ /**
+ * Creates a MergeAdapter with the given config and the given adapters in the given order.
+ *
+ * @param config The configuration for this MergeAdapter
+ * @param adapters The list of adapters to add
+ * @see Config.Builder
+ */
+ @SafeVarargs
+ public MergeAdapter(
+ @NonNull Config config,
+ @NonNull Adapter<? extends ViewHolder>... adapters) {
+ this(config, Arrays.asList(adapters));
+ }
+
+ /**
+ * Creates a MergeAdapter with {@link Config#DEFAULT} and the given adapters in the given order.
+ *
+ * @param adapters The list of adapters to add
+ */
+ public MergeAdapter(@NonNull List<Adapter<? extends ViewHolder>> adapters) {
+ this(Config.DEFAULT, adapters);
+ }
+
+ /**
+ * Creates a MergeAdapter with the given config and the given adapters in the given order.
+ *
+ * @param config The configuration for this MergeAdapter
+ * @param adapters The list of adapters to add
+ * @see Config.Builder
+ */
+ public MergeAdapter(
+ @NonNull Config config,
+ @NonNull List<Adapter<? extends ViewHolder>> adapters) {
+ mController = new MergeAdapterController(this, config);
+ for (Adapter<? extends ViewHolder> adapter : adapters) {
+ addAdapter(adapter);
+ }
+ }
+
+ /**
+ * Appends the given adapter to the existing list of adapters and notifies the observers of
+ * this {@link MergeAdapter}.
+ *
+ * @param adapter The new adapter to add
+ * @return {@code true} if the adapter is successfully added because it did not already exist,
+ * {@code false} otherwise.
+ * @see #addAdapter(int, Adapter)
+ * @see #removeAdapter(Adapter)
+ */
+ public boolean addAdapter(@NonNull Adapter<? extends ViewHolder> adapter) {
+ return mController.addAdapter((Adapter<ViewHolder>) adapter);
+ }
+
+ /**
+ * Adds the given adapter to the given index among other adapters that are already added.
+ *
+ * @param index The index into which to insert the adapter. MergeAdapter will throw an
+ * {@link IndexOutOfBoundsException} if the index is not between 0 and current
+ * adapter count (inclusive).
+ * @param adapter The new adapter to add to the adapters list.
+ * @return {@code true} if the adapter is successfully added because it did not already exist,
+ * {@code false} otherwise.
+ * @see #addAdapter(Adapter)
+ * @see #removeAdapter(Adapter)
+ */
+ public boolean addAdapter(int index, @NonNull Adapter<? extends ViewHolder> adapter) {
+ return mController.addAdapter(index, (Adapter<ViewHolder>) adapter);
+ }
+
+ /**
+ * Removes the given adapter from the adapters list if it exists
+ *
+ * @param adapter The adapter to remove
+ * @return {@code true} if the adapter was previously added to this {@code MergeAdapter} and
+ * now removed or {@code false} if it couldn't be found.
+ */
+ public boolean removeAdapter(@NonNull Adapter<? extends ViewHolder> adapter) {
+ return mController.removeAdapter((Adapter<ViewHolder>) adapter);
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mController.getItemViewType(position);
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return mController.onCreateViewHolder(parent, viewType);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ mController.onBindViewHolder(holder, position);
+ }
+
+ /**
+ * MergeAdapter does not support stable ids yet.
+ * Calling this method with {@code true} will result in an {@link IllegalArgumentException}.
+ *
+ * @param hasStableIds Whether items in data set have unique identifiers or not.
+ */
+ @Override
+ public void setHasStableIds(boolean hasStableIds) {
+ Preconditions.checkArgument(!hasStableIds,
+ "Stable ids are not supported for MergeAdapter yet");
+ //noinspection ConstantConditions
+ super.setHasStableIds(hasStableIds);
+ }
+
+ /**
+ * Calling this method on the MergeAdapter has no impact as the MergeAdapter infers this
+ * value from added sub adapters.
+ *
+ * {@link MergeAdapter} picks the least allowing strategy from given adapters. So if a nested
+ * adapter sets this to
+ * {@link androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationStrategy#PREVENT},
+ * merge adapter will report that.
+ *
+ * @param strategy The saved state restoration strategy for this Adapter.
+ */
+ @Override
+ public void setStateRestorationStrategy(@NonNull StateRestorationStrategy strategy) {
+ // do nothing
+ Log.w(TAG, "Calling setStateRestorationStrategy has no impact on the MergeAdapter as"
+ + " it derives its state restoration strategy from nested adapters");
+ }
+
+ /**
+ * Internal method to be called from the wrappers.
+ */
+ void internalSetStateRestorationStrategy(@NonNull StateRestorationStrategy strategy) {
+ super.setStateRestorationStrategy(strategy);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mController.getTotalCount();
+ }
+
+ @Override
+ public boolean onFailedToRecycleView(@NonNull ViewHolder holder) {
+ return mController.onFailedToRecycleView(holder);
+ }
+
+ @Override
+ public void onViewAttachedToWindow(@NonNull ViewHolder holder) {
+ mController.onViewAttachedToWindow(holder);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(@NonNull ViewHolder holder) {
+ mController.onViewDetachedFromWindow(holder);
+ }
+
+ @Override
+ public void onViewRecycled(@NonNull ViewHolder holder) {
+ mController.onViewRecycled(holder);
+ }
+
+ @Override
+ public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
+ mController.onAttachedToRecyclerView(recyclerView);
+ }
+
+ @Override
+ public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
+ mController.onDetachedFromRecyclerView(recyclerView);
+ }
+
+ /**
+ * Returns a copy of the list of adapters in this {@link MergeAdapter}.
+ * Note that this is a copy hence future changes in the MergeAdapter are not reflected in
+ * this list.
+ *
+ * @return A copy of the list of adapters in this MergeAdapter.
+ */
+ @NonNull
+ public List<Adapter<? extends ViewHolder>> getCopyOfAdapters() {
+ return mController.getCopyOfAdapters();
+ }
+
+ /**
+ * Returns the position of the given {@link ViewHolder} in the given {@link Adapter}.
+ *
+ * If the given {@link Adapter} is not part of this {@link MergeAdapter},
+ * {@link RecyclerView#NO_POSITION} is returned.
+ *
+ * @param adapter The adapter which is a sub adapter of this MergeAdapter or itself.
+ * @param viewHolder The view holder whose local position in the given adapter will be returned.
+ * @return The local position of the given {@link ViewHolder} in the given {@link Adapter} or
+ * {@link RecyclerView#NO_POSITION} if the {@link ViewHolder} is not bound to an item or the
+ * given {@link Adapter} is not part of this MergeAdapter.
+ */
+ @Override
+ public int findRelativeAdapterPositionIn(
+ @NonNull Adapter<? extends ViewHolder> adapter,
+ @NonNull ViewHolder viewHolder,
+ int globalPosition) {
+ return mController.getLocalAdapterPosition(adapter, viewHolder, globalPosition);
+ }
+
+ /**
+ * The configuration object for a {@link MergeAdapter}.
+ */
+ public static class Config {
+ /**
+ * If {@code false}, {@link MergeAdapter} assumes all assigned adapters share a global
+ * view type pool such that they use the same view types to refer to the same
+ * {@link ViewHolder}s.
+ * <p>
+ * Setting this to {@code false} will allow nested adapters to share {@link ViewHolder}s but
+ * it also means these adapters should not have conflicting view types
+ * ({@link Adapter#getItemViewType(int)}) such that two different adapters return the same
+ * view type for different {@link ViewHolder}s.
+ *
+ * By default, it is set to {@code true} which means {@link MergeAdapter} will isolate
+ * view types across adapters, preventing them from using the same {@link ViewHolder}s.
+ */
+ public final boolean isolateViewTypes;
+ @NonNull
+ public static final Config DEFAULT = new Config(true);
+
+ Config(boolean isolateViewTypes) {
+ this.isolateViewTypes = isolateViewTypes;
+ }
+
+ /**
+ * The builder for {@link Config} class.
+ */
+ public static class Builder {
+ private boolean mIsolateViewTypes;
+
+ /**
+ * Sets whether {@link MergeAdapter} should isolate view types of nested adapters from
+ * each other.
+ *
+ * @param isolateViewTypes {@code true} if {@link MergeAdapter} should override view
+ * types of nested adapters to avoid view type
+ * conflicts, {@code false} otherwise.
+ * Defaults to true.
+ * @return this
+ * @see Config#isolateViewTypes
+ */
+ @NonNull
+ public Builder setIsolateViewTypes(boolean isolateViewTypes) {
+ mIsolateViewTypes = isolateViewTypes;
+ return this;
+ }
+
+ /**
+ * @return A new instance of {@link Config} with the given parameters.
+ */
+ @NonNull
+ public Config build() {
+ return new Config(mIsolateViewTypes);
+ }
+ }
+ }
+}
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/MergeAdapterController.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/MergeAdapterController.java
new file mode 100644
index 00000000000..ab003d3428a
--- /dev/null
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/MergeAdapterController.java
@@ -0,0 +1,464 @@
+/*
+ * Copyright 2020 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 androidx.recyclerview.widget;
+
+import static androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationStrategy.ALLOW;
+import static androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationStrategy.PREVENT;
+import static androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationStrategy.PREVENT_WHEN_EMPTY;
+import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
+
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+import androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationStrategy;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+
+/**
+ * All logic for the {@link MergeAdapter} is here so that we can clearly see a separation
+ * between an adapter implementation and merging logic.
+ */
+class MergeAdapterController implements NestedAdapterWrapper.Callback {
+ private final MergeAdapter mMergeAdapter;
+
+ /**
+ * Holds the mapping from the view type to the adapter which reported that type.
+ */
+ private final ViewTypeStorage mViewTypeStorage;
+
+ /**
+ * We hold onto the list of attached recyclerviews so that we can dispatch attach/detach to
+ * any adapter that was added later on.
+ * Probably does not need to be a weak reference but playing safe here.
+ */
+ private List<WeakReference<RecyclerView>> mAttachedRecyclerViews = new ArrayList<>();
+
+ /**
+ * Keeps the information about which ViewHolder is bound by which adapter.
+ * It is set in onBind, reset at onRecycle.
+ */
+ private final IdentityHashMap<ViewHolder, NestedAdapterWrapper>
+ mBinderLookup = new IdentityHashMap<>();
+
+ private List<NestedAdapterWrapper> mWrappers = new ArrayList<>();
+
+ // keep one of these around so that we can return wrapper & position w/o allocation ¯\_(ツ)_/¯
+ private WrapperAndLocalPosition mReusableHolder = new WrapperAndLocalPosition();
+
+ MergeAdapterController(
+ MergeAdapter mergeAdapter,
+ MergeAdapter.Config config) {
+ mMergeAdapter = mergeAdapter;
+ if (config.isolateViewTypes) {
+ mViewTypeStorage = new ViewTypeStorage.IsolatedViewTypeStorage();
+ } else {
+ mViewTypeStorage = new ViewTypeStorage.SharedIdRangeViewTypeStorage();
+ }
+ }
+
+ @Nullable
+ private NestedAdapterWrapper findWrapperFor(Adapter<ViewHolder> adapter) {
+ final int index = indexOfWrapper(adapter);
+ if (index == -1) {
+ return null;
+ }
+ return mWrappers.get(index);
+ }
+
+ private int indexOfWrapper(Adapter<ViewHolder> adapter) {
+ final int limit = mWrappers.size();
+ for (int i = 0; i < limit; i++) {
+ if (mWrappers.get(i).adapter == adapter) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * return true if added, false otherwise.
+ *
+ * @see MergeAdapter#addAdapter(Adapter)
+ */
+ boolean addAdapter(Adapter<ViewHolder> adapter) {
+ return addAdapter(mWrappers.size(), adapter);
+ }
+
+ /**
+ * return true if added, false otherwise.
+ * throws exception if index is out of bounds
+ *
+ * @see MergeAdapter#addAdapter(int, Adapter)
+ */
+ boolean addAdapter(int index, Adapter<ViewHolder> adapter) {
+ if (index < 0 || index > mWrappers.size()) {
+ throw new IndexOutOfBoundsException("Index must be between 0 and "
+ + mWrappers.size() + ". Given:" + index);
+ }
+ NestedAdapterWrapper existing = findWrapperFor(adapter);
+ if (existing != null) {
+ return false;
+ }
+ NestedAdapterWrapper wrapper = new NestedAdapterWrapper(adapter, this, mViewTypeStorage);
+ mWrappers.add(index, wrapper);
+ // notify attach for all recyclerview
+ for (WeakReference<RecyclerView> reference : mAttachedRecyclerViews) {
+ RecyclerView recyclerView = reference.get();
+ if (recyclerView != null) {
+ adapter.onAttachedToRecyclerView(recyclerView);
+ }
+ }
+ // new items, notify add for them
+ if (wrapper.getCachedItemCount() > 0) {
+ mMergeAdapter.notifyItemRangeInserted(
+ countItemsBefore(wrapper),
+ wrapper.getCachedItemCount()
+ );
+ }
+ // reset state restoration strategy
+ calculateAndUpdateStateRestorationStrategy();
+ return true;
+ }
+
+ boolean removeAdapter(Adapter<ViewHolder> adapter) {
+ final int index = indexOfWrapper(adapter);
+ if (index == -1) {
+ return false;
+ }
+ NestedAdapterWrapper wrapper = mWrappers.get(index);
+ int offset = countItemsBefore(wrapper);
+ mWrappers.remove(index);
+ mMergeAdapter.notifyItemRangeRemoved(offset, wrapper.getCachedItemCount());
+ // notify detach for all recyclerviews
+ for (WeakReference<RecyclerView> reference : mAttachedRecyclerViews) {
+ RecyclerView recyclerView = reference.get();
+ if (recyclerView != null) {
+ adapter.onDetachedFromRecyclerView(recyclerView);
+ }
+ }
+ wrapper.dispose();
+ calculateAndUpdateStateRestorationStrategy();
+ return true;
+ }
+
+ private int countItemsBefore(NestedAdapterWrapper wrapper) {
+ int count = 0;
+ for (NestedAdapterWrapper item : mWrappers) {
+ if (item != wrapper) {
+ count += item.getCachedItemCount();
+ } else {
+ break;
+ }
+ }
+ return count;
+ }
+
+ @Override
+ public void onChanged(@NonNull NestedAdapterWrapper wrapper) {
+ // TODO should we notify more cleverly, maybe in v2
+ mMergeAdapter.notifyDataSetChanged();
+ calculateAndUpdateStateRestorationStrategy();
+ }
+
+ @Override
+ public void onItemRangeChanged(@NonNull NestedAdapterWrapper nestedAdapterWrapper,
+ int positionStart, int itemCount) {
+ final int offset = countItemsBefore(nestedAdapterWrapper);
+ mMergeAdapter.notifyItemRangeChanged(
+ positionStart + offset,
+ itemCount
+ );
+ }
+
+ @Override
+ public void onItemRangeChanged(@NonNull NestedAdapterWrapper nestedAdapterWrapper,
+ int positionStart, int itemCount, @Nullable Object payload) {
+ final int offset = countItemsBefore(nestedAdapterWrapper);
+ mMergeAdapter.notifyItemRangeChanged(
+ positionStart + offset,
+ itemCount,
+ payload
+ );
+ }
+
+ @Override
+ public void onItemRangeInserted(@NonNull NestedAdapterWrapper nestedAdapterWrapper,
+ int positionStart, int itemCount) {
+ final int offset = countItemsBefore(nestedAdapterWrapper);
+ mMergeAdapter.notifyItemRangeInserted(
+ positionStart + offset,
+ itemCount
+ );
+ }
+
+ @Override
+ public void onItemRangeRemoved(@NonNull NestedAdapterWrapper nestedAdapterWrapper,
+ int positionStart, int itemCount) {
+ int offset = countItemsBefore(nestedAdapterWrapper);
+ mMergeAdapter.notifyItemRangeRemoved(
+ positionStart + offset,
+ itemCount
+ );
+ }
+
+ @Override
+ public void onItemRangeMoved(@NonNull NestedAdapterWrapper nestedAdapterWrapper,
+ int fromPosition, int toPosition) {
+ int offset = countItemsBefore(nestedAdapterWrapper);
+ mMergeAdapter.notifyItemMoved(
+ fromPosition + offset,
+ toPosition + offset
+ );
+ }
+
+ @Override
+ public void onStateRestorationStrategyChanged(NestedAdapterWrapper nestedAdapterWrapper) {
+ calculateAndUpdateStateRestorationStrategy();
+ }
+
+ private void calculateAndUpdateStateRestorationStrategy() {
+ StateRestorationStrategy newStrategy = computeStateRestorationStrategy();
+ if (newStrategy != mMergeAdapter.getStateRestorationStrategy()) {
+ mMergeAdapter.internalSetStateRestorationStrategy(newStrategy);
+ }
+ }
+
+ private StateRestorationStrategy computeStateRestorationStrategy() {
+ for (NestedAdapterWrapper wrapper : mWrappers) {
+ StateRestorationStrategy strategy =
+ wrapper.adapter.getStateRestorationStrategy();
+ if (strategy == PREVENT) {
+ // one adapter can block all
+ return PREVENT;
+ } else if (strategy == PREVENT_WHEN_EMPTY && wrapper.getCachedItemCount() == 0) {
+ // an adapter wants to allow w/ size but we need to make sure there is no prevent
+ return PREVENT;
+ }
+ }
+ return ALLOW;
+ }
+
+ public int getTotalCount() {
+ // should we cache this as well ?
+ int total = 0;
+ for (NestedAdapterWrapper wrapper : mWrappers) {
+ total += wrapper.getCachedItemCount();
+ }
+ return total;
+ }
+
+ public int getItemViewType(int globalPosition) {
+ WrapperAndLocalPosition wrapperAndPos = findWrapperAndLocalPosition(globalPosition);
+ int itemViewType = wrapperAndPos.mWrapper.getItemViewType(wrapperAndPos.mLocalPosition);
+ releaseWrapperAndLocalPosition(wrapperAndPos);
+ return itemViewType;
+ }
+
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int globalViewType) {
+ NestedAdapterWrapper wrapper = mViewTypeStorage.getWrapperForGlobalType(globalViewType);
+ return wrapper.onCreateViewHolder(parent, globalViewType);
+ }
+
+ /**
+ * Always call {@link #releaseWrapperAndLocalPosition(WrapperAndLocalPosition)} when you are
+ * done with it
+ */
+ @NonNull
+ private WrapperAndLocalPosition findWrapperAndLocalPosition(
+ int globalPosition
+ ) {
+ WrapperAndLocalPosition result;
+ if (mReusableHolder.mInUse) {
+ result = new WrapperAndLocalPosition();
+ } else {
+ mReusableHolder.mInUse = true;
+ result = mReusableHolder;
+ }
+ int localPosition = globalPosition;
+ for (NestedAdapterWrapper wrapper : mWrappers) {
+ if (wrapper.getCachedItemCount() > localPosition) {
+ result.mWrapper = wrapper;
+ result.mLocalPosition = localPosition;
+ break;
+ }
+ localPosition -= wrapper.getCachedItemCount();
+ }
+ if (result.mWrapper == null) {
+ throw new IllegalArgumentException("Cannot find wrapper for " + globalPosition);
+ }
+ return result;
+ }
+
+ private void releaseWrapperAndLocalPosition(WrapperAndLocalPosition wrapperAndLocalPosition) {
+ wrapperAndLocalPosition.mInUse = false;
+ wrapperAndLocalPosition.mWrapper = null;
+ wrapperAndLocalPosition.mLocalPosition = -1;
+ mReusableHolder = wrapperAndLocalPosition;
+ }
+
+ public void onBindViewHolder(ViewHolder holder, int globalPosition) {
+ WrapperAndLocalPosition wrapperAndPos = findWrapperAndLocalPosition(globalPosition);
+ mBinderLookup.put(holder, wrapperAndPos.mWrapper);
+ wrapperAndPos.mWrapper.onBindViewHolder(holder, wrapperAndPos.mLocalPosition);
+ releaseWrapperAndLocalPosition(wrapperAndPos);
+ }
+
+ public boolean canRestoreState() {
+ for (NestedAdapterWrapper wrapper : mWrappers) {
+ if (!wrapper.adapter.canRestoreState()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void onViewAttachedToWindow(ViewHolder holder) {
+ NestedAdapterWrapper wrapper = getWrapper(holder);
+ wrapper.adapter.onViewAttachedToWindow(holder);
+ }
+
+ public void onViewDetachedFromWindow(ViewHolder holder) {
+ NestedAdapterWrapper wrapper = getWrapper(holder);
+ wrapper.adapter.onViewDetachedFromWindow(holder);
+ }
+
+ public void onViewRecycled(ViewHolder holder) {
+ NestedAdapterWrapper wrapper = mBinderLookup.remove(holder);
+ if (wrapper == null) {
+ throw new IllegalStateException("Cannot find wrapper for " + holder
+ + ", seems like it is not bound by this adapter: " + this);
+ }
+ wrapper.adapter.onViewRecycled(holder);
+ }
+
+ public boolean onFailedToRecycleView(ViewHolder holder) {
+ NestedAdapterWrapper wrapper = mBinderLookup.remove(holder);
+ if (wrapper == null) {
+ throw new IllegalStateException("Cannot find wrapper for " + holder
+ + ", seems like it is not bound by this adapter: " + this);
+ }
+ return wrapper.adapter.onFailedToRecycleView(holder);
+ }
+
+ @NonNull
+ private NestedAdapterWrapper getWrapper(ViewHolder holder) {
+ NestedAdapterWrapper wrapper = mBinderLookup.get(holder);
+ if (wrapper == null) {
+ throw new IllegalStateException("Cannot find wrapper for " + holder
+ + ", seems like it is not bound by this adapter: " + this);
+ }
+ return wrapper;
+ }
+
+ private boolean isAttachedTo(RecyclerView recyclerView) {
+ for (WeakReference<RecyclerView> reference : mAttachedRecyclerViews) {
+ if (reference.get() == recyclerView) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+ if (isAttachedTo(recyclerView)) {
+ return;
+ }
+ mAttachedRecyclerViews.add(new WeakReference<>(recyclerView));
+ for (NestedAdapterWrapper wrapper : mWrappers) {
+ wrapper.adapter.onAttachedToRecyclerView(recyclerView);
+ }
+ }
+
+ public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+ for (int i = mAttachedRecyclerViews.size() - 1; i >= 0; i--) {
+ WeakReference<RecyclerView> reference = mAttachedRecyclerViews.get(i);
+ if (reference.get() == null) {
+ mAttachedRecyclerViews.remove(i);
+ } else if (reference.get() == recyclerView) {
+ mAttachedRecyclerViews.remove(i);
+ break; // here we can break as we don't keep duplicates
+ }
+ }
+ for (NestedAdapterWrapper wrapper : mWrappers) {
+ wrapper.adapter.onDetachedFromRecyclerView(recyclerView);
+ }
+ }
+
+ public int getLocalAdapterPosition(
+ Adapter<? extends ViewHolder> adapter,
+ ViewHolder viewHolder,
+ int globalPosition
+ ) {
+ NestedAdapterWrapper wrapper = mBinderLookup.get(viewHolder);
+ if (wrapper == null) {
+ return NO_POSITION;
+ }
+ int itemsBefore = countItemsBefore(wrapper);
+ // local position is globalPosition - itemsBefore
+ int localPosition = globalPosition - itemsBefore;
+ // sanity check to detect errors early on
+ if (localPosition < 0 || localPosition >= wrapper.adapter.getItemCount()) {
+ throw new IllegalStateException("Detected inconsistent adapter updates. The"
+ + " local position of the view holder maps to " + localPosition + " which"
+ + " is out of bounds for the adapter with size "
+ + wrapper.getCachedItemCount() + "."
+ + "Make sure to immediately call notify methods in your adapter when you "
+ + "change the backing data"
+ + "viewHolder:" + viewHolder
+ + "adapter:" + adapter);
+ }
+ return wrapper.adapter.findRelativeAdapterPositionIn(adapter, viewHolder, localPosition);
+ }
+
+
+ @Nullable
+ public Adapter<? extends ViewHolder> getBoundAdapter(ViewHolder viewHolder) {
+ NestedAdapterWrapper wrapper = mBinderLookup.get(viewHolder);
+ if (wrapper == null) {
+ return null;
+ }
+ return wrapper.adapter;
+ }
+
+ public List<Adapter<? extends ViewHolder>> getCopyOfAdapters() {
+ if (mWrappers.isEmpty()) {
+ return Collections.emptyList();
+ }
+ List<Adapter<? extends ViewHolder>> adapters = new ArrayList<>(mWrappers.size());
+ for (NestedAdapterWrapper wrapper : mWrappers) {
+ adapters.add(wrapper.adapter);
+ }
+ return adapters;
+ }
+
+ /**
+ * Helper class to hold onto wrapper and local position without allocating objects as this is
+ * a very common call.
+ */
+ static class WrapperAndLocalPosition {
+ NestedAdapterWrapper mWrapper;
+ int mLocalPosition;
+ boolean mInUse;
+ }
+}
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/NestedAdapterWrapper.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/NestedAdapterWrapper.java
new file mode 100644
index 00000000000..ff1dfe56fb8
--- /dev/null
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/NestedAdapterWrapper.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2020 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 androidx.recyclerview.widget;
+
+import static androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationStrategy.PREVENT_WHEN_EMPTY;
+
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.util.Preconditions;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+/**
+ * Wrapper for each adapter in {@link MergeAdapter}.
+ */
+class NestedAdapterWrapper {
+ private final ViewTypeStorage.ViewTypeLookup mViewTypeLookup;
+ public final Adapter<ViewHolder> adapter;
+ @SuppressWarnings("WeakerAccess")
+ final Callback mCallback;
+ // we cache this value so that we can know the previous size when change happens
+ // this is also important as getting real size while an adapter is dispatching possibly a
+ // a chain of events might create inconsistencies (as it happens in DiffUtil).
+ // Instead, we always calculate this value based on notify events.
+ @SuppressWarnings("WeakerAccess")
+ int mCachedItemCount;
+
+ private RecyclerView.AdapterDataObserver mAdapterObserver =
+ new RecyclerView.AdapterDataObserver() {
+ @Override
+ public void onChanged() {
+ mCachedItemCount = adapter.getItemCount();
+ mCallback.onChanged(NestedAdapterWrapper.this);
+ }
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ mCallback.onItemRangeChanged(
+ NestedAdapterWrapper.this,
+ positionStart,
+ itemCount,
+ null
+ );
+ }
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount,
+ @Nullable Object payload) {
+ mCallback.onItemRangeChanged(
+ NestedAdapterWrapper.this,
+ positionStart,
+ itemCount,
+ payload
+ );
+ }
+
+ @Override
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ mCachedItemCount += itemCount;
+ mCallback.onItemRangeInserted(
+ NestedAdapterWrapper.this,
+ positionStart,
+ itemCount);
+ if (mCachedItemCount > 0
+ && adapter.getStateRestorationStrategy() == PREVENT_WHEN_EMPTY) {
+ mCallback.onStateRestorationStrategyChanged(NestedAdapterWrapper.this);
+ }
+ }
+
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ mCachedItemCount -= itemCount;
+ mCallback.onItemRangeRemoved(
+ NestedAdapterWrapper.this,
+ positionStart,
+ itemCount
+ );
+ if (mCachedItemCount < 1
+ && adapter.getStateRestorationStrategy() == PREVENT_WHEN_EMPTY) {
+ mCallback.onStateRestorationStrategyChanged(NestedAdapterWrapper.this);
+ }
+ }
+
+ @Override
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ Preconditions.checkArgument(itemCount == 1,
+ "moving more than 1 item is not supported in RecyclerView");
+ mCallback.onItemRangeMoved(
+ NestedAdapterWrapper.this,
+ fromPosition,
+ toPosition
+ );
+ }
+
+ @Override
+ public void onStateRestorationStrategyChanged() {
+ mCallback.onStateRestorationStrategyChanged(
+ NestedAdapterWrapper.this
+ );
+ }
+ };
+
+ NestedAdapterWrapper(
+ Adapter<ViewHolder> adapter,
+ final Callback callback,
+ ViewTypeStorage viewTypeStorage) {
+ this.adapter = adapter;
+ mCallback = callback;
+ mViewTypeLookup = viewTypeStorage.createViewTypeWrapper(this);
+ mCachedItemCount = this.adapter.getItemCount();
+ this.adapter.registerAdapterDataObserver(mAdapterObserver);
+ }
+
+
+ void dispose() {
+ adapter.unregisterAdapterDataObserver(mAdapterObserver);
+ mViewTypeLookup.dispose();
+ }
+
+ int getCachedItemCount() {
+ return mCachedItemCount;
+ }
+
+ int getItemViewType(int localPosition) {
+ return mViewTypeLookup.localToGlobal(adapter.getItemViewType(localPosition));
+ }
+
+ ViewHolder onCreateViewHolder(
+ ViewGroup parent,
+ int globalViewType) {
+ int localType = mViewTypeLookup.globalToLocal(globalViewType);
+ return adapter.onCreateViewHolder(parent, localType);
+ }
+
+ void onBindViewHolder(ViewHolder viewHolder, int localPosition) {
+ adapter.bindViewHolder(viewHolder, localPosition);
+ }
+
+ interface Callback {
+ void onChanged(@NonNull NestedAdapterWrapper wrapper);
+
+ void onItemRangeChanged(
+ @NonNull NestedAdapterWrapper nestedAdapterWrapper,
+ int positionStart,
+ int itemCount
+ );
+
+ void onItemRangeChanged(
+ @NonNull NestedAdapterWrapper nestedAdapterWrapper,
+ int positionStart,
+ int itemCount,
+ @Nullable Object payload
+ );
+
+ void onItemRangeInserted(
+ @NonNull NestedAdapterWrapper nestedAdapterWrapper,
+ int positionStart,
+ int itemCount);
+
+ void onItemRangeRemoved(
+ @NonNull NestedAdapterWrapper nestedAdapterWrapper,
+ int positionStart,
+ int itemCount
+ );
+
+ void onItemRangeMoved(
+ @NonNull NestedAdapterWrapper nestedAdapterWrapper,
+ int fromPosition,
+ int toPosition
+ );
+
+ void onStateRestorationStrategyChanged(NestedAdapterWrapper nestedAdapterWrapper);
+ }
+
+}
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
index 5ae3d825492..201fb2f572e 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
@@ -146,14 +146,15 @@ import java.util.List;
* is seeing.
* <p>
* The other set of position related methods are in the form of
- * <code>*AdapterPosition*</code>. (e.g. {@link ViewHolder#getAdapterPosition()},
+ * <code>*AdapterPosition*</code>. (e.g. {@link ViewHolder#getAbsoluteAdapterPosition()},
+ * {@link ViewHolder#getBindingAdapterPosition()},
* {@link #findViewHolderForAdapterPosition(int)}) You should use these methods when you need to
* work with up-to-date adapter positions even if they may not have been reflected to layout yet.
* For example, if you want to access the item in the adapter on a ViewHolder click, you should use
- * {@link ViewHolder#getAdapterPosition()}. Beware that these methods may not be able to calculate
- * adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new layout has
- * not yet been calculated. For this reasons, you should carefully handle {@link #NO_POSITION} or
- * <code>null</code> results from these methods.
+ * {@link ViewHolder#getBindingAdapterPosition()}. Beware that these methods may not be able to
+ * calculate adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new
+ * layout has not yet been calculated. For this reasons, you should carefully handle
+ * {@link #NO_POSITION} or <code>null</code> results from these methods.
* <p>
* When writing a {@link LayoutManager} you almost always want to use layout positions whereas when
* writing an {@link Adapter}, you probably want to use adapter positions.
@@ -3879,7 +3880,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
// removed item.
mState.mFocusedItemPosition = mDataSetHasChangedAfterLayout ? NO_POSITION
: (focusedVh.isRemoved() ? focusedVh.mOldPosition
- : focusedVh.getAdapterPosition());
+ : focusedVh.getAbsoluteAdapterPosition());
mState.mFocusedSubChildId = getDeepestFocusedViewWithId(focusedVh.itemView);
}
}
@@ -4841,7 +4842,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
*/
public int getChildAdapterPosition(@NonNull View child) {
final ViewHolder holder = getChildViewHolderInt(child);
- return holder != null ? holder.getAdapterPosition() : NO_POSITION;
+ return holder != null ? holder.getAbsoluteAdapterPosition() : NO_POSITION;
}
/**
@@ -4893,7 +4894,11 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
* Note that when Adapter contents change, ViewHolder positions are not updated until the
* next layout calculation. If there are pending adapter updates, the return value of this
* method may not match your adapter contents. You can use
- * #{@link ViewHolder#getAdapterPosition()} to get the current adapter position of a ViewHolder.
+ * #{@link ViewHolder#getBindingAdapterPosition()} to get the current adapter position
+ * of a ViewHolder. If the {@link Adapter} that is assigned to the RecyclerView is an adapter
+ * that combines other adapters (e.g. {@link MergeAdapter}), you can use the
+ * {@link ViewHolder#getBindingAdapter()}) to find the position relative to the {@link Adapter}
+ * that bound the {@link ViewHolder}.
* <p>
* When the ItemAnimator is running a change animation, there might be 2 ViewHolders
* with the same layout position representing the same Item. In this case, the updated
@@ -4935,7 +4940,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.isRemoved()
- && getAdapterPositionFor(holder) == position) {
+ && getAdapterPositionInRecyclerView(holder) == position) {
if (mChildHelper.isHidden(holder.itemView)) {
hidden = holder;
} else {
@@ -6020,6 +6025,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
@SuppressWarnings("unchecked")
private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,
int position, long deadlineNs) {
+ holder.mBindingAdapter = null;
holder.mOwnerRecyclerView = RecyclerView.this;
final int viewType = holder.getItemViewType();
long startBindNs = getNanoTime();
@@ -6527,6 +6533,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
// from view holder lists.
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
+ holder.mBindingAdapter = null;
holder.mOwnerRecyclerView = null;
}
}
@@ -6556,6 +6563,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
if (dispatchRecycled) {
dispatchViewRecycled(holder);
}
+ holder.mBindingAdapter = null;
holder.mOwnerRecyclerView = null;
getRecycledViewPool().putRecycledView(holder);
}
@@ -7039,8 +7047,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
* invalidated or the new position cannot be determined. For this reason, you should only
* use the <code>position</code> parameter while acquiring the related data item inside
* this method and should not keep a copy of it. If you need the position of an item later
- * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
- * have the updated adapter position.
+ * on (e.g. in a click listener), use {@link ViewHolder#getBindingAdapterPosition()} which
+ * will have the updated adapter position.
*
* Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can
* handle efficient partial bind.
@@ -7061,8 +7069,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
* invalidated or the new position cannot be determined. For this reason, you should only
* use the <code>position</code> parameter while acquiring the related data item inside
* this method and should not keep a copy of it. If you need the position of an item later
- * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
- * have the updated adapter position.
+ * on (e.g. in a click listener), use {@link ViewHolder#getBindingAdapterPosition()} which
+ * will have the updated adapter position.
* <p>
* Partial bind vs full bind:
* <p>
@@ -7086,6 +7094,31 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
}
/**
+ * Returns the position of the given {@link ViewHolder} in the given {@link Adapter}.
+ *
+ * If the given {@link Adapter} is not part of this {@link Adapter},
+ * {@link RecyclerView#NO_POSITION} is returned.
+ *
+ * @param adapter The adapter which is a sub adapter of this adapter or itself.
+ * @param viewHolder The ViewHolder whose local position in the given adapter will be
+ * returned.
+ * @return The local position of the given {@link ViewHolder} in this {@link Adapter}
+ * or {@link RecyclerView#NO_POSITION} if the {@link ViewHolder} is not bound to an item
+ * or the given {@link Adapter} is not part of this Adapter (if this Adapter merges other
+ * adapters).
+ */
+ public int findRelativeAdapterPositionIn(
+ @NonNull Adapter<? extends ViewHolder> adapter,
+ @NonNull ViewHolder viewHolder,
+ int localPosition
+ ) {
+ if (adapter == this) {
+ return localPosition;
+ }
+ return NO_POSITION;
+ }
+
+ /**
* This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new
* {@link ViewHolder} and initializes some private fields to be used by RecyclerView.
*
@@ -7113,24 +7146,38 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
* {@link ViewHolder} contents with the item at the given position and also sets up some
* private fields to be used by RecyclerView.
*
+ * Adapters that merge other adapters should use
+ * {@link #bindViewHolder(ViewHolder, int)} when calling nested adapters so that
+ * RecyclerView can track which adapter bound the {@link ViewHolder} to return the correct
+ * position from {@link ViewHolder#getBindingAdapterPosition()} method.
+ * They should also override
+ * the {@link #findRelativeAdapterPositionIn(Adapter, ViewHolder, int)} method.
+ * @param holder The view holder whose contents should be updated
+ * @param position The position of the holder with respect to this adapter
* @see #onBindViewHolder(ViewHolder, int)
*/
public final void bindViewHolder(@NonNull VH holder, int position) {
- holder.mPosition = position;
- if (hasStableIds()) {
- holder.mItemId = getItemId(position);
- }
- holder.setFlags(ViewHolder.FLAG_BOUND,
- ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
- | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
- TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
+ boolean rootBind = holder.mBindingAdapter == null;
+ if (rootBind) {
+ holder.mPosition = position;
+ if (hasStableIds()) {
+ holder.mItemId = getItemId(position);
+ }
+ holder.setFlags(ViewHolder.FLAG_BOUND,
+ ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
+ | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
+ TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
+ }
+ holder.mBindingAdapter = this;
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
- holder.clearPayload();
- final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
- if (layoutParams instanceof RecyclerView.LayoutParams) {
- ((LayoutParams) layoutParams).mInsetsDirty = true;
+ if (rootBind) {
+ holder.clearPayload();
+ final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
+ if (layoutParams instanceof RecyclerView.LayoutParams) {
+ ((LayoutParams) layoutParams).mInsetsDirty = true;
+ }
+ TraceCompat.endSection();
}
- TraceCompat.endSection();
}
/**
@@ -7207,7 +7254,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
* <p>
* RecyclerView calls this method right before clearing ViewHolder's internal data and
* sending it to RecycledViewPool. This way, if ViewHolder was holding valid information
- * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get
+ * before being recycled, you can call {@link ViewHolder#getBindingAdapterPosition()} to get
* its adapter position.
*
* @param holder The ViewHolder for the view being recycled
@@ -11007,7 +11054,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
*
* RecyclerView calls this method right before clearing ViewHolder's internal data and
* sending it to RecycledViewPool. This way, if ViewHolder was holding valid information
- * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get
+ * before being recycled, you can call {@link ViewHolder#getBindingAdapterPosition()} to get
* its adapter position.
*
* @param holder The ViewHolder containing the view that was recycled
@@ -11186,6 +11233,9 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
*/
RecyclerView mOwnerRecyclerView;
+ // The last adapter that bound this ViewHolder. It is cleaned before VH is recycled.
+ Adapter<? extends ViewHolder> mBindingAdapter;
+
public ViewHolder(@NonNull View itemView) {
if (itemView == null) {
throw new IllegalArgumentException("itemView may not be null");
@@ -11232,11 +11282,13 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
/**
* @deprecated This method is deprecated because its meaning is ambiguous due to the async
- * handling of adapter updates. You should use {@link #getLayoutPosition()} or
- * {@link #getAdapterPosition()} depending on your use case.
+ * handling of adapter updates. You should use {@link #getLayoutPosition()},
+ * {@link #getBindingAdapterPosition()} or {@link #getAbsoluteAdapterPosition()}
+ * depending on your use case.
*
* @see #getLayoutPosition()
- * @see #getAdapterPosition()
+ * @see #getBindingAdapterPosition()
+ * @see #getAbsoluteAdapterPosition()
*/
@Deprecated
public final int getPosition() {
@@ -11259,18 +11311,33 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
* of the item.
* <p>
* If LayoutManager needs to call an external method that requires the adapter position of
- * the item, it can use {@link #getAdapterPosition()} or
+ * the item, it can use {@link #getAbsoluteAdapterPosition()} or
* {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}.
*
* @return Returns the adapter position of the ViewHolder in the latest layout pass.
- * @see #getAdapterPosition()
+ * @see #getBindingAdapterPosition()
+ * @see #getAbsoluteAdapterPosition()
*/
public final int getLayoutPosition() {
return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
}
+
/**
- * Returns the Adapter position of the item represented by this ViewHolder.
+ * @deprecated This method is confusing when adapters nest other adapters.
+ * If you are calling this in the context of an Adapter, you probably want to call
+ * {@link #getBindingAdapterPosition()} or if you want the position as {@link RecyclerView}
+ * sees it, you should call {@link #getAbsoluteAdapterPosition()}.
+ * @return {@link #getBindingAdapterPosition()}
+ */
+ @Deprecated
+ public final int getAdapterPosition() {
+ return getBindingAdapterPosition();
+ }
+
+ /**
+ * Returns the Adapter position of the item represented by this ViewHolder with respect to
+ * the {@link Adapter} that bound it.
* <p>
* Note that this might be different than the {@link #getLayoutPosition()} if there are
* pending adapter updates but a new layout pass has not happened yet.
@@ -11285,17 +11352,89 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
* <p>
* Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the
* next layout pass, the return value of this method will be {@link #NO_POSITION}.
- *
+ * <p>
+ * If the {@link Adapter} that bound this {@link ViewHolder} is inside another
+ * {@link Adapter} (e.g. {@link MergeAdapter}), this position might be different than
+ * {@link #getAbsoluteAdapterPosition()}. If you would like to know the position that
+ * {@link RecyclerView} considers (e.g. for saved state), you should use
+ * {@link #getAbsoluteAdapterPosition()}.
* @return The adapter position of the item if it still exists in the adapter.
* {@link RecyclerView#NO_POSITION} if item has been removed from the adapter,
* {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last
* layout pass or the ViewHolder has already been recycled.
+ * @see #getAbsoluteAdapterPosition()
+ * @see #getLayoutPosition()
*/
- public final int getAdapterPosition() {
+ public final int getBindingAdapterPosition() {
+ if (mBindingAdapter == null) {
+ return NO_POSITION;
+ }
if (mOwnerRecyclerView == null) {
return NO_POSITION;
}
- return mOwnerRecyclerView.getAdapterPositionFor(this);
+ @SuppressWarnings("unchecked")
+ Adapter<? extends ViewHolder> rvAdapter = mOwnerRecyclerView.getAdapter();
+ if (rvAdapter == null) {
+ return NO_POSITION;
+ }
+ int globalPosition = mOwnerRecyclerView.getAdapterPositionInRecyclerView(this);
+ if (globalPosition == NO_POSITION) {
+ return NO_POSITION;
+ }
+ return rvAdapter.findRelativeAdapterPositionIn(mBindingAdapter, this, globalPosition);
+ }
+
+ /**
+ * Returns the Adapter position of the item represented by this ViewHolder with respect to
+ * the {@link RecyclerView}'s {@link Adapter}. If the {@link Adapter} that bound this
+ * {@link ViewHolder} is inside another adapter (e.g. {@link MergeAdapter}), this
+ * position might be different and will include
+ * the offsets caused by other adapters in the {@link MergeAdapter}.
+ * <p>
+ * Note that this might be different than the {@link #getLayoutPosition()} if there are
+ * pending adapter updates but a new layout pass has not happened yet.
+ * <p>
+ * RecyclerView does not handle any adapter updates until the next layout traversal. This
+ * may create temporary inconsistencies between what user sees on the screen and what
+ * adapter contents have. This inconsistency is not important since it will be less than
+ * 16ms but it might be a problem if you want to use ViewHolder position to access the
+ * adapter. Sometimes, you may need to get the exact adapter position to do
+ * some actions in response to user events. In that case, you should use this method which
+ * will calculate the Adapter position of the ViewHolder.
+ * <p>
+ * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the
+ * next layout pass, the return value of this method will be {@link #NO_POSITION}.
+ * <p>
+ * Note that if you are querying the position as {@link RecyclerView} sees, you should use
+ * {@link #getAbsoluteAdapterPosition()} (e.g. you want to use it to save scroll
+ * state). If you are querying the position to access the {@link Adapter} contents,
+ * you should use {@link #getBindingAdapterPosition()}.
+ *
+ * @return The adapter position of the item from {@link RecyclerView}'s perspective if it
+ * still exists in the adapter and bound to a valid item.
+ * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter,
+ * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last
+ * layout pass or the ViewHolder has already been recycled.
+ * @see #getBindingAdapterPosition()
+ * @see #getLayoutPosition()
+ */
+ public final int getAbsoluteAdapterPosition() {
+ if (mOwnerRecyclerView == null) {
+ return NO_POSITION;
+ }
+ return mOwnerRecyclerView.getAdapterPositionInRecyclerView(this);
+ }
+
+ /**
+ * Returns the {@link Adapter} that last bound this {@link ViewHolder}.
+ * Might return {@code null} if this {@link ViewHolder} is not bound to any adapter.
+ *
+ * @return The {@link Adapter} that last bound this {@link ViewHolder} or {@code null} if
+ * this {@link ViewHolder} is not bound by any adapter (e.g. recycled).
+ */
+ @Nullable
+ public final Adapter<? extends ViewHolder> getBindingAdapter() {
+ return mBindingAdapter;
}
/**
@@ -11596,7 +11735,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
mPendingAccessibilityImportanceChange.clear();
}
- int getAdapterPositionFor(ViewHolder viewHolder) {
+ int getAdapterPositionInRecyclerView(ViewHolder viewHolder) {
if (viewHolder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)
|| !viewHolder.isBound()) {
@@ -11805,15 +11944,41 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
}
/**
+ * @deprecated This method is confusing when nested adapters are used.
+ * If you are calling from the context of an {@link Adapter},
+ * use {@link #getBindingAdapterPosition()}. If you need the position that
+ * {@link RecyclerView} sees, use {@link #getAbsoluteAdapterPosition()}.
+ */
+ @Deprecated
+ public int getViewAdapterPosition() {
+ return mViewHolder.getBindingAdapterPosition();
+ }
+
+ /**
* Returns the up-to-date adapter position that the view this LayoutParams is attached to
- * corresponds to.
+ * corresponds to in the {@link RecyclerView}. If the {@link RecyclerView} has an
+ * {@link Adapter} that merges other adapters, this position will be with respect to the
+ * adapter that is assigned to the {@link RecyclerView}.
*
- * @return the up-to-date adapter position this view. It may return
- * {@link RecyclerView#NO_POSITION} if item represented by this View has been removed or
+ * @return the up-to-date adapter position this view with respect to the RecyclerView. It
+ * may return {@link RecyclerView#NO_POSITION} if item represented by this View has been
+ * removed or
* its up-to-date position cannot be calculated.
*/
- public int getViewAdapterPosition() {
- return mViewHolder.getAdapterPosition();
+ public int getAbsoluteAdapterPosition() {
+ return mViewHolder.getAbsoluteAdapterPosition();
+ }
+
+ /**
+ * Returns the up-to-date adapter position that the view this LayoutParams is attached to
+ * corresponds to with respect to the {@link Adapter} that bound this View.
+ *
+ * @return the up-to-date adapter position this view relative to the {@link Adapter} that
+ * bound this View. It may return {@link RecyclerView#NO_POSITION} if item represented by
+ * this View has been removed or its up-to-date position cannot be calculated.
+ */
+ public int getBindingAdapterPosition() {
+ return mViewHolder.getBindingAdapterPosition();
}
}
@@ -13237,7 +13402,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView,
}
if ((flags & FLAG_INVALIDATED) == 0) {
final int oldPos = viewHolder.getOldPosition();
- final int pos = viewHolder.getAdapterPosition();
+ final int pos = viewHolder.getAbsoluteAdapterPosition();
if (oldPos != NO_POSITION && pos != NO_POSITION && oldPos != pos) {
flags |= FLAG_MOVED;
}
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ViewTypeStorage.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ViewTypeStorage.java
new file mode 100644
index 00000000000..0f131299f14
--- /dev/null
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ViewTypeStorage.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2020 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 androidx.recyclerview.widget;
+
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used by {@link MergeAdapter} to isolate view types between nested adapters, if necessary.
+ */
+interface ViewTypeStorage {
+ @NonNull
+ NestedAdapterWrapper getWrapperForGlobalType(int globalViewType);
+
+ @NonNull
+ ViewTypeLookup createViewTypeWrapper(
+ @NonNull NestedAdapterWrapper wrapper
+ );
+
+ /**
+ * Api given to {@link NestedAdapterWrapper}s.
+ */
+ interface ViewTypeLookup {
+ int localToGlobal(int localType);
+
+ int globalToLocal(int globalType);
+
+ void dispose();
+ }
+
+ class SharedIdRangeViewTypeStorage implements ViewTypeStorage {
+ // we keep a list of nested wrappers here even though we only need 1 to create because
+ // they might be removed.
+ SparseArray<List<NestedAdapterWrapper>> mGlobalTypeToWrapper = new SparseArray<>();
+
+ @NonNull
+ @Override
+ public NestedAdapterWrapper getWrapperForGlobalType(int globalViewType) {
+ List<NestedAdapterWrapper> nestedAdapterWrappers = mGlobalTypeToWrapper.get(
+ globalViewType);
+ if (nestedAdapterWrappers == null || nestedAdapterWrappers.isEmpty()) {
+ throw new IllegalArgumentException("Cannot find the wrapper for global view"
+ + " type " + globalViewType);
+ }
+ // just return the first one since they are shared
+ return nestedAdapterWrappers.get(0);
+ }
+
+ @NonNull
+ @Override
+ public ViewTypeLookup createViewTypeWrapper(
+ @NonNull NestedAdapterWrapper wrapper) {
+ return new WrapperViewTypeLookup(wrapper);
+ }
+
+ void removeWrapper(@NonNull NestedAdapterWrapper wrapper) {
+ for (int i = mGlobalTypeToWrapper.size() - 1; i >= 0; i--) {
+ List<NestedAdapterWrapper> wrappers = mGlobalTypeToWrapper.valueAt(i);
+ if (wrappers.remove(wrapper)) {
+ if (wrappers.isEmpty()) {
+ mGlobalTypeToWrapper.removeAt(i);
+ }
+ }
+ }
+ }
+
+ class WrapperViewTypeLookup implements ViewTypeLookup {
+ final NestedAdapterWrapper mWrapper;
+
+ WrapperViewTypeLookup(NestedAdapterWrapper wrapper) {
+ mWrapper = wrapper;
+ }
+
+ @Override
+ public int localToGlobal(int localType) {
+ // register it first
+ List<NestedAdapterWrapper> wrappers = mGlobalTypeToWrapper.get(
+ localType);
+ if (wrappers == null) {
+ wrappers = new ArrayList<>();
+ mGlobalTypeToWrapper.put(localType, wrappers);
+ }
+ if (!wrappers.contains(mWrapper)) {
+ wrappers.add(mWrapper);
+ }
+ return localType;
+ }
+
+ @Override
+ public int globalToLocal(int globalType) {
+ return globalType;
+ }
+
+ @Override
+ public void dispose() {
+ removeWrapper(mWrapper);
+ }
+ }
+ }
+
+ class IsolatedViewTypeStorage implements ViewTypeStorage {
+ SparseArray<NestedAdapterWrapper> mGlobalTypeToWrapper = new SparseArray<>();
+
+ int mNextViewType = 0;
+
+ int obtainViewType(NestedAdapterWrapper wrapper) {
+ int nextId = mNextViewType++;
+ mGlobalTypeToWrapper.put(nextId, wrapper);
+ return nextId;
+ }
+
+ @NonNull
+ @Override
+ public NestedAdapterWrapper getWrapperForGlobalType(int globalViewType) {
+ NestedAdapterWrapper wrapper = mGlobalTypeToWrapper.get(
+ globalViewType);
+ if (wrapper == null) {
+ throw new IllegalArgumentException("Cannot find the wrapper for global"
+ + " view type " + globalViewType);
+ }
+ return wrapper;
+ }
+
+ @Override
+ @NonNull
+ public ViewTypeLookup createViewTypeWrapper(
+ @NonNull NestedAdapterWrapper wrapper) {
+ return new WrapperViewTypeLookup(wrapper);
+ }
+
+ void removeWrapper(@NonNull NestedAdapterWrapper wrapper) {
+ for (int i = mGlobalTypeToWrapper.size() - 1; i >= 0; i--) {
+ NestedAdapterWrapper existingWrapper = mGlobalTypeToWrapper.valueAt(i);
+ if (existingWrapper == wrapper) {
+ mGlobalTypeToWrapper.removeAt(i);
+ }
+ }
+ }
+
+ class WrapperViewTypeLookup implements ViewTypeLookup {
+ private SparseIntArray mLocalToGlobalMapping = new SparseIntArray(1);
+ private SparseIntArray mGlobalToLocalMapping = new SparseIntArray(1);
+ final NestedAdapterWrapper mWrapper;
+
+ WrapperViewTypeLookup(NestedAdapterWrapper wrapper) {
+ mWrapper = wrapper;
+ }
+
+ @Override
+ public int localToGlobal(int localType) {
+ int index = mLocalToGlobalMapping.indexOfKey(localType);
+ if (index > -1) {
+ return mLocalToGlobalMapping.valueAt(index);
+ }
+ // get a new key.
+ int globalType = obtainViewType(mWrapper);
+ mLocalToGlobalMapping.put(localType, globalType);
+ mGlobalToLocalMapping.put(globalType, localType);
+ return globalType;
+ }
+
+ @Override
+ public int globalToLocal(int globalType) {
+ int index = mGlobalToLocalMapping.indexOfKey(globalType);
+ if (index < 0) {
+ throw new IllegalStateException("requested global type " + globalType + " does"
+ + " not belong to the adapter:" + mWrapper.adapter);
+ }
+ return mGlobalToLocalMapping.valueAt(index);
+ }
+
+ @Override
+ public void dispose() {
+ removeWrapper(mWrapper);
+ }
+ }
+ }
+}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/util/SortedListActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/util/SortedListActivity.java
index 761c3838033..7f4377d4014 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/util/SortedListActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/util/SortedListActivity.java
@@ -121,7 +121,7 @@ public class SortedListActivity extends AppCompatActivity {
mLayoutInflater.inflate(R.layout.sorted_list_item_view, parent, false)) {
@Override
void onDoneChanged(boolean isDone) {
- int adapterPosition = getAdapterPosition();
+ int adapterPosition = getBindingAdapterPosition();
if (adapterPosition == RecyclerView.NO_POSITION) {
return;
}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/AnimatedRecyclerView.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/AnimatedRecyclerView.java
index 8b7a4782f6c..3c857c52e52 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/AnimatedRecyclerView.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/AnimatedRecyclerView.java
@@ -359,7 +359,7 @@ public class AnimatedRecyclerView extends Activity {
public void itemClicked(View view) {
ViewGroup parent = (ViewGroup) view;
MyViewHolder holder = (MyViewHolder) mRecyclerView.getChildViewHolder(parent);
- final int position = holder.getAdapterPosition();
+ final int position = holder.getBindingAdapterPosition();
if (position == RecyclerView.NO_POSITION) {
return;
}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/BaseLayoutManagerActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/BaseLayoutManagerActivity.java
index 98ae6c441ce..ff1f343bb89 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/BaseLayoutManagerActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/BaseLayoutManagerActivity.java
@@ -87,7 +87,7 @@ abstract public class BaseLayoutManagerActivity<T extends RecyclerView.LayoutMan
vh.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- final int pos = vh.getAdapterPosition();
+ final int pos = vh.getBindingAdapterPosition();
if (pos != RecyclerView.NO_POSITION && pos + 1 < getItemCount()) {
swap(pos, pos + 1);
}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/GridLayoutManagerActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/GridLayoutManagerActivity.java
index 9ac08f2dc24..fd1f2e0a20e 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/GridLayoutManagerActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/GridLayoutManagerActivity.java
@@ -122,7 +122,7 @@ public class GridLayoutManagerActivity extends BaseLayoutManagerActivity<GridLay
vh.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- final int pos = vh.getAdapterPosition();
+ final int pos = vh.getBindingAdapterPosition();
if (pos == RecyclerView.NO_POSITION) {
return;
}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/NestedRecyclerViewActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/NestedRecyclerViewActivity.java
index 106c9d3ea50..366028ac230 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/NestedRecyclerViewActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/NestedRecyclerViewActivity.java
@@ -170,7 +170,7 @@ public class NestedRecyclerViewActivity extends BaseLayoutManagerActivity<Linear
@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
- mSavedStates.set(holder.getAdapterPosition(),
+ mSavedStates.set(holder.getBindingAdapterPosition(),
holder.mRecyclerView.getLayoutManager().onSaveInstanceState());
}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/RecyclerViewActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/RecyclerViewActivity.java
index 220049dd4ae..a80ea810e7d 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/RecyclerViewActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/RecyclerViewActivity.java
@@ -56,7 +56,7 @@ public class RecyclerViewActivity extends Activity {
vh.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- final int pos = vh.getAdapterPosition();
+ final int pos = vh.getBindingAdapterPosition();
if (pos == RecyclerView.NO_POSITION) {
return;
}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/StableIdActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/StableIdActivity.java
index f4990477180..7d88692293e 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/StableIdActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/StableIdActivity.java
@@ -115,7 +115,7 @@ public class StableIdActivity extends BaseLayoutManagerActivity<LinearLayoutMana
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- final int pos = viewHolder.getAdapterPosition();
+ final int pos = viewHolder.getBindingAdapterPosition();
if (pos != RecyclerView.NO_POSITION) {
// swap item to top, and notify data set changed
Pair<Integer, String> d = mData.remove(pos);
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyItemHolder.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyItemHolder.java
index c7761b230a3..944e200acc3 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyItemHolder.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyItemHolder.java
@@ -45,7 +45,7 @@ final class FancyItemHolder extends FancyHolder {
mDetails = new ItemDetails<Uri>() {
@Override
public int getPosition() {
- return FancyItemHolder.this.getAdapterPosition();
+ return FancyItemHolder.this.getBindingAdapterPosition();
}
@Override
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/DemoHolder.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/DemoHolder.java
index 975990591f4..9edbdd8bba0 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/DemoHolder.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/DemoHolder.java
@@ -41,7 +41,7 @@ final class DemoHolder extends RecyclerView.ViewHolder {
mDetails = new ItemDetails<Long>() {
@Override
public int getPosition() {
- return DemoHolder.this.getAdapterPosition();
+ return DemoHolder.this.getBindingAdapterPosition();
}
@Override
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/touch/ItemTouchHelperActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/touch/ItemTouchHelperActivity.java
index 1730c9ca093..5a2cd9e92db 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/touch/ItemTouchHelperActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/touch/ItemTouchHelperActivity.java
@@ -115,7 +115,8 @@ abstract public class ItemTouchHelperActivity extends Activity {
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target) {
- mAdapter.move(viewHolder.getAdapterPosition(), target.getAdapterPosition());
+ mAdapter.move(viewHolder.getBindingAdapterPosition(),
+ target.getBindingAdapterPosition());
return true;
}
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/PagedListViewActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/PagedListViewActivity.java
index a4a8ae88129..f38262c6ae6 100644
--- a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/PagedListViewActivity.java
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/PagedListViewActivity.java
@@ -133,7 +133,7 @@ public class PagedListViewActivity extends Activity {
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
- mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
+ mAdapter.onItemDismiss(viewHolder.getBindingAdapterPosition());
}
}
}
diff --git a/samples/SupportTransitionDemos/src/main/java/com/example/android/support/transition/widget/RecyclerViewUsage.java b/samples/SupportTransitionDemos/src/main/java/com/example/android/support/transition/widget/RecyclerViewUsage.java
index 2bbcddcde21..4f0b18f73fc 100644
--- a/samples/SupportTransitionDemos/src/main/java/com/example/android/support/transition/widget/RecyclerViewUsage.java
+++ b/samples/SupportTransitionDemos/src/main/java/com/example/android/support/transition/widget/RecyclerViewUsage.java
@@ -68,7 +68,7 @@ public class RecyclerViewUsage extends TransitionUsageBase {
@Override
public void onBindViewHolder(@NonNull TransitionHolder holder, int position) {
holder.itemView.setOnClickListener(v -> {
- final int clickedPosition = holder.getAdapterPosition();
+ final int clickedPosition = holder.getBindingAdapterPosition();
if (clickedPosition == RecyclerView.NO_POSITION) return;
TransitionManager.beginDelayedTransition(mRecyclerView,
diff --git a/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/ParallelNestedScrollingActivity.kt b/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/ParallelNestedScrollingActivity.kt
index c20b77dd576..bdd594ddd9b 100644
--- a/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/ParallelNestedScrollingActivity.kt
+++ b/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/ParallelNestedScrollingActivity.kt
@@ -60,7 +60,8 @@ class ParallelNestedScrollingActivity : Activity() {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
with(holder) {
- title.text = title.context.getString(R.string.page_position, adapterPosition)
+ title.text =
+ title.context.getString(R.string.page_position, absoluteAdapterPosition)
itemView.setBackgroundResource(PAGE_COLORS[position % PAGE_COLORS.size])
}
}
@@ -99,7 +100,8 @@ class ParallelNestedScrollingActivity : Activity() {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
with(holder) {
- tv.text = tv.context.getString(R.string.item_position, adapterPosition)
+ tv.text =
+ tv.context.getString(R.string.item_position, absoluteAdapterPosition)
tv.setBackgroundResource(CELL_COLORS[position % CELL_COLORS.size])
}
}