1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package androidx.leanback.widget; 15 16 import static androidx.leanback.widget.BaseGridView.SAVE_ALL_CHILD; 17 import static androidx.leanback.widget.BaseGridView.SAVE_LIMITED_CHILD; 18 import static androidx.leanback.widget.BaseGridView.SAVE_NO_CHILD; 19 import static androidx.leanback.widget.BaseGridView.SAVE_ON_SCREEN_CHILD; 20 21 import android.os.Bundle; 22 import android.os.Parcelable; 23 import android.util.SparseArray; 24 import android.view.View; 25 26 import androidx.collection.LruCache; 27 28 import java.util.Iterator; 29 import java.util.Map; 30 import java.util.Map.Entry; 31 32 /** 33 * Maintains a bundle of states for a group of views. Each view must have a unique id to identify 34 * it. There are four different strategies {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD} 35 * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}. 36 * <p> 37 * This class serves purpose of nested "listview" e.g. a vertical list of horizontal list. 38 * Vertical list maintains id->bundle mapping of all its children (even the children is offscreen 39 * and being pruned). 40 * <p> 41 * The class is currently used within {@link GridLayoutManager}, but it might be used by other 42 * ViewGroup. 43 */ 44 class ViewsStateBundle { 45 46 public static final int LIMIT_DEFAULT = 100; 47 public static final int UNLIMITED = Integer.MAX_VALUE; 48 49 private int mSavePolicy; 50 private int mLimitNumber; 51 52 private LruCache<String, SparseArray<Parcelable>> mChildStates; 53 ViewsStateBundle()54 public ViewsStateBundle() { 55 mSavePolicy = SAVE_NO_CHILD; 56 mLimitNumber = LIMIT_DEFAULT; 57 } 58 clear()59 public void clear() { 60 if (mChildStates != null) { 61 mChildStates.evictAll(); 62 } 63 } 64 remove(int id)65 public void remove(int id) { 66 if (mChildStates != null && mChildStates.size() != 0) { 67 mChildStates.remove(getSaveStatesKey(id)); 68 } 69 } 70 71 /** 72 * @return the saved views states 73 */ saveAsBundle()74 public final Bundle saveAsBundle() { 75 if (mChildStates == null || mChildStates.size() == 0) { 76 return null; 77 } 78 Map<String, SparseArray<Parcelable>> snapshot = mChildStates.snapshot(); 79 Bundle bundle = new Bundle(); 80 for (Iterator<Entry<String, SparseArray<Parcelable>>> i = 81 snapshot.entrySet().iterator(); i.hasNext(); ) { 82 Entry<String, SparseArray<Parcelable>> e = i.next(); 83 bundle.putSparseParcelableArray(e.getKey(), e.getValue()); 84 } 85 return bundle; 86 } 87 loadFromBundle(Bundle savedBundle)88 public final void loadFromBundle(Bundle savedBundle) { 89 if (mChildStates != null && savedBundle != null) { 90 mChildStates.evictAll(); 91 for (Iterator<String> i = savedBundle.keySet().iterator(); i.hasNext(); ) { 92 String key = i.next(); 93 mChildStates.put(key, savedBundle.getSparseParcelableArray(key)); 94 } 95 } 96 } 97 98 /** 99 * @return the savePolicy, see {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD} 100 * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD} 101 */ getSavePolicy()102 public final int getSavePolicy() { 103 return mSavePolicy; 104 } 105 106 /** 107 * @return the limitNumber, only works when {@link #getSavePolicy()} is 108 * {@link #SAVE_LIMITED_CHILD} 109 */ getLimitNumber()110 public final int getLimitNumber() { 111 return mLimitNumber; 112 } 113 114 /** 115 * @see ViewsStateBundle#getSavePolicy() 116 */ setSavePolicy(int savePolicy)117 public final void setSavePolicy(int savePolicy) { 118 this.mSavePolicy = savePolicy; 119 applyPolicyChanges(); 120 } 121 122 /** 123 * @see ViewsStateBundle#getLimitNumber() 124 */ setLimitNumber(int limitNumber)125 public final void setLimitNumber(int limitNumber) { 126 this.mLimitNumber = limitNumber; 127 applyPolicyChanges(); 128 } 129 applyPolicyChanges()130 protected void applyPolicyChanges() { 131 if (mSavePolicy == SAVE_LIMITED_CHILD) { 132 if (mLimitNumber <= 0) { 133 throw new IllegalArgumentException(); 134 } 135 if (mChildStates == null || mChildStates.maxSize() != mLimitNumber) { 136 mChildStates = new LruCache<String, SparseArray<Parcelable>>(mLimitNumber); 137 } 138 } else if (mSavePolicy == SAVE_ALL_CHILD || mSavePolicy == SAVE_ON_SCREEN_CHILD) { 139 if (mChildStates == null || mChildStates.maxSize() != UNLIMITED) { 140 mChildStates = new LruCache<String, SparseArray<Parcelable>>(UNLIMITED); 141 } 142 } else { 143 mChildStates = null; 144 } 145 } 146 147 /** 148 * Load view from states, it's none operation if the there is no state associated with the id. 149 * 150 * @param view view where loads into 151 * @param id unique id for the view within this ViewsStateBundle 152 */ loadView(View view, int id)153 public final void loadView(View view, int id) { 154 if (mChildStates != null) { 155 String key = getSaveStatesKey(id); 156 // Once loaded the state, do not keep the state of child. The child state will 157 // be saved again either when child is offscreen or when the parent is saved. 158 SparseArray<Parcelable> container = mChildStates.remove(key); 159 if (container != null) { 160 view.restoreHierarchyState(container); 161 } 162 } 163 } 164 165 /** 166 * Save views regardless what's the current policy is. 167 * 168 * @param view view to save 169 * @param id unique id for the view within this ViewsStateBundle 170 */ saveViewUnchecked(View view, int id)171 protected final void saveViewUnchecked(View view, int id) { 172 if (mChildStates != null) { 173 String key = getSaveStatesKey(id); 174 SparseArray<Parcelable> container = new SparseArray<Parcelable>(); 175 view.saveHierarchyState(container); 176 mChildStates.put(key, container); 177 } 178 } 179 180 /** 181 * The on screen view is saved when policy is not {@link #SAVE_NO_CHILD}. 182 * 183 * @param bundle Bundle where we save the on screen view state. If null, 184 * a new Bundle is created and returned. 185 * @param view The view to save. 186 * @param id Id of the view. 187 */ saveOnScreenView(Bundle bundle, View view, int id)188 public final Bundle saveOnScreenView(Bundle bundle, View view, int id) { 189 if (mSavePolicy != SAVE_NO_CHILD) { 190 String key = getSaveStatesKey(id); 191 SparseArray<Parcelable> container = new SparseArray<Parcelable>(); 192 view.saveHierarchyState(container); 193 if (bundle == null) { 194 bundle = new Bundle(); 195 } 196 bundle.putSparseParcelableArray(key, container); 197 } 198 return bundle; 199 } 200 201 /** 202 * Save off screen views according to policy. 203 * 204 * @param view view to save 205 * @param id unique id for the view within this ViewsStateBundle 206 */ saveOffscreenView(View view, int id)207 public final void saveOffscreenView(View view, int id) { 208 switch (mSavePolicy) { 209 case SAVE_LIMITED_CHILD: 210 case SAVE_ALL_CHILD: 211 saveViewUnchecked(view, id); 212 break; 213 case SAVE_ON_SCREEN_CHILD: 214 remove(id); 215 break; 216 default: 217 break; 218 } 219 } 220 getSaveStatesKey(int id)221 static String getSaveStatesKey(int id) { 222 return Integer.toString(id); 223 } 224 } 225