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