blob: e5dd6f2ff8b734870153a95dfeffad870446a68d [file] [log] [blame]
/*
* Copyright (C) 2013 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 com.android.photos.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.EdgeEffectCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.ListAdapter;
import android.widget.OverScroller;
import java.util.ArrayList;
public class GalleryThumbnailView extends ViewGroup {
public interface GalleryThumbnailAdapter extends ListAdapter {
/**
* @param position Position to get the intrinsic aspect ratio for
* @return width / height
*/
float getIntrinsicAspectRatio(int position);
}
private static final String TAG = "GalleryThumbnailView";
private static final float ASPECT_RATIO = (float) Math.sqrt(1.5f);
private static final int LAND_UNITS = 2;
private static final int PORT_UNITS = 3;
private GalleryThumbnailAdapter mAdapter;
private final RecycleBin mRecycler = new RecycleBin();
private final AdapterDataSetObserver mObserver = new AdapterDataSetObserver();
private boolean mDataChanged;
private int mOldItemCount;
private int mItemCount;
private boolean mHasStableIds;
private int mFirstPosition;
private boolean mPopulating;
private boolean mInLayout;
private int mTouchSlop;
private int mMaximumVelocity;
private int mFlingVelocity;
private float mLastTouchX;
private float mTouchRemainderX;
private int mActivePointerId;
private static final int TOUCH_MODE_IDLE = 0;
private static final int TOUCH_MODE_DRAGGING = 1;
private static final int TOUCH_MODE_FLINGING = 2;
private int mTouchMode;
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
private final OverScroller mScroller;
private final EdgeEffectCompat mLeftEdge;
private final EdgeEffectCompat mRightEdge;
private int mLargeColumnWidth;
private int mSmallColumnWidth;
private int mLargeColumnUnitCount = 8;
private int mSmallColumnUnitCount = 10;
public GalleryThumbnailView(Context context) {
this(context, null);
}
public GalleryThumbnailView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GalleryThumbnailView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final ViewConfiguration vc = ViewConfiguration.get(context);
mTouchSlop = vc.getScaledTouchSlop();
mMaximumVelocity = vc.getScaledMaximumFlingVelocity();
mFlingVelocity = vc.getScaledMinimumFlingVelocity();
mScroller = new OverScroller(context);
mLeftEdge = new EdgeEffectCompat(context);
mRightEdge = new EdgeEffectCompat(context);
setWillNotDraw(false);
setClipToPadding(false);
}
@Override
public void requestLayout() {
if (!mPopulating) {
super.requestLayout();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
Log.e(TAG, "onMeasure: must have an exact width or match_parent! " +
"Using fallback spec of EXACTLY " + widthSize);
}
if (heightMode != MeasureSpec.EXACTLY) {
Log.e(TAG, "onMeasure: must have an exact height or match_parent! " +
"Using fallback spec of EXACTLY " + heightSize);
}
setMeasuredDimension(widthSize, heightSize);
float portSpaces = mLargeColumnUnitCount / PORT_UNITS;
float height = getMeasuredHeight() / portSpaces;
mLargeColumnWidth = (int) (height / ASPECT_RATIO);
portSpaces++;
height = getMeasuredHeight() / portSpaces;
mSmallColumnWidth = (int) (height / ASPECT_RATIO);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mInLayout = true;
populate();
mInLayout = false;
final int width = r - l;
final int height = b - t;
mLeftEdge.setSize(width, height);
mRightEdge.setSize(width, height);
}
private void populate() {
if (getWidth() == 0 || getHeight() == 0) {
return;
}
// TODO: Handle size changing
// final int colCount = mColCount;
// if (mItemTops == null || mItemTops.length != colCount) {
// mItemTops = new int[colCount];
// mItemBottoms = new int[colCount];
// final int top = getPaddingTop();
// final int offset = top + Math.min(mRestoreOffset, 0);
// Arrays.fill(mItemTops, offset);
// Arrays.fill(mItemBottoms, offset);
// mLayoutRecords.clear();
// if (mInLayout) {
// removeAllViewsInLayout();
// } else {
// removeAllViews();
// }
// mRestoreOffset = 0;
// }
mPopulating = true;
layoutChildren(mDataChanged);
fillRight(mFirstPosition + getChildCount(), 0);
fillLeft(mFirstPosition - 1, 0);
mPopulating = false;
mDataChanged = false;
}
final void layoutChildren(boolean queryAdapter) {
// TODO
// final int childCount = getChildCount();
// for (int i = 0; i < childCount; i++) {
// View child = getChildAt(i);
//
// if (child.isLayoutRequested()) {
// final int widthSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(), MeasureSpec.EXACTLY);
// final int heightSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(), MeasureSpec.EXACTLY);
// child.measure(widthSpec, heightSpec);
// child.layout(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
// }
//
// int childTop = mItemBottoms[col] > Integer.MIN_VALUE ?
// mItemBottoms[col] + mItemMargin : child.getTop();
// if (span > 1) {
// int lowest = childTop;
// for (int j = col + 1; j < col + span; j++) {
// final int bottom = mItemBottoms[j] + mItemMargin;
// if (bottom > lowest) {
// lowest = bottom;
// }
// }
// childTop = lowest;
// }
// final int childHeight = child.getMeasuredHeight();
// final int childBottom = childTop + childHeight;
// final int childLeft = paddingLeft + col * (colWidth + itemMargin);
// final int childRight = childLeft + child.getMeasuredWidth();
// child.layout(childLeft, childTop, childRight, childBottom);
// }
}
/**
* Obtain the view and add it to our list of children. The view can be made
* fresh, converted from an unused view, or used as is if it was in the
* recycle bin.
*
* @param startPosition Logical position in the list to start from
* @param x Left or right edge of the view to add
* @param forward If true, align left edge to x and increase position.
* If false, align right edge to x and decrease position.
* @return Number of views added
*/
private int makeAndAddColumn(int startPosition, int x, boolean forward) {
int columnWidth = mLargeColumnWidth;
int addViews = 0;
for (int remaining = mLargeColumnUnitCount, i = 0;
remaining > 0 && startPosition + i >= 0 && startPosition + i < mItemCount;
i += forward ? 1 : -1, addViews++) {
if (mAdapter.getIntrinsicAspectRatio(startPosition + i) >= 1f) {
// landscape
remaining -= LAND_UNITS;
} else {
// portrait
remaining -= PORT_UNITS;
if (remaining < 0) {
remaining += (mSmallColumnUnitCount - mLargeColumnUnitCount);
columnWidth = mSmallColumnWidth;
}
}
}
int nextTop = 0;
for (int i = 0; i < addViews; i++) {
int position = startPosition + (forward ? i : -i);
View child = obtainView(position, null);
if (child.getParent() != this) {
if (mInLayout) {
addViewInLayout(child, forward ? -1 : 0, child.getLayoutParams());
} else {
addView(child, forward ? -1 : 0);
}
}
int heightSize = (int) (.5f + (mAdapter.getIntrinsicAspectRatio(position) >= 1f
? columnWidth / ASPECT_RATIO
: columnWidth * ASPECT_RATIO));
int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
int widthSpec = MeasureSpec.makeMeasureSpec(columnWidth, MeasureSpec.EXACTLY);
child.measure(widthSpec, heightSpec);
int childLeft = forward ? x : x - columnWidth;
child.layout(childLeft, nextTop, childLeft + columnWidth, nextTop + heightSize);
nextTop += heightSize;
}
return addViews;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
mVelocityTracker.addMovement(ev);
final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
mVelocityTracker.clear();
mScroller.abortAnimation();
mLastTouchX = ev.getX();
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
mTouchRemainderX = 0;
if (mTouchMode == TOUCH_MODE_FLINGING) {
// Catch!
mTouchMode = TOUCH_MODE_DRAGGING;
return true;
}
break;
case MotionEvent.ACTION_MOVE: {
final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
if (index < 0) {
Log.e(TAG, "onInterceptTouchEvent could not find pointer with id " +
mActivePointerId + " - did StaggeredGridView receive an inconsistent " +
"event stream?");
return false;
}
final float x = MotionEventCompat.getX(ev, index);
final float dx = x - mLastTouchX + mTouchRemainderX;
final int deltaY = (int) dx;
mTouchRemainderX = dx - deltaY;
if (Math.abs(dx) > mTouchSlop) {
mTouchMode = TOUCH_MODE_DRAGGING;
return true;
}
}
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
mVelocityTracker.addMovement(ev);
final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
mVelocityTracker.clear();
mScroller.abortAnimation();
mLastTouchX = ev.getX();
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
mTouchRemainderX = 0;
break;
case MotionEvent.ACTION_MOVE: {
final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
if (index < 0) {
Log.e(TAG, "onInterceptTouchEvent could not find pointer with id " +
mActivePointerId + " - did StaggeredGridView receive an inconsistent " +
"event stream?");
return false;
}
final float x = MotionEventCompat.getX(ev, index);
final float dx = x - mLastTouchX + mTouchRemainderX;
final int deltaX = (int) dx;
mTouchRemainderX = dx - deltaX;
if (Math.abs(dx) > mTouchSlop) {
mTouchMode = TOUCH_MODE_DRAGGING;
}
if (mTouchMode == TOUCH_MODE_DRAGGING) {
mLastTouchX = x;
if (!trackMotionScroll(deltaX, true)) {
// Break fling velocity if we impacted an edge.
mVelocityTracker.clear();
}
}
} break;
case MotionEvent.ACTION_CANCEL:
mTouchMode = TOUCH_MODE_IDLE;
break;
case MotionEvent.ACTION_UP: {
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
final float velocity = VelocityTrackerCompat.getXVelocity(mVelocityTracker,
mActivePointerId);
if (Math.abs(velocity) > mFlingVelocity) { // TODO
mTouchMode = TOUCH_MODE_FLINGING;
mScroller.fling(0, 0, (int) velocity, 0,
Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
mLastTouchX = 0;
ViewCompat.postInvalidateOnAnimation(this);
} else {
mTouchMode = TOUCH_MODE_IDLE;
}
} break;
}
return true;
}
/**
*
* @param deltaX Pixels that content should move by
* @return true if the movement completed, false if it was stopped prematurely.
*/
private boolean trackMotionScroll(int deltaX, boolean allowOverScroll) {
final boolean contentFits = contentFits();
final int allowOverhang = Math.abs(deltaX);
final int overScrolledBy;
final int movedBy;
if (!contentFits) {
final int overhang;
final boolean up;
mPopulating = true;
if (deltaX > 0) {
overhang = fillLeft(mFirstPosition - 1, allowOverhang);
up = true;
} else {
overhang = fillRight(mFirstPosition + getChildCount(), allowOverhang);
up = false;
}
movedBy = Math.min(overhang, allowOverhang);
offsetChildren(up ? movedBy : -movedBy);
recycleOffscreenViews();
mPopulating = false;
overScrolledBy = allowOverhang - overhang;
} else {
overScrolledBy = allowOverhang;
movedBy = 0;
}
if (allowOverScroll) {
final int overScrollMode = ViewCompat.getOverScrollMode(this);
if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
(overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits)) {
if (overScrolledBy > 0) {
EdgeEffectCompat edge = deltaX > 0 ? mLeftEdge : mRightEdge;
edge.onPull((float) Math.abs(deltaX) / getWidth());
ViewCompat.postInvalidateOnAnimation(this);
}
}
}
return deltaX == 0 || movedBy != 0;
}
/**
* Important: this method will leave offscreen views attached if they
* are required to maintain the invariant that child view with index i
* is always the view corresponding to position mFirstPosition + i.
*/
private void recycleOffscreenViews() {
final int height = getHeight();
final int clearAbove = 0;
final int clearBelow = height;
for (int i = getChildCount() - 1; i >= 0; i--) {
final View child = getChildAt(i);
if (child.getTop() <= clearBelow) {
// There may be other offscreen views, but we need to maintain
// the invariant documented above.
break;
}
if (mInLayout) {
removeViewsInLayout(i, 1);
} else {
removeViewAt(i);
}
mRecycler.addScrap(child);
}
while (getChildCount() > 0) {
final View child = getChildAt(0);
if (child.getBottom() >= clearAbove) {
// There may be other offscreen views, but we need to maintain
// the invariant documented above.
break;
}
if (mInLayout) {
removeViewsInLayout(0, 1);
} else {
removeViewAt(0);
}
mRecycler.addScrap(child);
mFirstPosition++;
}
}
final void offsetChildren(int offset) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
child.layout(child.getLeft() + offset, child.getTop(),
child.getRight() + offset, child.getBottom());
}
}
private boolean contentFits() {
final int childCount = getChildCount();
if (childCount == 0) return true;
if (childCount != mItemCount) return false;
return getChildAt(0).getLeft() >= getPaddingLeft() &&
getChildAt(childCount - 1).getRight() <= getWidth() - getPaddingRight();
}
private void recycleAllViews() {
for (int i = 0; i < getChildCount(); i++) {
mRecycler.addScrap(getChildAt(i));
}
if (mInLayout) {
removeAllViewsInLayout();
} else {
removeAllViews();
}
}
private int fillRight(int pos, int overhang) {
int end = (getRight() - getLeft()) + overhang;
int nextLeft = getChildCount() == 0 ? 0 : getChildAt(getChildCount() - 1).getRight();
while (nextLeft < end && pos < mItemCount) {
pos += makeAndAddColumn(pos, nextLeft, true);
nextLeft = getChildAt(getChildCount() - 1).getRight();
}
final int gridRight = getWidth() - getPaddingRight();
return getChildAt(getChildCount() - 1).getRight() - gridRight;
}
private int fillLeft(int pos, int overhang) {
int end = getPaddingLeft() - overhang;
int nextRight = getChildAt(0).getLeft();
while (nextRight > end && pos >= 0) {
pos -= makeAndAddColumn(pos, nextRight, false);
nextRight = getChildAt(0).getLeft();
}
mFirstPosition = pos + 1;
return getPaddingLeft() - getChildAt(0).getLeft();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
final int x = mScroller.getCurrX();
final int dx = (int) (x - mLastTouchX);
mLastTouchX = x;
final boolean stopped = !trackMotionScroll(dx, false);
if (!stopped && !mScroller.isFinished()) {
ViewCompat.postInvalidateOnAnimation(this);
} else {
if (stopped) {
final int overScrollMode = ViewCompat.getOverScrollMode(this);
if (overScrollMode != ViewCompat.OVER_SCROLL_NEVER) {
final EdgeEffectCompat edge;
if (dx > 0) {
edge = mLeftEdge;
} else {
edge = mRightEdge;
}
edge.onAbsorb(Math.abs((int) mScroller.getCurrVelocity()));
ViewCompat.postInvalidateOnAnimation(this);
}
mScroller.abortAnimation();
}
mTouchMode = TOUCH_MODE_IDLE;
}
}
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (!mLeftEdge.isFinished()) {
final int restoreCount = canvas.save();
final int height = getHeight() - getPaddingTop() - getPaddingBottom();
canvas.rotate(270);
canvas.translate(-height + getPaddingTop(), 0);
mLeftEdge.setSize(height, getWidth());
if (mLeftEdge.draw(canvas)) {
postInvalidateOnAnimation();
}
canvas.restoreToCount(restoreCount);
}
if (!mRightEdge.isFinished()) {
final int restoreCount = canvas.save();
final int width = getWidth();
final int height = getHeight() - getPaddingTop() - getPaddingBottom();
canvas.rotate(90);
canvas.translate(-getPaddingTop(), width);
mRightEdge.setSize(height, width);
if (mRightEdge.draw(canvas)) {
postInvalidateOnAnimation();
}
canvas.restoreToCount(restoreCount);
}
}
/**
* Obtain a populated view from the adapter. If optScrap is non-null and is not
* reused it will be placed in the recycle bin.
*
* @param position position to get view for
* @param optScrap Optional scrap view; will be reused if possible
* @return A new view, a recycled view from mRecycler, or optScrap
*/
private final View obtainView(int position, View optScrap) {
View view = mRecycler.getTransientStateView(position);
if (view != null) {
return view;
}
// Reuse optScrap if it's of the right type (and not null)
final int optType = optScrap != null ?
((LayoutParams) optScrap.getLayoutParams()).viewType : -1;
final int positionViewType = mAdapter.getItemViewType(position);
final View scrap = optType == positionViewType ?
optScrap : mRecycler.getScrapView(positionViewType);
view = mAdapter.getView(position, scrap, this);
if (view != scrap && scrap != null) {
// The adapter didn't use it; put it back.
mRecycler.addScrap(scrap);
}
ViewGroup.LayoutParams lp = view.getLayoutParams();
if (view.getParent() != this) {
if (lp == null) {
lp = generateDefaultLayoutParams();
} else if (!checkLayoutParams(lp)) {
lp = generateLayoutParams(lp);
}
view.setLayoutParams(lp);
}
final LayoutParams sglp = (LayoutParams) lp;
sglp.position = position;
sglp.viewType = positionViewType;
return view;
}
public GalleryThumbnailAdapter getAdapter() {
return mAdapter;
}
public void setAdapter(GalleryThumbnailAdapter adapter) {
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(mObserver);
}
// TODO: If the new adapter says that there are stable IDs, remove certain layout records
// and onscreen views if they have changed instead of removing all of the state here.
clearAllState();
mAdapter = adapter;
mDataChanged = true;
mOldItemCount = mItemCount = adapter != null ? adapter.getCount() : 0;
if (adapter != null) {
adapter.registerDataSetObserver(mObserver);
mRecycler.setViewTypeCount(adapter.getViewTypeCount());
mHasStableIds = adapter.hasStableIds();
} else {
mHasStableIds = false;
}
populate();
}
/**
* Clear all state because the grid will be used for a completely different set of data.
*/
private void clearAllState() {
// Clear all layout records and views
removeAllViews();
// Reset to the top of the grid
mFirstPosition = 0;
// Clear recycler because there could be different view types now
mRecycler.clear();
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT);
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
return new LayoutParams(lp);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams lp) {
return lp instanceof LayoutParams;
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends ViewGroup.LayoutParams {
private static final int[] LAYOUT_ATTRS = new int[] {
android.R.attr.layout_span
};
private static final int SPAN_INDEX = 0;
/**
* The number of columns this item should span
*/
public int span = 1;
/**
* Item position this view represents
*/
int position;
/**
* Type of this view as reported by the adapter
*/
int viewType;
/**
* The column this view is occupying
*/
int column;
/**
* The stable ID of the item this view displays
*/
long id = -1;
public LayoutParams(int height) {
super(MATCH_PARENT, height);
if (this.height == MATCH_PARENT) {
Log.w(TAG, "Constructing LayoutParams with height MATCH_PARENT - " +
"impossible! Falling back to WRAP_CONTENT");
this.height = WRAP_CONTENT;
}
}
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
if (this.width != MATCH_PARENT) {
Log.w(TAG, "Inflation setting LayoutParams width to " + this.width +
" - must be MATCH_PARENT");
this.width = MATCH_PARENT;
}
if (this.height == MATCH_PARENT) {
Log.w(TAG, "Inflation setting LayoutParams height to MATCH_PARENT - " +
"impossible! Falling back to WRAP_CONTENT");
this.height = WRAP_CONTENT;
}
TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
span = a.getInteger(SPAN_INDEX, 1);
a.recycle();
}
public LayoutParams(ViewGroup.LayoutParams other) {
super(other);
if (this.width != MATCH_PARENT) {
Log.w(TAG, "Constructing LayoutParams with width " + this.width +
" - must be MATCH_PARENT");
this.width = MATCH_PARENT;
}
if (this.height == MATCH_PARENT) {
Log.w(TAG, "Constructing LayoutParams with height MATCH_PARENT - " +
"impossible! Falling back to WRAP_CONTENT");
this.height = WRAP_CONTENT;
}
}
}
private class RecycleBin {
private ArrayList<View>[] mScrapViews;
private int mViewTypeCount;
private int mMaxScrap;
private SparseArray<View> mTransientStateViews;
public void setViewTypeCount(int viewTypeCount) {
if (viewTypeCount < 1) {
throw new IllegalArgumentException("Must have at least one view type (" +
viewTypeCount + " types reported)");
}
if (viewTypeCount == mViewTypeCount) {
return;
}
ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
for (int i = 0; i < viewTypeCount; i++) {
scrapViews[i] = new ArrayList<View>();
}
mViewTypeCount = viewTypeCount;
mScrapViews = scrapViews;
}
public void clear() {
final int typeCount = mViewTypeCount;
for (int i = 0; i < typeCount; i++) {
mScrapViews[i].clear();
}
if (mTransientStateViews != null) {
mTransientStateViews.clear();
}
}
public void clearTransientViews() {
if (mTransientStateViews != null) {
mTransientStateViews.clear();
}
}
public void addScrap(View v) {
final LayoutParams lp = (LayoutParams) v.getLayoutParams();
if (ViewCompat.hasTransientState(v)) {
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArray<View>();
}
mTransientStateViews.put(lp.position, v);
return;
}
final int childCount = getChildCount();
if (childCount > mMaxScrap) {
mMaxScrap = childCount;
}
ArrayList<View> scrap = mScrapViews[lp.viewType];
if (scrap.size() < mMaxScrap) {
scrap.add(v);
}
}
public View getTransientStateView(int position) {
if (mTransientStateViews == null) {
return null;
}
final View result = mTransientStateViews.get(position);
if (result != null) {
mTransientStateViews.remove(position);
}
return result;
}
public View getScrapView(int type) {
ArrayList<View> scrap = mScrapViews[type];
if (scrap.isEmpty()) {
return null;
}
final int index = scrap.size() - 1;
final View result = scrap.get(index);
scrap.remove(index);
return result;
}
}
private class AdapterDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
// TODO: Consider matching these back up if we have stable IDs.
mRecycler.clearTransientViews();
if (!mHasStableIds) {
recycleAllViews();
}
// TODO: consider repopulating in a deferred runnable instead
// (so that successive changes may still be batched)
requestLayout();
}
@Override
public void onInvalidated() {
}
}
}