1/* 2 * Copyright 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package androidx.recyclerview.selection; 18 19import android.util.SparseArray; 20import android.view.View; 21 22import androidx.annotation.NonNull; 23import androidx.annotation.Nullable; 24import androidx.recyclerview.widget.RecyclerView; 25import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener; 26 27import java.util.HashMap; 28import java.util.Map; 29 30/** 31 * An {@link ItemKeyProvider} that provides stable ids by way of cached 32 * {@link RecyclerView.Adapter} stable ids. Items enter the cache as they are laid out by 33 * RecyclerView, and are removed from the cache as they are recycled. 34 * 35 * <p> 36 * There are trade-offs with this implementation as it necessarily auto-boxes {@code long} 37 * stable id values into {@code Long} values for use as selection keys. The core Selection API 38 * uses a parameterized key type to permit other keys (such as Strings or URIs). 39 */ 40public final class StableIdKeyProvider extends ItemKeyProvider<Long> { 41 42 private final SparseArray<Long> mPositionToKey = new SparseArray<>(); 43 private final Map<Long, Integer> mKeyToPosition = new HashMap<Long, Integer>(); 44 private final RecyclerView mRecyclerView; 45 46 /** 47 * Creates a new key provider that uses cached {@code long} stable ids associated 48 * with the RecyclerView items. 49 * 50 * @param recyclerView the owner RecyclerView 51 */ 52 public StableIdKeyProvider(@NonNull RecyclerView recyclerView) { 53 54 // Since this provide is based on stable ids based on whats laid out in the window 55 // we can only satisfy "window" scope key access. 56 super(SCOPE_CACHED); 57 58 mRecyclerView = recyclerView; 59 60 mRecyclerView.addOnChildAttachStateChangeListener( 61 new OnChildAttachStateChangeListener() { 62 @Override 63 public void onChildViewAttachedToWindow(View view) { 64 onAttached(view); 65 } 66 67 @Override 68 public void onChildViewDetachedFromWindow(View view) { 69 onDetached(view); 70 } 71 } 72 ); 73 74 } 75 76 private void onAttached(@NonNull View view) { 77 RecyclerView.ViewHolder holder = mRecyclerView.findContainingViewHolder(view); 78 int position = holder.getAdapterPosition(); 79 long id = holder.getItemId(); 80 if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) { 81 mPositionToKey.put(position, id); 82 mKeyToPosition.put(id, position); 83 } 84 } 85 86 private void onDetached(@NonNull View view) { 87 RecyclerView.ViewHolder holder = mRecyclerView.findContainingViewHolder(view); 88 int position = holder.getAdapterPosition(); 89 long id = holder.getItemId(); 90 if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) { 91 mPositionToKey.delete(position); 92 mKeyToPosition.remove(id); 93 } 94 } 95 96 @Override 97 public @Nullable Long getKey(int position) { 98 return mPositionToKey.get(position, null); 99 } 100 101 @Override 102 public int getPosition(@NonNull Long key) { 103 if (mKeyToPosition.containsKey(key)) { 104 return mKeyToPosition.get(key); 105 } 106 return RecyclerView.NO_POSITION; 107 } 108} 109