2 * Copyright (C) 2013 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.photos.views;
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.database.DataSetObserver;
22 import android.graphics.Canvas;
23 import androidx.core.view.MotionEventCompat;
24 import androidx.core.view.VelocityTrackerCompat;
25 import androidx.core.view.ViewCompat;
26 import androidx.core.widget.EdgeEffectCompat;
27 import android.util.AttributeSet;
28 import android.util.Log;
29 import android.util.SparseArray;
30 import android.view.MotionEvent;
31 import android.view.VelocityTracker;
32 import android.view.View;
33 import android.view.ViewConfiguration;
34 import android.view.ViewGroup;
35 import android.widget.ListAdapter;
36 import android.widget.OverScroller;
38 import java.util.ArrayList;
40 public class GalleryThumbnailView extends ViewGroup {
42 public interface GalleryThumbnailAdapter extends ListAdapter {
44 * @param position Position to get the intrinsic aspect ratio for
45 * @return width / height
47 float getIntrinsicAspectRatio(int position);
50 private static final String TAG = "GalleryThumbnailView";
51 private static final float ASPECT_RATIO = (float) Math.sqrt(1.5f);
52 private static final int LAND_UNITS = 2;
53 private static final int PORT_UNITS = 3;
55 private GalleryThumbnailAdapter mAdapter;
57 private final RecycleBin mRecycler = new RecycleBin();
59 private final AdapterDataSetObserver mObserver = new AdapterDataSetObserver();
61 private boolean mDataChanged;
62 private int mOldItemCount;
63 private int mItemCount;
64 private boolean mHasStableIds;
66 private int mFirstPosition;
68 private boolean mPopulating;
69 private boolean mInLayout;
71 private int mTouchSlop;
72 private int mMaximumVelocity;
73 private int mFlingVelocity;
74 private float mLastTouchX;
75 private float mTouchRemainderX;
76 private int mActivePointerId;
78 private static final int TOUCH_MODE_IDLE = 0;
79 private static final int TOUCH_MODE_DRAGGING = 1;
80 private static final int TOUCH_MODE_FLINGING = 2;
82 private int mTouchMode;
83 private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
84 private final OverScroller mScroller;
86 private final EdgeEffectCompat mLeftEdge;
87 private final EdgeEffectCompat mRightEdge;
89 private int mLargeColumnWidth;
90 private int mSmallColumnWidth;
91 private int mLargeColumnUnitCount = 8;
92 private int mSmallColumnUnitCount = 10;
94 public GalleryThumbnailView(Context context) {
98 public GalleryThumbnailView(Context context, AttributeSet attrs) {
99 this(context, attrs, 0);
102 public GalleryThumbnailView(Context context, AttributeSet attrs, int defStyle) {
103 super(context, attrs, defStyle);
105 final ViewConfiguration vc = ViewConfiguration.get(context);
106 mTouchSlop = vc.getScaledTouchSlop();
107 mMaximumVelocity = vc.getScaledMaximumFlingVelocity();
108 mFlingVelocity = vc.getScaledMinimumFlingVelocity();
109 mScroller = new OverScroller(context);
111 mLeftEdge = new EdgeEffectCompat(context);
112 mRightEdge = new EdgeEffectCompat(context);
113 setWillNotDraw(false);
114 setClipToPadding(false);
118 public void requestLayout() {
120 super.requestLayout();
125 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
126 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
127 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
128 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
129 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
131 if (widthMode != MeasureSpec.EXACTLY) {
132 Log.e(TAG, "onMeasure: must have an exact width or match_parent! " +
133 "Using fallback spec of EXACTLY " + widthSize);
135 if (heightMode != MeasureSpec.EXACTLY) {
136 Log.e(TAG, "onMeasure: must have an exact height or match_parent! " +
137 "Using fallback spec of EXACTLY " + heightSize);
140 setMeasuredDimension(widthSize, heightSize);
142 float portSpaces = mLargeColumnUnitCount / PORT_UNITS;
143 float height = getMeasuredHeight() / portSpaces;
144 mLargeColumnWidth = (int) (height / ASPECT_RATIO);
146 height = getMeasuredHeight() / portSpaces;
147 mSmallColumnWidth = (int) (height / ASPECT_RATIO);
151 protected void onLayout(boolean changed, int l, int t, int r, int b) {
156 final int width = r - l;
157 final int height = b - t;
158 mLeftEdge.setSize(width, height);
159 mRightEdge.setSize(width, height);
162 private void populate() {
163 if (getWidth() == 0 || getHeight() == 0) {
167 // TODO: Handle size changing
168 // final int colCount = mColCount;
169 // if (mItemTops == null || mItemTops.length != colCount) {
170 // mItemTops = new int[colCount];
171 // mItemBottoms = new int[colCount];
172 // final int top = getPaddingTop();
173 // final int offset = top + Math.min(mRestoreOffset, 0);
174 // Arrays.fill(mItemTops, offset);
175 // Arrays.fill(mItemBottoms, offset);
176 // mLayoutRecords.clear();
178 // removeAllViewsInLayout();
182 // mRestoreOffset = 0;
186 layoutChildren(mDataChanged);
187 fillRight(mFirstPosition + getChildCount(), 0);
188 fillLeft(mFirstPosition - 1, 0);
190 mDataChanged = false;
193 final void layoutChildren(boolean queryAdapter) {
195 // final int childCount = getChildCount();
196 // for (int i = 0; i < childCount; i++) {
197 // View child = getChildAt(i);
199 // if (child.isLayoutRequested()) {
200 // final int widthSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(), MeasureSpec.EXACTLY);
201 // final int heightSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(), MeasureSpec.EXACTLY);
202 // child.measure(widthSpec, heightSpec);
203 // child.layout(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
206 // int childTop = mItemBottoms[col] > Integer.MIN_VALUE ?
207 // mItemBottoms[col] + mItemMargin : child.getTop();
209 // int lowest = childTop;
210 // for (int j = col + 1; j < col + span; j++) {
211 // final int bottom = mItemBottoms[j] + mItemMargin;
212 // if (bottom > lowest) {
216 // childTop = lowest;
218 // final int childHeight = child.getMeasuredHeight();
219 // final int childBottom = childTop + childHeight;
220 // final int childLeft = paddingLeft + col * (colWidth + itemMargin);
221 // final int childRight = childLeft + child.getMeasuredWidth();
222 // child.layout(childLeft, childTop, childRight, childBottom);
227 * Obtain the view and add it to our list of children. The view can be made
228 * fresh, converted from an unused view, or used as is if it was in the
231 * @param startPosition Logical position in the list to start from
232 * @param x Left or right edge of the view to add
233 * @param forward If true, align left edge to x and increase position.
234 * If false, align right edge to x and decrease position.
235 * @return Number of views added
237 private int makeAndAddColumn(int startPosition, int x, boolean forward) {
238 int columnWidth = mLargeColumnWidth;
240 for (int remaining = mLargeColumnUnitCount, i = 0;
241 remaining > 0 && startPosition + i >= 0 && startPosition + i < mItemCount;
242 i += forward ? 1 : -1, addViews++) {
243 if (mAdapter.getIntrinsicAspectRatio(startPosition + i) >= 1f) {
245 remaining -= LAND_UNITS;
248 remaining -= PORT_UNITS;
250 remaining += (mSmallColumnUnitCount - mLargeColumnUnitCount);
251 columnWidth = mSmallColumnWidth;
256 for (int i = 0; i < addViews; i++) {
257 int position = startPosition + (forward ? i : -i);
258 View child = obtainView(position, null);
259 if (child.getParent() != this) {
261 addViewInLayout(child, forward ? -1 : 0, child.getLayoutParams());
263 addView(child, forward ? -1 : 0);
266 int heightSize = (int) (.5f + (mAdapter.getIntrinsicAspectRatio(position) >= 1f
267 ? columnWidth / ASPECT_RATIO
268 : columnWidth * ASPECT_RATIO));
269 int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
270 int widthSpec = MeasureSpec.makeMeasureSpec(columnWidth, MeasureSpec.EXACTLY);
271 child.measure(widthSpec, heightSpec);
272 int childLeft = forward ? x : x - columnWidth;
273 child.layout(childLeft, nextTop, childLeft + columnWidth, nextTop + heightSize);
274 nextTop += heightSize;
280 public boolean onInterceptTouchEvent(MotionEvent ev) {
281 mVelocityTracker.addMovement(ev);
282 final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
284 case MotionEvent.ACTION_DOWN:
285 mVelocityTracker.clear();
286 mScroller.abortAnimation();
287 mLastTouchX = ev.getX();
288 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
289 mTouchRemainderX = 0;
290 if (mTouchMode == TOUCH_MODE_FLINGING) {
292 mTouchMode = TOUCH_MODE_DRAGGING;
297 case MotionEvent.ACTION_MOVE: {
298 final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
300 Log.e(TAG, "onInterceptTouchEvent could not find pointer with id " +
301 mActivePointerId + " - did StaggeredGridView receive an inconsistent " +
305 final float x = MotionEventCompat.getX(ev, index);
306 final float dx = x - mLastTouchX + mTouchRemainderX;
307 final int deltaY = (int) dx;
308 mTouchRemainderX = dx - deltaY;
310 if (Math.abs(dx) > mTouchSlop) {
311 mTouchMode = TOUCH_MODE_DRAGGING;
321 public boolean onTouchEvent(MotionEvent ev) {
322 mVelocityTracker.addMovement(ev);
323 final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
325 case MotionEvent.ACTION_DOWN:
326 mVelocityTracker.clear();
327 mScroller.abortAnimation();
328 mLastTouchX = ev.getX();
329 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
330 mTouchRemainderX = 0;
333 case MotionEvent.ACTION_MOVE: {
334 final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
336 Log.e(TAG, "onInterceptTouchEvent could not find pointer with id " +
337 mActivePointerId + " - did StaggeredGridView receive an inconsistent " +
341 final float x = MotionEventCompat.getX(ev, index);
342 final float dx = x - mLastTouchX + mTouchRemainderX;
343 final int deltaX = (int) dx;
344 mTouchRemainderX = dx - deltaX;
346 if (Math.abs(dx) > mTouchSlop) {
347 mTouchMode = TOUCH_MODE_DRAGGING;
350 if (mTouchMode == TOUCH_MODE_DRAGGING) {
353 if (!trackMotionScroll(deltaX, true)) {
354 // Break fling velocity if we impacted an edge.
355 mVelocityTracker.clear();
360 case MotionEvent.ACTION_CANCEL:
361 mTouchMode = TOUCH_MODE_IDLE;
364 case MotionEvent.ACTION_UP: {
365 mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
366 final float velocity = VelocityTrackerCompat.getXVelocity(mVelocityTracker,
368 if (Math.abs(velocity) > mFlingVelocity) { // TODO
369 mTouchMode = TOUCH_MODE_FLINGING;
370 mScroller.fling(0, 0, (int) velocity, 0,
371 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
373 ViewCompat.postInvalidateOnAnimation(this);
375 mTouchMode = TOUCH_MODE_IDLE;
385 * @param deltaX Pixels that content should move by
386 * @return true if the movement completed, false if it was stopped prematurely.
388 private boolean trackMotionScroll(int deltaX, boolean allowOverScroll) {
389 final boolean contentFits = contentFits();
390 final int allowOverhang = Math.abs(deltaX);
392 final int overScrolledBy;
399 overhang = fillLeft(mFirstPosition - 1, allowOverhang);
402 overhang = fillRight(mFirstPosition + getChildCount(), allowOverhang);
405 movedBy = Math.min(overhang, allowOverhang);
406 offsetChildren(up ? movedBy : -movedBy);
407 recycleOffscreenViews();
409 overScrolledBy = allowOverhang - overhang;
411 overScrolledBy = allowOverhang;
415 if (allowOverScroll) {
416 final int overScrollMode = ViewCompat.getOverScrollMode(this);
418 if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
419 (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits)) {
421 if (overScrolledBy > 0) {
422 EdgeEffectCompat edge = deltaX > 0 ? mLeftEdge : mRightEdge;
423 edge.onPull((float) Math.abs(deltaX) / getWidth());
424 ViewCompat.postInvalidateOnAnimation(this);
429 return deltaX == 0 || movedBy != 0;
433 * Important: this method will leave offscreen views attached if they
434 * are required to maintain the invariant that child view with index i
435 * is always the view corresponding to position mFirstPosition + i.
437 private void recycleOffscreenViews() {
438 final int height = getHeight();
439 final int clearAbove = 0;
440 final int clearBelow = height;
441 for (int i = getChildCount() - 1; i >= 0; i--) {
442 final View child = getChildAt(i);
443 if (child.getTop() <= clearBelow) {
444 // There may be other offscreen views, but we need to maintain
445 // the invariant documented above.
450 removeViewsInLayout(i, 1);
455 mRecycler.addScrap(child);
458 while (getChildCount() > 0) {
459 final View child = getChildAt(0);
460 if (child.getBottom() >= clearAbove) {
461 // There may be other offscreen views, but we need to maintain
462 // the invariant documented above.
467 removeViewsInLayout(0, 1);
472 mRecycler.addScrap(child);
477 final void offsetChildren(int offset) {
478 final int childCount = getChildCount();
479 for (int i = 0; i < childCount; i++) {
480 final View child = getChildAt(i);
481 child.layout(child.getLeft() + offset, child.getTop(),
482 child.getRight() + offset, child.getBottom());
486 private boolean contentFits() {
487 final int childCount = getChildCount();
488 if (childCount == 0) return true;
489 if (childCount != mItemCount) return false;
491 return getChildAt(0).getLeft() >= getPaddingLeft() &&
492 getChildAt(childCount - 1).getRight() <= getWidth() - getPaddingRight();
495 private void recycleAllViews() {
496 for (int i = 0; i < getChildCount(); i++) {
497 mRecycler.addScrap(getChildAt(i));
501 removeAllViewsInLayout();
507 private int fillRight(int pos, int overhang) {
508 int end = (getRight() - getLeft()) + overhang;
510 int nextLeft = getChildCount() == 0 ? 0 : getChildAt(getChildCount() - 1).getRight();
511 while (nextLeft < end && pos < mItemCount) {
512 pos += makeAndAddColumn(pos, nextLeft, true);
513 nextLeft = getChildAt(getChildCount() - 1).getRight();
515 final int gridRight = getWidth() - getPaddingRight();
516 return getChildAt(getChildCount() - 1).getRight() - gridRight;
519 private int fillLeft(int pos, int overhang) {
520 int end = getPaddingLeft() - overhang;
522 int nextRight = getChildAt(0).getLeft();
523 while (nextRight > end && pos >= 0) {
524 pos -= makeAndAddColumn(pos, nextRight, false);
525 nextRight = getChildAt(0).getLeft();
528 mFirstPosition = pos + 1;
529 return getPaddingLeft() - getChildAt(0).getLeft();
533 public void computeScroll() {
534 if (mScroller.computeScrollOffset()) {
535 final int x = mScroller.getCurrX();
536 final int dx = (int) (x - mLastTouchX);
538 final boolean stopped = !trackMotionScroll(dx, false);
540 if (!stopped && !mScroller.isFinished()) {
541 ViewCompat.postInvalidateOnAnimation(this);
544 final int overScrollMode = ViewCompat.getOverScrollMode(this);
545 if (overScrollMode != ViewCompat.OVER_SCROLL_NEVER) {
546 final EdgeEffectCompat edge;
552 edge.onAbsorb(Math.abs((int) mScroller.getCurrVelocity()));
553 ViewCompat.postInvalidateOnAnimation(this);
555 mScroller.abortAnimation();
557 mTouchMode = TOUCH_MODE_IDLE;
563 public void draw(Canvas canvas) {
566 if (!mLeftEdge.isFinished()) {
567 final int restoreCount = canvas.save();
568 final int height = getHeight() - getPaddingTop() - getPaddingBottom();
571 canvas.translate(-height + getPaddingTop(), 0);
572 mLeftEdge.setSize(height, getWidth());
573 if (mLeftEdge.draw(canvas)) {
574 postInvalidateOnAnimation();
576 canvas.restoreToCount(restoreCount);
578 if (!mRightEdge.isFinished()) {
579 final int restoreCount = canvas.save();
580 final int width = getWidth();
581 final int height = getHeight() - getPaddingTop() - getPaddingBottom();
584 canvas.translate(-getPaddingTop(), width);
585 mRightEdge.setSize(height, width);
586 if (mRightEdge.draw(canvas)) {
587 postInvalidateOnAnimation();
589 canvas.restoreToCount(restoreCount);
594 * Obtain a populated view from the adapter. If optScrap is non-null and is not
595 * reused it will be placed in the recycle bin.
597 * @param position position to get view for
598 * @param optScrap Optional scrap view; will be reused if possible
599 * @return A new view, a recycled view from mRecycler, or optScrap
601 private final View obtainView(int position, View optScrap) {
602 View view = mRecycler.getTransientStateView(position);
607 // Reuse optScrap if it's of the right type (and not null)
608 final int optType = optScrap != null ?
609 ((LayoutParams) optScrap.getLayoutParams()).viewType : -1;
610 final int positionViewType = mAdapter.getItemViewType(position);
611 final View scrap = optType == positionViewType ?
612 optScrap : mRecycler.getScrapView(positionViewType);
614 view = mAdapter.getView(position, scrap, this);
616 if (view != scrap && scrap != null) {
617 // The adapter didn't use it; put it back.
618 mRecycler.addScrap(scrap);
621 ViewGroup.LayoutParams lp = view.getLayoutParams();
623 if (view.getParent() != this) {
625 lp = generateDefaultLayoutParams();
626 } else if (!checkLayoutParams(lp)) {
627 lp = generateLayoutParams(lp);
629 view.setLayoutParams(lp);
632 final LayoutParams sglp = (LayoutParams) lp;
633 sglp.position = position;
634 sglp.viewType = positionViewType;
639 public GalleryThumbnailAdapter getAdapter() {
643 public void setAdapter(GalleryThumbnailAdapter adapter) {
644 if (mAdapter != null) {
645 mAdapter.unregisterDataSetObserver(mObserver);
647 // TODO: If the new adapter says that there are stable IDs, remove certain layout records
648 // and onscreen views if they have changed instead of removing all of the state here.
652 mOldItemCount = mItemCount = adapter != null ? adapter.getCount() : 0;
653 if (adapter != null) {
654 adapter.registerDataSetObserver(mObserver);
655 mRecycler.setViewTypeCount(adapter.getViewTypeCount());
656 mHasStableIds = adapter.hasStableIds();
658 mHasStableIds = false;
664 * Clear all state because the grid will be used for a completely different set of data.
666 private void clearAllState() {
667 // Clear all layout records and views
670 // Reset to the top of the grid
673 // Clear recycler because there could be different view types now
678 protected LayoutParams generateDefaultLayoutParams() {
679 return new LayoutParams(LayoutParams.WRAP_CONTENT);
683 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
684 return new LayoutParams(lp);
688 protected boolean checkLayoutParams(ViewGroup.LayoutParams lp) {
689 return lp instanceof LayoutParams;
693 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
694 return new LayoutParams(getContext(), attrs);
697 public static class LayoutParams extends ViewGroup.LayoutParams {
698 private static final int[] LAYOUT_ATTRS = new int[] {
699 android.R.attr.layout_span
702 private static final int SPAN_INDEX = 0;
705 * The number of columns this item should span
710 * Item position this view represents
715 * Type of this view as reported by the adapter
720 * The column this view is occupying
725 * The stable ID of the item this view displays
729 public LayoutParams(int height) {
730 super(MATCH_PARENT, height);
732 if (this.height == MATCH_PARENT) {
733 Log.w(TAG, "Constructing LayoutParams with height MATCH_PARENT - " +
734 "impossible! Falling back to WRAP_CONTENT");
735 this.height = WRAP_CONTENT;
739 public LayoutParams(Context c, AttributeSet attrs) {
742 if (this.width != MATCH_PARENT) {
743 Log.w(TAG, "Inflation setting LayoutParams width to " + this.width +
744 " - must be MATCH_PARENT");
745 this.width = MATCH_PARENT;
747 if (this.height == MATCH_PARENT) {
748 Log.w(TAG, "Inflation setting LayoutParams height to MATCH_PARENT - " +
749 "impossible! Falling back to WRAP_CONTENT");
750 this.height = WRAP_CONTENT;
753 TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
754 span = a.getInteger(SPAN_INDEX, 1);
758 public LayoutParams(ViewGroup.LayoutParams other) {
761 if (this.width != MATCH_PARENT) {
762 Log.w(TAG, "Constructing LayoutParams with width " + this.width +
763 " - must be MATCH_PARENT");
764 this.width = MATCH_PARENT;
766 if (this.height == MATCH_PARENT) {
767 Log.w(TAG, "Constructing LayoutParams with height MATCH_PARENT - " +
768 "impossible! Falling back to WRAP_CONTENT");
769 this.height = WRAP_CONTENT;
774 private class RecycleBin {
775 private ArrayList<View>[] mScrapViews;
776 private int mViewTypeCount;
777 private int mMaxScrap;
779 private SparseArray<View> mTransientStateViews;
781 public void setViewTypeCount(int viewTypeCount) {
782 if (viewTypeCount < 1) {
783 throw new IllegalArgumentException("Must have at least one view type (" +
784 viewTypeCount + " types reported)");
786 if (viewTypeCount == mViewTypeCount) {
790 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
791 for (int i = 0; i < viewTypeCount; i++) {
792 scrapViews[i] = new ArrayList<View>();
794 mViewTypeCount = viewTypeCount;
795 mScrapViews = scrapViews;
798 public void clear() {
799 final int typeCount = mViewTypeCount;
800 for (int i = 0; i < typeCount; i++) {
801 mScrapViews[i].clear();
803 if (mTransientStateViews != null) {
804 mTransientStateViews.clear();
808 public void clearTransientViews() {
809 if (mTransientStateViews != null) {
810 mTransientStateViews.clear();
814 public void addScrap(View v) {
815 final LayoutParams lp = (LayoutParams) v.getLayoutParams();
816 if (ViewCompat.hasTransientState(v)) {
817 if (mTransientStateViews == null) {
818 mTransientStateViews = new SparseArray<View>();
820 mTransientStateViews.put(lp.position, v);
824 final int childCount = getChildCount();
825 if (childCount > mMaxScrap) {
826 mMaxScrap = childCount;
829 ArrayList<View> scrap = mScrapViews[lp.viewType];
830 if (scrap.size() < mMaxScrap) {
835 public View getTransientStateView(int position) {
836 if (mTransientStateViews == null) {
840 final View result = mTransientStateViews.get(position);
841 if (result != null) {
842 mTransientStateViews.remove(position);
847 public View getScrapView(int type) {
848 ArrayList<View> scrap = mScrapViews[type];
849 if (scrap.isEmpty()) {
853 final int index = scrap.size() - 1;
854 final View result = scrap.get(index);
860 private class AdapterDataSetObserver extends DataSetObserver {
862 public void onChanged() {
864 mOldItemCount = mItemCount;
865 mItemCount = mAdapter.getCount();
867 // TODO: Consider matching these back up if we have stable IDs.
868 mRecycler.clearTransientViews();
870 if (!mHasStableIds) {
874 // TODO: consider repopulating in a deferred runnable instead
875 // (so that successive changes may still be batched)
880 public void onInvalidated() {