diff options
-rw-r--r-- | core/java/android/view/View.java | 47 | ||||
-rw-r--r-- | core/java/android/view/ViewGroup.java | 57 | ||||
-rw-r--r-- | core/java/android/view/ViewParent.java | 10 | ||||
-rw-r--r-- | core/java/android/view/ViewRootImpl.java | 4 | ||||
-rw-r--r-- | core/java/android/widget/AbsListView.java | 119 | ||||
-rw-r--r-- | core/java/android/widget/EdgeEffect.java | 2 | ||||
-rw-r--r-- | core/java/android/widget/GridView.java | 1 | ||||
-rw-r--r-- | core/java/android/widget/ListView.java | 1 |
8 files changed, 229 insertions, 12 deletions
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 32f5732..c16eb31 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1759,6 +1759,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal static final int LAYOUT_DIRECTION_RESOLVED = 0x00000008; + /** + * Indicates that the view is tracking some sort of transient state + * that the app should not need to be aware of, but that the framework + * should take special care to preserve. + * + * @hide + */ + static final int HAS_TRANSIENT_STATE = 0x00000010; + + /* End of masks for mPrivateFlags2 */ static final int DRAG_MASK = DRAG_CAN_ACCEPT | DRAG_HOVERED; @@ -4889,6 +4899,43 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Indicates whether the view is currently tracking transient state that the + * app should not need to concern itself with saving and restoring, but that + * the framework should take special note to preserve when possible. + * + * @return true if the view has transient state + * + * @hide + */ + @ViewDebug.ExportedProperty(category = "layout") + public boolean hasTransientState() { + return (mPrivateFlags2 & HAS_TRANSIENT_STATE) == HAS_TRANSIENT_STATE; + } + + /** + * Set whether this view is currently tracking transient state that the + * framework should attempt to preserve when possible. + * + * @param hasTransientState true if this view has transient state + * + * @hide + */ + public void setHasTransientState(boolean hasTransientState) { + if (hasTransientState() == hasTransientState) return; + + mPrivateFlags2 = (mPrivateFlags2 & ~HAS_TRANSIENT_STATE) | + (hasTransientState ? HAS_TRANSIENT_STATE : 0); + if (mParent != null) { + try { + mParent.childHasTransientStateChanged(this, hasTransientState); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } + } + + /** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index f2935e8..ac46d4e 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -77,6 +77,7 @@ import java.util.HashSet; * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges */ public abstract class ViewGroup extends View implements ViewParent, ViewManager { + private static final String TAG = "ViewGroup"; private static final boolean DBG = false; @@ -375,6 +376,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @ViewDebug.ExportedProperty(category = "drawing") boolean mDrawLayers = true; + // Indicates how many of this container's child subtrees contain transient state + @ViewDebug.ExportedProperty(category = "layout") + private int mChildCountWithTransientState = 0; + public ViewGroup(Context context) { super(context); initViewGroup(); @@ -653,6 +658,38 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * Called when a child view has changed whether or not it is tracking transient state. + * + * @hide + */ + public void childHasTransientStateChanged(View child, boolean childHasTransientState) { + final boolean oldHasTransientState = hasTransientState(); + if (childHasTransientState) { + mChildCountWithTransientState++; + } else { + mChildCountWithTransientState--; + } + + final boolean newHasTransientState = hasTransientState(); + if (mParent != null && oldHasTransientState != newHasTransientState) { + try { + mParent.childHasTransientStateChanged(this, newHasTransientState); + } catch (AbstractMethodError e) { + Log.e(TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } + } + + /** + * @hide + */ + @Override + public boolean hasTransientState() { + return mChildCountWithTransientState > 0 || super.hasTransientState(); + } + + /** * {@inheritDoc} */ @Override @@ -3099,6 +3136,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) { mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE; } + + if (child.hasTransientState()) { + childHasTransientStateChanged(child, true); + } } private void addInArray(View child, int index) { @@ -3295,6 +3336,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager view.dispatchDetachedFromWindow(); } + if (view.hasTransientState()) { + childHasTransientStateChanged(view, false); + } + onViewRemoved(view); needGlobalAttributesUpdate(false); @@ -3366,6 +3411,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager view.dispatchDetachedFromWindow(); } + if (view.hasTransientState()) { + childHasTransientStateChanged(view, false); + } + needGlobalAttributesUpdate(false); onViewRemoved(view); @@ -3431,6 +3480,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager view.dispatchDetachedFromWindow(); } + if (view.hasTransientState()) { + childHasTransientStateChanged(view, false); + } + onViewRemoved(view); view.mParent = null; @@ -3471,6 +3524,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager child.dispatchDetachedFromWindow(); } + if (child.hasTransientState()) { + childHasTransientStateChanged(child, false); + } + onViewRemoved(child); } diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index 873d4bb..8395f1b 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -261,4 +261,14 @@ public interface ViewParent { * @return True if the event was sent. */ public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event); + + /** + * Called when a child view now has or no longer is tracking transient state. + * + * @param child Child view whose state has changed + * @param hasTransientState true if this child has transient state + * + * @hide + */ + public void childHasTransientStateChanged(View child, boolean hasTransientState); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index d41d168..84ce2a4 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4144,6 +4144,10 @@ public final class ViewRootImpl implements ViewParent, return scrollToRectOrFocus(rectangle, immediate); } + public void childHasTransientStateChanged(View child, boolean hasTransientState) { + // Do nothing. + } + class TakenSurfaceHolder extends BaseSurfaceHolder { @Override public boolean onAllowLockCanvas() { diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index e94b1cb..e7bc1e1 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -36,6 +36,7 @@ import android.text.TextWatcher; import android.util.AttributeSet; import android.util.Log; import android.util.LongSparseArray; +import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.StateSet; import android.view.ActionMode; @@ -87,6 +88,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te ViewTreeObserver.OnTouchModeChangeListener, RemoteViewsAdapter.RemoteAdapterConnectionCallback { + private static final String TAG = "AbsListView"; + /** * Disables the transcript mode. * @@ -263,6 +266,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private RemoteViewsAdapter mRemoteAdapter; /** + * If mAdapter != null, whenever this is true the adapter has stable IDs. + */ + boolean mAdapterHasStableIds; + + /** * This flag indicates the a full notify is required when the RemoteViewsAdapter connects */ private boolean mDeferNotifyDataSetChanged = false; @@ -812,7 +820,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te @Override public void setAdapter(ListAdapter adapter) { if (adapter != null) { - if (mChoiceMode != CHOICE_MODE_NONE && mAdapter.hasStableIds() && + mAdapterHasStableIds = mAdapter.hasStableIds(); + if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds && mCheckedIdStates == null) { mCheckedIdStates = new LongSparseArray<Integer>(); } @@ -2011,6 +2020,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te isScrap[0] = false; View scrapView; + scrapView = mRecycler.getTransientStateView(position); + if (scrapView != null) { + return scrapView; + } + scrapView = mRecycler.getScrapView(position); View child; @@ -2021,6 +2035,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } child = mAdapter.getView(position, scrapView, this); + if (mAdapterHasStableIds) { + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp == null) { + lp = (LayoutParams) generateDefaultLayoutParams(); + } + lp.itemId = mAdapter.getItemId(position); + } if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW, @@ -4543,7 +4564,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (count > 0) { detachViewsFromParent(start, count); + mRecycler.removeSkippedScrap(); } + offsetChildrenTopAndBottom(incrementalDeltaY); if (down) { @@ -4853,6 +4876,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te confirmCheckedPositionsById(); } + // TODO: In the future we can recycle these views based on stable ID instead. + mRecycler.clearTransientStateViews(); + if (count > 0) { int newPos; int selectablePos; @@ -5735,6 +5761,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te */ int scrappedFromPosition; + /** + * The ID the view represents + */ + long itemId = -1; + public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); } @@ -5807,6 +5838,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private ArrayList<View> mCurrentScrap; + private ArrayList<View> mSkippedScrap; + + private SparseArray<View> mTransientStateViews; + public void setViewTypeCount(int viewTypeCount) { if (viewTypeCount < 1) { throw new IllegalArgumentException("Can't have a viewTypeCount < 1"); @@ -5838,6 +5873,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } } + if (mTransientStateViews != null) { + final int count = mTransientStateViews.size(); + for (int i = 0; i < count; i++) { + mTransientStateViews.valueAt(i).forceLayout(); + } + } } public boolean shouldRecycleViewType(int viewType) { @@ -5864,6 +5905,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } } + if (mTransientStateViews != null) { + mTransientStateViews.clear(); + } } /** @@ -5910,6 +5954,28 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return null; } + View getTransientStateView(int position) { + if (mTransientStateViews == null) { + return null; + } + final int index = mTransientStateViews.indexOfKey(position); + if (index < 0) { + return null; + } + final View result = mTransientStateViews.valueAt(index); + mTransientStateViews.removeAt(index); + return result; + } + + /** + * Dump any currently saved views with transient state. + */ + void clearTransientStateViews() { + if (mTransientStateViews != null) { + mTransientStateViews.clear(); + } + } + /** * @return A view from the ScrapViews collection. These are unordered. */ @@ -5926,7 +5992,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** - * Put a view into the ScapViews list. These views are unordered. + * Put a view into the ScrapViews list. These views are unordered. * * @param scrap The view to add */ @@ -5936,23 +6002,32 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return; } + lp.scrappedFromPosition = position; + // Don't put header or footer views or views that should be ignored // into the scrap heap int viewType = lp.viewType; - if (!shouldRecycleViewType(viewType)) { - if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { - removeDetachedView(scrap, false); + final boolean scrapHasTransientState = scrap.hasTransientState(); + if (!shouldRecycleViewType(viewType) || scrapHasTransientState) { + if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER || scrapHasTransientState) { + if (mSkippedScrap == null) { + mSkippedScrap = new ArrayList<View>(); + } + mSkippedScrap.add(scrap); + } + if (scrapHasTransientState) { + if (mTransientStateViews == null) { + mTransientStateViews = new SparseArray<View>(); + } + mTransientStateViews.put(position, scrap); } return; } - lp.scrappedFromPosition = position; - + scrap.dispatchStartTemporaryDetach(); if (mViewTypeCount == 1) { - scrap.dispatchStartTemporaryDetach(); mCurrentScrap.add(scrap); } else { - scrap.dispatchStartTemporaryDetach(); mScrapViews[viewType].add(scrap); } @@ -5962,6 +6037,20 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** + * Finish the removal of any views that skipped the scrap heap. + */ + void removeSkippedScrap() { + if (mSkippedScrap == null) { + return; + } + final int count = mSkippedScrap.size(); + for (int i = 0; i < count; i++) { + removeDetachedView(mSkippedScrap.get(i), false); + } + mSkippedScrap.clear(); + } + + /** * Move all views remaining in mActiveViews to mScrapViews. */ void scrapActiveViews() { @@ -5980,11 +6069,19 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te activeViews[i] = null; - if (!shouldRecycleViewType(whichScrap)) { + final boolean scrapHasTransientState = victim.hasTransientState(); + if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) { // Do not move views that should be ignored - if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { + if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER || + scrapHasTransientState) { removeDetachedView(victim, false); } + if (scrapHasTransientState) { + if (mTransientStateViews == null) { + mTransientStateViews = new SparseArray<View>(); + } + mTransientStateViews.put(mFirstActivePosition + i, victim); + } continue; } diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index 326587e..83aa8ba 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -56,7 +56,7 @@ public class EdgeEffect { // Time it will take in ms for a pulled glow to decay to partial strength before release private static final int PULL_DECAY_TIME = 1000; - private static final float MAX_ALPHA = 0.8f; + private static final float MAX_ALPHA = 1.f; private static final float HELD_EDGE_ALPHA = 0.7f; private static final float HELD_EDGE_SCALE_Y = 0.5f; private static final float HELD_GLOW_ALPHA = 0.5f; diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index be2df8e..6bc5a15 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -1205,6 +1205,7 @@ public class GridView extends AbsListView { // Clear out old views //removeAllViewsInLayout(); detachAllViewsFromParent(); + recycleBin.removeSkippedScrap(); switch (mLayoutMode) { case LAYOUT_SET_SELECTION: diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 67fd059..46c2c07 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -1591,6 +1591,7 @@ public class ListView extends AbsListView { // Clear out old views detachAllViewsFromParent(); + recycleBin.removeSkippedScrap(); switch (mLayoutMode) { case LAYOUT_SET_SELECTION: |