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 */ 14package android.support.v17.leanback.widget; 15 16import android.os.Bundle; 17import android.os.Parcelable; 18import android.support.v4.util.LruCache; 19import android.util.SparseArray; 20import android.view.View; 21 22import java.util.Iterator; 23import java.util.Map; 24import java.util.Map.Entry; 25 26import static android.support.v17.leanback.widget.BaseGridView.SAVE_NO_CHILD; 27import static android.support.v17.leanback.widget.BaseGridView.SAVE_ON_SCREEN_CHILD; 28import static android.support.v17.leanback.widget.BaseGridView.SAVE_LIMITED_CHILD; 29import 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 */ 43class 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 53 public ViewsStateBundle() { 54 mSavePolicy = SAVE_NO_CHILD; 55 mLimitNumber = LIMIT_DEFAULT; 56 } 57 58 public void clear() { 59 if (mChildStates != null) { 60 mChildStates.evictAll(); 61 } 62 } 63 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 */ 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 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 */ 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 */ 109 public final int getLimitNumber() { 110 return mLimitNumber; 111 } 112 113 /** 114 * @see ViewsStateBundle#getSavePolicy() 115 */ 116 public final void setSavePolicy(int savePolicy) { 117 this.mSavePolicy = savePolicy; 118 applyPolicyChanges(); 119 } 120 121 /** 122 * @see ViewsStateBundle#getLimitNumber() 123 */ 124 public final void setLimitNumber(int limitNumber) { 125 this.mLimitNumber = limitNumber; 126 applyPolicyChanges(); 127 } 128 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 */ 152 public final void loadView(View view, int id) { 153 if (mChildStates != null) { 154 String key = getSaveStatesKey(id); 155 SparseArray<Parcelable> container = mChildStates.get(key); 156 if (container != null) { 157 view.restoreHierarchyState(container); 158 } 159 } 160 } 161 162 /** 163 * Save views regardless what's the current policy is. 164 * 165 * @param view view to save 166 * @param id unique id for the view within this ViewsStateBundle 167 */ 168 protected final void saveViewUnchecked(View view, int id) { 169 if (mChildStates != null) { 170 String key = getSaveStatesKey(id); 171 SparseArray<Parcelable> container = new SparseArray<Parcelable>(); 172 view.saveHierarchyState(container); 173 mChildStates.put(key, container); 174 } 175 } 176 177 /** 178 * The on screen view is saved when policy is not {@link #SAVE_NO_CHILD}. 179 * 180 * @param view 181 * @param id 182 */ 183 public final void saveOnScreenView(View view, int id) { 184 if (mSavePolicy != SAVE_NO_CHILD) { 185 saveViewUnchecked(view, id); 186 } 187 } 188 189 /** 190 * Save off screen views according to policy. 191 * 192 * @param view view to save 193 * @param id unique id for the view within this ViewsStateBundle 194 */ 195 public final void saveOffscreenView(View view, int id) { 196 switch (mSavePolicy) { 197 case SAVE_LIMITED_CHILD: 198 case SAVE_ALL_CHILD: 199 saveViewUnchecked(view, id); 200 break; 201 case SAVE_ON_SCREEN_CHILD: 202 remove(id); 203 break; 204 default: 205 break; 206 } 207 } 208 209 static String getSaveStatesKey(int id) { 210 return Integer.toString(id); 211 } 212} 213