From b742b87a6b098ff5c354e421c0a89fb32346c244 Mon Sep 17 00:00:00 2001 From: Yigit Boyar Date: Fri, 6 May 2016 16:11:12 -0700 Subject: [PATCH 1/2] Remove detached headers ListView was leaving header and footer views in the detached state, which is a problem because no view should be left in that state after a layout calculation. This was also causing the view to never received detached-from-window callback if the ListView is detached while the header is not visible. This CL fixes an issue by traversing fixed views and removing the ones that match the criteria. To keep the behavior as similar as possible, we still do not call startTmpDispatch on fixed views unless we'll remove them. Bug: 24490656 Change-Id: I8acfbd1a9d9b8b24c1c4b7692ef82cfe0f4d43a8 --- core/java/android/widget/ListView.java | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index bb1ffcb2d5c6a..781f733cd7b4e 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -54,6 +54,7 @@ import android.view.accessibility.AccessibilityNodeProvider; import android.widget.RemoteViews.RemoteView; import java.util.ArrayList; +import java.util.List; /* * Implementation Notes: @@ -1763,6 +1764,10 @@ public class ListView extends AbsListView { // Flush any cached views that did not get reused above recycleBin.scrapActiveViews(); + // remove any header/footer that has been temp detached and not re-attached + removeUnusedFixedViews(mHeaderViewInfos); + removeUnusedFixedViews(mFooterViewInfos); + if (sel != null) { // The current selected item should get focus if items are // focusable. @@ -1879,6 +1884,36 @@ public class ListView extends AbsListView { } } + @Override + boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { + final boolean result = super.trackMotionScroll(deltaY, incrementalDeltaY); + removeUnusedFixedViews(mHeaderViewInfos); + removeUnusedFixedViews(mFooterViewInfos); + return result; + } + + /** + * Header and Footer views are not scrapped / recycled like other views but they are still + * detached from the ViewGroup. After a layout operation, call this method to remove such views. + * + * @param infoList The info list to be traversed + */ + private void removeUnusedFixedViews(@Nullable List infoList) { + if (infoList == null) { + return; + } + for (int i = infoList.size() - 1; i >= 0; i--) { + final FixedViewInfo fixedViewInfo = infoList.get(i); + final View view = fixedViewInfo.view; + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); + if (view.getParent() == null && lp != null && lp.recycledHeaderFooter) { + removeDetachedView(view, false); + lp.recycledHeaderFooter = false; + } + + } + } + /** * @param child a direct child of this list. * @return Whether child is a header or footer view. @@ -3179,6 +3214,8 @@ public class ListView extends AbsListView { last = getChildAt(--lastIndex); } } + removeUnusedFixedViews(mHeaderViewInfos); + removeUnusedFixedViews(mFooterViewInfos); } private View addViewAbove(View theView, int position) { From 9afbf9ceb7ddad0e4aee28c6c6393800338af6f9 Mon Sep 17 00:00:00 2001 From: Yigit Boyar Date: Mon, 9 May 2016 16:42:37 -0700 Subject: [PATCH 2/2] Fully detach unused scrap views This CL fixes a bug in list view where an unused scrap view would stay in temporarily detached state until it is re-used. It is an invalid state for a view and to fix that issue, I've changed AbsListView to fully detached scrap views at the end of a layout pass and also treat them as regular new views once they are re-used after being fully detached. Bug: 28672259 Change-Id: I6e3f4da29f8cbca32787862402f5c21f674a7145 --- core/java/android/widget/AbsListView.java | 46 ++++++++++++++++++----- core/java/android/widget/ListView.java | 1 + 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 6e1dff9ea0def..28ade80a260e1 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -2322,7 +2322,8 @@ public abstract class AbsListView extends AdapterView implements Te * * @param position The position to display * @param isScrap Array of at least 1 boolean, the first entry will become true if - * the returned view was taken from the scrap heap, false if otherwise. + * the returned view was taken from the "temporary detached" scrap heap, false if + * otherwise. * * @return A view displaying the data associated with the specified position */ @@ -2362,10 +2363,18 @@ public abstract class AbsListView extends AdapterView implements Te // Failed to re-bind the data, return scrap to the heap. mRecycler.addScrapView(scrapView, position); } else { - isScrap[0] = true; + if (child.isTemporarilyDetached()) { + isScrap[0] = true; + + // Finish the temporary detach started in addScrapView(). + child.dispatchFinishTemporaryDetach(); + } else { + // we set isScrap to "true" only if the view is temporarily detached. + // if the view is fully detached, it is as good as a view created by the + // adapter + isScrap[0] = false; + } - // Finish the temporary detach started in addScrapView(). - child.dispatchFinishTemporaryDetach(); } } @@ -5152,6 +5161,7 @@ public abstract class AbsListView extends AdapterView implements Te fillGap(down); } + mRecycler.fullyDetachScrapViews(); if (!inTouchMode && mSelectedPosition != INVALID_POSITION) { final int childIndex = mSelectedPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { @@ -6861,8 +6871,8 @@ public abstract class AbsListView extends AdapterView implements Te scrapViews = mScrapViews[whichScrap]; } - victim.dispatchStartTemporaryDetach(); lp.scrappedFromPosition = mFirstActivePosition + i; + removeDetachedView(victim, false); scrapViews.add(victim); if (hasListener) { @@ -6871,10 +6881,28 @@ public abstract class AbsListView extends AdapterView implements Te } } } - pruneScrapViews(); } + /** + * At the end of a layout pass, all temp detached views should either be re-attached or + * completely detached. This method ensures that any remaining view in the scrap list is + * fully detached. + */ + void fullyDetachScrapViews() { + final int viewTypeCount = mViewTypeCount; + final ArrayList[] scrapViews = mScrapViews; + for (int i = 0; i < viewTypeCount; ++i) { + final ArrayList scrapPile = scrapViews[i]; + for (int j = scrapPile.size() - 1; j >= 0; j--) { + final View view = scrapPile.get(j); + if (view.isTemporarilyDetached()) { + removeDetachedView(view, false); + } + } + } + } + /** * Makes sure that the size of mScrapViews does not exceed the size of * mActiveViews, which can happen if an adapter does not recycle its @@ -6888,10 +6916,8 @@ public abstract class AbsListView extends AdapterView implements Te for (int i = 0; i < viewTypeCount; ++i) { final ArrayList scrapPile = scrapViews[i]; int size = scrapPile.size(); - final int extras = size - maxViews; - size--; - for (int j = 0; j < extras; j++) { - removeDetachedView(scrapPile.remove(size--), false); + while (size > maxViews) { + scrapPile.remove(--size); } } diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 781f733cd7b4e..0e04e3003d7c9 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -3214,6 +3214,7 @@ public class ListView extends AbsListView { last = getChildAt(--lastIndex); } } + recycleBin.fullyDetachScrapViews(); removeUnusedFixedViews(mHeaderViewInfos); removeUnusedFixedViews(mFooterViewInfos); }