/* * 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.StateRestorationPolicy.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 ConcatAdapter}. */ class NestedAdapterWrapper { @NonNull private final ViewTypeStorage.ViewTypeLookup mViewTypeLookup; @NonNull private final StableIdStorage.StableIdLookup mStableIdLookup; public final Adapter 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.getStateRestorationPolicy() == PREVENT_WHEN_EMPTY) { mCallback.onStateRestorationPolicyChanged(NestedAdapterWrapper.this); } } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { mCachedItemCount -= itemCount; mCallback.onItemRangeRemoved( NestedAdapterWrapper.this, positionStart, itemCount ); if (mCachedItemCount < 1 && adapter.getStateRestorationPolicy() == PREVENT_WHEN_EMPTY) { mCallback.onStateRestorationPolicyChanged(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 onStateRestorationPolicyChanged() { mCallback.onStateRestorationPolicyChanged( NestedAdapterWrapper.this ); } }; NestedAdapterWrapper( Adapter adapter, final Callback callback, ViewTypeStorage viewTypeStorage, StableIdStorage.StableIdLookup stableIdLookup) { this.adapter = adapter; mCallback = callback; mViewTypeLookup = viewTypeStorage.createViewTypeWrapper(this); mStableIdLookup = stableIdLookup; 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); } public long getItemId(int localPosition) { long localItemId = adapter.getItemId(localPosition); return mStableIdLookup.localToGlobal(localItemId); } 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 onStateRestorationPolicyChanged(NestedAdapterWrapper nestedAdapterWrapper); } }