/* * Copyright (C) 2014 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 android.support.v17.leanback.widget; import android.os.Bundle; import android.os.Parcelable; import android.support.v4.util.LruCache; import android.util.SparseArray; import android.view.View; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import static android.support.v17.leanback.widget.BaseGridView.SAVE_NO_CHILD; import static android.support.v17.leanback.widget.BaseGridView.SAVE_ON_SCREEN_CHILD; import static android.support.v17.leanback.widget.BaseGridView.SAVE_LIMITED_CHILD; import static android.support.v17.leanback.widget.BaseGridView.SAVE_ALL_CHILD; /** * Maintains a bundle of states for a group of views. Each view must have a unique id to identify * it. There are four different strategies {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD} * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}. *

* This class serves purpose of nested "listview" e.g. a vertical list of horizontal list. * Vertical list maintains id->bundle mapping of all it's children (even the children is offscreen * and being pruned). *

* The class is currently used within {@link GridLayoutManager}, but it might be used by other * ViewGroup. */ class ViewsStateBundle { public static final int LIMIT_DEFAULT = 100; public static final int UNLIMITED = Integer.MAX_VALUE; private int mSavePolicy; private int mLimitNumber; private LruCache> mChildStates; public ViewsStateBundle() { mSavePolicy = SAVE_NO_CHILD; mLimitNumber = LIMIT_DEFAULT; } public void clear() { if (mChildStates != null) { mChildStates.evictAll(); } } public void remove(int id) { if (mChildStates != null && mChildStates.size() != 0) { mChildStates.remove(getSaveStatesKey(id)); } } /** * @return the saved views states */ public final Bundle saveAsBundle() { if (mChildStates == null || mChildStates.size() == 0) { return null; } Map> snapshot = mChildStates.snapshot(); Bundle bundle = new Bundle(); for (Iterator>> i = snapshot.entrySet().iterator(); i.hasNext(); ) { Entry> e = i.next(); bundle.putSparseParcelableArray(e.getKey(), e.getValue()); } return bundle; } public final void loadFromBundle(Bundle savedBundle) { if (mChildStates != null && savedBundle != null) { mChildStates.evictAll(); for (Iterator i = savedBundle.keySet().iterator(); i.hasNext(); ) { String key = i.next(); mChildStates.put(key, savedBundle.getSparseParcelableArray(key)); } } } /** * @return the savePolicy, see {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD} * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD} */ public final int getSavePolicy() { return mSavePolicy; } /** * @return the limitNumber, only works when {@link #getSavePolicy()} is * {@link #SAVE_LIMITED_CHILD} */ public final int getLimitNumber() { return mLimitNumber; } /** * @see ViewsStateBundle#getSavePolicy() */ public final void setSavePolicy(int savePolicy) { this.mSavePolicy = savePolicy; applyPolicyChanges(); } /** * @see ViewsStateBundle#getLimitNumber() */ public final void setLimitNumber(int limitNumber) { this.mLimitNumber = limitNumber; applyPolicyChanges(); } protected void applyPolicyChanges() { if (mSavePolicy == SAVE_LIMITED_CHILD) { if (mLimitNumber <= 0) { throw new IllegalArgumentException(); } if (mChildStates == null || mChildStates.maxSize() != mLimitNumber) { mChildStates = new LruCache>(mLimitNumber); } } else if (mSavePolicy == SAVE_ALL_CHILD || mSavePolicy == SAVE_ON_SCREEN_CHILD) { if (mChildStates == null || mChildStates.maxSize() != UNLIMITED) { mChildStates = new LruCache>(UNLIMITED); } } else { mChildStates = null; } } /** * Load view from states, it's none operation if the there is no state associated with the id. * * @param view view where loads into * @param id unique id for the view within this ViewsStateBundle */ public final void loadView(View view, int id) { if (mChildStates != null) { String key = getSaveStatesKey(id); // Once loaded the state, do not keep the state of child. The child state will // be saved again either when child is offscreen or when the parent is saved. SparseArray container = mChildStates.remove(key); if (container != null) { view.restoreHierarchyState(container); } } } /** * Save views regardless what's the current policy is. * * @param view view to save * @param id unique id for the view within this ViewsStateBundle */ protected final void saveViewUnchecked(View view, int id) { if (mChildStates != null) { String key = getSaveStatesKey(id); SparseArray container = new SparseArray(); view.saveHierarchyState(container); mChildStates.put(key, container); } } /** * The on screen view is saved when policy is not {@link #SAVE_NO_CHILD}. * * @param view * @param id */ public final void saveOnScreenView(View view, int id) { if (mSavePolicy != SAVE_NO_CHILD) { saveViewUnchecked(view, id); } } /** * Save off screen views according to policy. * * @param view view to save * @param id unique id for the view within this ViewsStateBundle */ public final void saveOffscreenView(View view, int id) { switch (mSavePolicy) { case SAVE_LIMITED_CHILD: case SAVE_ALL_CHILD: saveViewUnchecked(view, id); break; case SAVE_ON_SCREEN_CHILD: remove(id); break; default: break; } } static String getSaveStatesKey(int id) { return Integer.toString(id); } }