package com.electronapps.LJPro;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.os.IInterface;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.View.MeasureSpec;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.AbsListView.RecyclerListener;
public class HorizontalSnapView extends HorizontalScrollView {
private static final int SWIPE_MIN_DISTANCE = 5;
private static final int SWIPE_THRESHOLD_VELOCITY = 300;
static final int SYNC_MAX_DURATION_MILLIS = 100;
private GestureDetector mGestureDetector;
private int mActiveFeature = 0;
private Context mContext;
ContainerLayout mContainer;
ListAdapter mAdapter;
SelectionObserver mObserver;
public interface SelectionObserver {
void onSelectionChanged(int position);
}
private final static String TAG=HorizontalSnapView.class.getSimpleName();
public HorizontalSnapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext=context;
setHorizontalScrollBarEnabled(false);
setupContainer();
}
public HorizontalSnapView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext=context;
setHorizontalScrollBarEnabled(false);
setupContainer();
}
public HorizontalSnapView(Context context) {
super(context);
mContext=context;
setHorizontalScrollBarEnabled(false);
setupContainer();
}
protected class ContainerLayout extends LinearLayout {
public ContainerLayout(Context context) {
super(context);
setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
setOrientation(LinearLayout.HORIZONTAL);
}
public void detachAllViews() {
super.detachAllViewsFromParent();
}
public void detachView(int index) {
super.detachViewFromParent(index);
}
public void detachView (View view) {
super.detachViewFromParent(view);
}
public void removeView(View view) {
super.removeViewInLayout(view);
}
public void addView(View view,int index, ViewGroup.LayoutParams params) {
super.addViewInLayout(view, index, params);
}
public void removeDetachedView(View view, Boolean animate) {
super.removeDetachedView(view, animate);
}
public void attachView(View child, int index, ViewGroup.LayoutParams params) {
super.attachViewToParent(child, index, params);
}
}
public void registerSelectionObserver(SelectionObserver observer) {
mObserver=observer;
}
public void setupContainer() {
mContainer= new ContainerLayout(mContext);
addView(mContainer);
setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
//If the user swipes
if (mGestureDetector.onTouchEvent(event)) {
return true;
}
else if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL ){
computeScroll();
int scrollX = getScrollX();
int featureWidth = v.getMeasuredWidth();
int offset=0;
double shift=(scrollX-(mSelectedPosition-mFirstPosition)*featureWidth)/(featureWidth/2.0d);
if (shift>1.0)
{
offset=1;
}
if (shift<-1.0) {
offset=-1;
}
final int selPos=mSelectedPosition;
final int firstPos=mFirstPosition;
mActiveFeature=selPos-firstPos;
//snap back to current view if we have met offset criteria
if (offset==0) smoothScrollTo(mContainer.getChildAt(mActiveFeature).getLeft(),0);
else scrollListItemsBy(offset);
return true;
}
else{
return false;
}
}
});
mGestureDetector = new GestureDetector(new MyGestureDetector());
}
class MyGestureDetector extends SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
super.onFling(e1,e1,velocityX,velocityY);
try {
if(Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY&&Math.abs(velocityX)>Math.abs(velocityY)) { //right to left
if(velocityX<0) {
scrollListItemsBy(1);
return true;
}
//left to right
else if (velocityX>0) {
scrollListItemsBy(-1);
return true;
}
}
} catch (Exception e) {
Log.e(TAG, "There was an error processing the Fling event:" + e.getMessage());
}
return false;
}
}
boolean mDataChanged=false;
private int mLayoutMode=LAYOUT_NORMAL;
private int mFirstPosition=0;
private boolean mBlockLayoutRequests;
AdapterDataSetObserver mDataSetObserver;
private boolean mAreAllItemsSelectable;
private int mOldItemCount;
private int mItemCount;
private boolean mNeedSync;
private int mSelectedPosition;
private long mSelectedRowId;
private long mSyncRowId;
private int mSyncPosition;
private int mWindowSize=3;
static final int LAYOUT_SET_SELECTION = 2;
static final int LAYOUT_SYNC = 3;
static final int LAYOUT_NORMAL=1;
private final static int SYNC_SELECTED_POSITION=1;
private final static int SYNC_FIRST_POSITION=2;
protected void layoutChildren() {
final boolean blockLayoutRequests = mBlockLayoutRequests;
if (!blockLayoutRequests) {
mBlockLayoutRequests = true;
} else {
return;
}
try {
invalidate();
if (mAdapter == null) {
resetList();
return;
}
int childCount = mContainer.getChildCount();
int index;
View sel;
int newSel = AdapterView.INVALID_POSITION;
View focusLayoutRestoreView = null;
boolean dataChanged = mDataChanged;
if (dataChanged) {
handleDataChanged();
}
// Handle the empty set by removing all views that are visible
// and calling it a day
if (mItemCount == 0) {
resetList();
return;
} else if (mItemCount != mAdapter.getCount()) {
throw new IllegalStateException("The content of the adapter has changed but "
+ "ListView did not receive a notification. Make sure the content of "
+ "your adapter is not modified from a background thread, but only "
+ "from the UI thread. [in ListView(" + getId() + ", " + getClass()
+ ") with Adapter(" + mAdapter.getClass() + ")]");
}
// Pull all children into the RecycleBin.
// These views will be reused if possible
final int firstPosition =mFirstPosition;
final RecycleBin recycleBin = mRecycler;
// reset the focus restoration
View focusLayoutRestoreDirectChild = null;
// Don't put header or footer views into the Recycler. Those are
// already cached in mHeaderViews;
if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i));
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(getChildAt(i),
ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);
}
}
} else {
recycleBin.fillActiveViews(childCount, firstPosition);
}
// take focus back to us temporarily to avoid the eventual
// call to clear focus when removing the focused child below
// from messing things up when ViewRoot assigns focus back
// to someone else
final View focusedChild = mContainer.getFocusedChild();
if (focusedChild != null) {
// TODO: in some cases focusedChild.getParent() == null
// we can remember the focused view to restore after relayout if the
// data hasn't changed, or if the focused position is a header or footer
if (!dataChanged) {
focusLayoutRestoreDirectChild = focusedChild;
// remember the specific view that had focus
focusLayoutRestoreView = mContainer.findFocus();
if (focusLayoutRestoreView != null) {
// tell it we are going to mess with it
focusLayoutRestoreView.onStartTemporaryDetach();
}
}
mContainer.requestFocus();
}
// Clear out old views
//remremoveAllViewsInLayout();
try{
mContainer.detachAllViews();
}
catch(Throwable e) {
Log.e(TAG,e.getMessage(),e);
}
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
if (newSel !=AdapterView.INVALID_POSITION) {
sel = fillSpecific(newSel);
} else {
sel = fillSpecific(mSelectedPosition);
}
break;
case LAYOUT_SYNC:
sel = fillSpecific(mSyncPosition);
break;
default:
if (childCount == 0) {
sel = fillFromFirst();
} else {
if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
sel = fillSpecific(mSelectedPosition);
} else if (mFirstPosition < mItemCount) {
sel = fillSpecific(mFirstPosition);
} else {
sel = fillSpecific(0);
}
}
break;
}
// Flush any cached views that did not get reused above
recycleBin.scrapActiveViews();
if (sel != null) {
// the current selected item should get focus if items
// are focusable
if (mContainer.hasFocus() && !sel.hasFocus()) {
final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
if (!focusWasTaken) {
// selected item didn't take focus, fine, but still want
// to make sure something else outside of the selected view
// has focus
final View focused = getFocusedChild();
if (focused != null) {
focused.clearFocus();
}
} else {
sel.setSelected(false);
}
}
} else {
// even if there is not selected position, we may need to restore
// focus (i.e. something focusable in touch mode)
if (mContainer.hasFocus() && focusLayoutRestoreView != null) {
focusLayoutRestoreView.requestFocus();
}
}
// tell focus view we are done mucking with it, if it is still in
// our view hierarchy.
if (focusLayoutRestoreView != null
&& focusLayoutRestoreView.getWindowToken() != null) {
focusLayoutRestoreView.onFinishTemporaryDetach();
}
setSelectedPositionInt(mSelectedPosition);
mLayoutMode = LAYOUT_NORMAL;
mDataChanged = false;
mNeedSync = false;
//mContainer.scrollTo(sel.getLeft(),0);
//scrollToChild(sel);
} finally {
if (!blockLayoutRequests) {
mBlockLayoutRequests = false;
}
}
}
public void setWindowSize(int size){
mWindowSize=size;
}
private View fillFromFirst() {
mFirstPosition=0;
setSelectedPositionInt(0);
View selectedView = null;
//Are we filling right from first or as part of fill from selected?
int maxFill=mWindowSize;
for(int i=0;i<maxFill;i++){
if(i>mItemCount) break;
// is this the selected item?
boolean selected = i == mSelectedPosition;
View child = makeAndAddView(i,FLOW_RIGHT,selected);
if (selected) {
selectedView = child;
}
}
return selectedView;
}
private View fillFromLast() {
mFirstPosition=mItemCount-mWindowSize;
mSelectedPosition=mItemCount-1;
View selectedView = null;
//Are we filling right from end or as part of fill from selected?
int maxFill=mWindowSize;
for(int i=mItemCount-1;(mItemCount-i)<=maxFill;i--){
if(i<0) break;
// is this the selected item?
boolean selected = i == mSelectedPosition;
View child = makeAndAddView(i,FLOW_LEFT,selected);
if (selected) {
selectedView = child;
}
}
return selectedView;
}
private final int FLOW_LEFT=0;
private final int FLOW_RIGHT=1;
private boolean mNeedLayout;
private View makeAndAddView(int position, int flow,boolean selected) {
View child = null;
if (!mDataChanged) {
// Try to use an exsiting view for this position
child = mRecycler.getActiveView(position);
if (child != null) {
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
position, mContainer.getChildCount());
}
// Found it -- we're using an existing child
// This just needs to be positioned
setupChild(child, position,flow,selected, true);
return child;
}
}
// Make a new view for this position, or convert an unused view if possible
try {
child = obtainView(position);
}
catch(Throwable e) {
Log.e(TAG,e.getMessage(),e);
}
if (child!=null)
// This needs to be positioned and measured
{ setupChild(child, position,flow,selected, false);}
return child;
}
private void setupChild(View child, int position,int flow,boolean selected, boolean recycled) {
// Respect layout params that are already in the view. Otherwise make some up...
// noinspection unchecked
ViewGroup.LayoutParams p = (ViewGroup.LayoutParams) child.getLayoutParams();
final int numChildren=mContainer.getChildCount();
final boolean needToMeasure = !recycled || child.isLayoutRequested();
if (p == null) {
p = new ViewGroup.LayoutParams(mMeasuredWidth,mMeasuredHeight);
}
if (recycled) {
try {
mContainer.attachView(child,flow==FLOW_RIGHT?-1:0, p);
//TODO: Do we need to measure children here?
} catch(Throwable e) {
Log.e(TAG,e.getMessage(),e);
}
} else {
mContainer.addView(child,flow==FLOW_RIGHT?-1:0, p);
}
if (needToMeasure) {
int childHeightSpec;
int childWidthSpec;
//childHeightSpec=ViewGroup.getChildMeasureSpec(MeasureSpec.EXACTLY,this.getHorizontalFadingEdgeLength(),mMeasuredHeight);
childHeightSpec = MeasureSpec.makeMeasureSpec(mMeasuredHeight,MeasureSpec.EXACTLY);
childWidthSpec=ViewGroup.getChildMeasureSpec(MeasureSpec.EXACTLY,this.getHorizontalFadingEdgeLength()*2,mMeasuredWidth);
//childWidthSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth,MeasureSpec.EXACTLY);
child.measure(childWidthSpec, childHeightSpec);
} else {
cleanupLayoutState(child);
}
final int w = child.getMeasuredWidth();
final int childrenRight=numChildren*w;
final int h = child.getMeasuredHeight();
final int childRight =flow==FLOW_RIGHT?childrenRight+w:w;
final int childLeft =flow==FLOW_RIGHT?childrenRight:0;
if (needToMeasure) {
child.layout(childLeft, h, childRight, 0);
} else {
child.offsetLeftAndRight(flow==FLOW_RIGHT?childrenRight - child.getLeft():-child.getLeft());
}
}
private void offsetChildrenLeftAndRight(int offset) {
for (int i = mContainer.getChildCount() - 1; i >= 0; i--) {
mContainer.getChildAt(i).offsetLeftAndRight(offset);
}
}
private void handleDataChanged() {
int count = mItemCount;
if (count > 0) {
int newPos;
int selectablePos;
// Find the row we are supposed to sync to
if (mNeedSync) {
// Update this first, since setNextSelectedPositionInt inspects it
mNeedSync = false;
switch (mSyncMode) {
case SYNC_SELECTED_POSITION:
if (isInTouchMode()) {
// We saved our state when not in touch mode. (We know this because
// mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
// restore in touch mode. Just leave mSyncPosition as it is (possibly
// adjusting if the available range changed) and return.
mLayoutMode = LAYOUT_SYNC;
mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
return;
} else {
// See if we can find a position in the new data with the same
// id as the old selection. This will change mSyncPosition.
newPos = findSyncPosition();
if (newPos >= 0) {
// Found it. Now verify that new selection is still selectable
selectablePos =lookForSelectablePosition(newPos, true);
if (selectablePos == newPos) {
// Same row id is selected
mSyncPosition = newPos;
// Restore selection
setSelectedPositionInt(newPos);
return;
}
}
}
break;
case SYNC_FIRST_POSITION:
// Leave mSyncPosition as it is -- just pin to available range
mLayoutMode = LAYOUT_SYNC;
mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
return;
}
}
if (!isInTouchMode()) {
// We couldn't find matching data -- try to use the same position
newPos = getSelectedItemPosition();
// Pin position to the available range
if (newPos >= count) {
newPos = count - 1;
}
if (newPos < 0) {
newPos = 0;
}
// Make sure we select something selectable -- first look down
selectablePos =lookForSelectablePosition(newPos, true);
if (selectablePos >= 0) {
setSelectedPositionInt(selectablePos);
return;
} else {
// Looking down didn't work -- try looking up
selectablePos = lookForSelectablePosition(newPos, false);
if (selectablePos >= 0) {
setSelectedPositionInt(selectablePos);
return;
}
}
}
}
}
int lookForSelectablePosition(int position, boolean lookDown) {
final ListAdapter adapter = mAdapter;
if (adapter == null || isInTouchMode()) {
return AdapterView.INVALID_POSITION;
}
final int count = adapter.getCount();
if (!mAreAllItemsSelectable) {
if (lookDown) {
position = Math.max(0, position);
while (position < count && !adapter.isEnabled(position)) {
position++;
}
} else {
position = Math.min(position, count - 1);
while (position >= 0 && !adapter.isEnabled(position)) {
position--;
}
}
if (position < 0 || position >= count) {
return AdapterView.INVALID_POSITION;
}
return position;
} else {
if (position < 0 || position >= count) {
return AdapterView.INVALID_POSITION;
}
return position;
}
}
private int findSyncPosition() {
int count = mItemCount;
if (count == 0) {
return AdapterView.INVALID_POSITION;
}
long idToMatch = mSyncRowId;
int seed = mSyncPosition;
// If there isn't a selection don't hunt for it
if (idToMatch == AdapterView.INVALID_ROW_ID) {
return AdapterView.INVALID_POSITION;
}
// Pin seed to reasonable values
seed = Math.max(0, seed);
seed = Math.min(count - 1, seed);
long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
long rowId;
// first position scanned so far
int first = seed;
// last position scanned so far
int last = seed;
// True if we should move down on the next iteration
boolean next = false;
// True when we have looked at the first item in the data
boolean hitFirst;
// True when we have looked at the last item in the data
boolean hitLast;
// Get the item ID locally (instead of getItemIdAtPosition), so
// we need the adapter
ListAdapter adapter=mAdapter;
if (adapter == null) {
return AdapterView.INVALID_POSITION;
}
while (SystemClock.uptimeMillis() <= endTime) {
rowId = adapter.getItemId(seed);
if (rowId == idToMatch) {
// Found it!
return seed;
}
hitLast = last == count - 1;
hitFirst = first == 0;
if (hitLast && hitFirst) {
// Looked at everything
break;
}
if (hitFirst || (next && !hitLast)) {
// Either we hit the top, or we are trying to move down
last++;
seed = last;
// Try going up next time
next = false;
} else if (hitLast || (!next && !hitFirst)) {
// Either we hit the bottom, or we are trying to move up
first--;
seed = first;
// Try going down next time
next = true;
}
}
return AdapterView.INVALID_POSITION;
}
private View fillSpecific(int startPosition) {
setSelectedPositionInt(startPosition);
final int hardLimit=(mWindowSize-1)/2;
mFirstPosition=Math.max(0,startPosition-Math.max(hardLimit,hardLimit+(hardLimit-(mItemCount-startPosition))));
final int count=mItemCount;
if (startPosition==0) {
return fillFromFirst();
}
else if(startPosition==mItemCount-1) {
return fillFromLast();
}
else {
final int leftLimit=Math.max(hardLimit,hardLimit+(hardLimit-(mItemCount-startPosition)));
final int rightLimit=Math.max(hardLimit, hardLimit+hardLimit-startPosition);
View selectedChild=null;
int firstChild=Math.max(0,startPosition-leftLimit);
int lastChild=Math.min(startPosition+rightLimit,mItemCount);
int current=firstChild;
while(current<=lastChild) {
View child=makeAndAddView(current,FLOW_RIGHT,false);
if (current==startPosition) selectedChild=child;
current++;
}
return selectedChild;
}
}
private void scrollListItemsBy(int amount) {
//final int listRight=mContainer.getRight()+mContainer.getPaddingRight();
//final int listLeft=mContainer.getLeft()+mContainer.getPaddingLeft();
if (amount!=0)
{
final RecycleBin recycleBin = mRecycler;
int numChildren = mContainer.getChildCount();
final int lastChildPosition = mFirstPosition + numChildren - 1;
if (amount >0) {
// shifted items left
//only start adding views once we are in the middle of the first window and not in the last
if (mSelectedPosition>=(mWindowSize-1)/2&&lastChildPosition < mItemCount - 1) {
addViewRight(lastChildPosition);
View first = mContainer.getChildAt(0);
int childWidth=first.getWidth();
mContainer.removeViewInLayout(first);
recycleBin.addScrapView(first);
mFirstPosition++;
int next=mSelectedPosition+1;
setSelectedPositionInt(next);
offsetChildrenLeftAndRight(-childWidth);
View child=mContainer.getChildAt(mSelectedPosition-mFirstPosition);
int childLeft=child.getLeft();
int scrollX=getScrollX();
//int childWidth=child.getMeasuredWidth();
if (scrollX!=childLeft)
{
Log.e(TAG,"Block Layout?: "+((Boolean)mBlockLayoutRequests).toString());
Log.e(TAG," scrollListItems Adjusted Scroll to: "+((Integer) childLeft).toString());
smoothScrollTo(childLeft,0);
}
// Put first view in scrap
}
else {
int next=mSelectedPosition+1;
setSelectedPositionInt(next);
View child=mContainer.getChildAt(mSelectedPosition-mFirstPosition);
smoothScrollTo(child.getLeft(),0);
mContainer.invalidate();
}
}
else {
// shifted items right
//Only add more views if we aren't currently within the last window
if (mSelectedPosition>(mWindowSize-1)/2&&mFirstPosition>0) {
addViewLeft(mFirstPosition);
int lastIndex = mContainer.getChildCount() - 1;
View last = mContainer.getChildAt(lastIndex);
int childWidth=last.getWidth();
mContainer.removeViewInLayout(last);
recycleBin.addScrapView(last);
mFirstPosition--;
int next=mSelectedPosition-1;
setSelectedPositionInt(next);
offsetChildrenLeftAndRight(childWidth);
View child=mContainer.getChildAt(mSelectedPosition-mFirstPosition);
int childLeft=child.getLeft();
int scrollX=getScrollX();
//int childWidth=child.getMeasuredWidth();
if (scrollX!=childLeft)
{
Log.e(TAG,"Block Layout?: "+((Boolean)mBlockLayoutRequests).toString());
Log.e(TAG,"scrollListItem Adjusted Scroll to: "+((Integer) childLeft).toString());
smoothScrollTo(childLeft,0);
mContainer.invalidate();
}
// View child=mContainer.getChildAt(0);
// scrollTo(child.getMeasuredWidth()*(mWindowSize-1)/2,0);
}
else {
int next=mSelectedPosition-1;
setSelectedPositionInt(next);
View child=mContainer.getChildAt(mSelectedPosition-mFirstPosition);
smoothScrollTo(child.getLeft(),0);
}
}
}
}
private View addViewLeft(int position) {
int leftPosition = position - 1;
View view = obtainView(leftPosition);
setupChild(view, leftPosition, FLOW_LEFT, false, false);
return view;
}
private View addViewRight(int position) {
int rightPosition = position + 1;
View view = obtainView(rightPosition);
setupChild(view, rightPosition, FLOW_RIGHT,false, false);
return view;
}
static class SavedState extends BaseSavedState {
long selectedId;
long firstId;
int position;
/**
* Constructor called from {@link AbsListView#onSaveInstanceState()}
*/
SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(Parcel in) {
super(in);
selectedId = in.readLong();
firstId = in.readLong();
position = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeLong(selectedId);
out.writeLong(firstId);
out.writeInt(position);
}
@Override
public String toString() {
return "AbsListView.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " selectedId=" + selectedId
+ " firstId=" + firstId
+ " viewTop="
+ " position=" + position
+ "}";
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
long selectedId = getSelectedItemId();
ss.selectedId = selectedId;
ss.position=getSelectedItemPosition();
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mDataChanged = true;
mNeedSync = true;
mSyncRowId = ss.selectedId;
mSyncPosition = ss.position;
mSyncMode = SYNC_SELECTED_POSITION;
requestLayout();
}
public void setAdapter(ListAdapter adapter,int position) {
if (null != mAdapter) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
resetList();
mRecycler.clear();
mAdapter = adapter;
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
mLayoutMode=LAYOUT_SET_SELECTION;
setSelectedPositionInt(position);
mNeedLayout=true;
requestLayout();
}
}
private int mMeasuredHeight;
private int mMeasuredWidth;
//private boolean mAdjustScroll=false;
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//We handle adjusting children in scrollListItemsTo...no need to go through a layout pass here.
if (changed||mNeedLayout) {
mMeasuredHeight=getMeasuredHeight();
mMeasuredWidth=getMeasuredWidth();
mNeedLayout=false;
layoutChildren();
}
//View first=mContainer.getChildAt(0);
View child=mContainer.getChildAt(mSelectedPosition-mFirstPosition);
if (child!=null) {
int childLeft=child.getLeft();
int scrollX=getScrollX();
//int childWidth=child.getMeasuredWidth();
if (scrollX!=childLeft)
{
Log.e(TAG,"Block Layout?: "+((Boolean)mBlockLayoutRequests).toString());
Log.e(TAG,"onLayout Adjusted Scroll to: "+((Integer) childLeft).toString());
scrollTo(childLeft,0);
}
}
}
static final int NO_POSITION = -1;
public void setSelection(int selection){
mSelectedPosition = selection;
requestLayout();
}
void setSelectedPositionInt(int position) {
mSelectedPosition = position;
if (mObserver!=null) mObserver.onSelectionChanged(position);
mSelectedRowId = getItemIdAtPosition(position);
}
public long getSelectedItemId() {
return mSelectedRowId;
}
public long getItemIdAtPosition(int position) {
ListAdapter adapter=mAdapter;
return (adapter == null || position < 0) ? AdapterView.INVALID_ROW_ID : adapter.getItemId(position);
}
public int getSelectedItemPosition() {
return mSelectedPosition;
}
void resetList() {
mContainer.removeAllViewsInLayout();
mFirstPosition = 0;
mDataChanged = false;
mNeedSync = false;
mSelectedPosition=AdapterView.INVALID_POSITION;
invalidate();
}
RecycleBin mRecycler=new RecycleBin();
View obtainView(int position) {
View scrapView;
scrapView = mRecycler.getScrapView(position);
View child;
if (scrapView != null) {
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP,
position, -1);
}
child = mAdapter.getView(position, scrapView, this);
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW,
position, mContainer.getChildCount());
}
if (child != scrapView) {
mRecycler.addScrapView(scrapView);
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
position, -1);
}
}
} else {
child = mAdapter.getView(position, null, this);
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW,
position, mContainer.getChildCount());
}
}
return child;
}
class RecycleBin {
private RecyclerListener mRecyclerListener;
/**
* The position of the first view stored in mActiveViews.
*/
private int mFirstActivePosition;
/**
* Views that were on screen at the start of layout. This array is populated at the start of
* layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
* Views in mActiveViews represent a contiguous range of Views, with position of the first
* view store in mFirstActivePosition.
*/
private View[] mActiveViews = new View[0];
/**
* Unsorted views that can be used by the adapter as a convert view.
*/
private ArrayList<View> mScrapViews;
RecycleBin() {
mScrapViews = new ArrayList<View>();
}
public void setViewTypeCount(int viewTypeCount) {
if (viewTypeCount < 1) {
throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
}
//noinspection unchecked
}
public boolean shouldRecycleViewType(int viewType) {
return viewType >= 0;
}
/**
* Clears the scrap heap.
*/
void clear() {
final ArrayList<View> scrap = mScrapViews;
final int scrapCount = scrap.size();
for (int i = 0; i < scrapCount; i++) {
mContainer.removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
}
}
/**
* Fill ActiveViews with all of the children of the AbsListView.
*
* @param childCount The minimum number of views mActiveViews should hold
* @param firstActivePosition The position of the first view that will be stored in
* mActiveViews
*/
void fillActiveViews(int childCount, int firstActivePosition) {
if (mActiveViews.length < childCount) {
mActiveViews = new View[childCount];
}
mFirstActivePosition = firstActivePosition;
final View[] activeViews = mActiveViews;
for (int i = 0; i < childCount; i++) {
View child = mContainer.getChildAt(i);
activeViews[i] = child;
}
}
/**
* Get the view corresponding to the specified position. The view will be removed from
* mActiveViews if it is found.
*
* @param position The position to look up in mActiveViews
* @return The view if it is found, null otherwise
*/
View getActiveView(int position) {
int index = position - mFirstActivePosition;
final View[] activeViews = mActiveViews;
if (index >=0 && index < activeViews.length) {
final View match = activeViews[index];
activeViews[index] = null;
return match;
}
return null;
}
/**
* @return A view from the ScrapViews collection. These are unordered.
*/
View getScrapView(int position) {
ArrayList<View> scrapViews;
scrapViews = mScrapViews;
int size = scrapViews.size();
if (size > 0) {
return scrapViews.remove(size - 1);
} else {
return null;
}
}
/**
* Put a view into the ScapViews list. These views are unordered.
*
* @param scrap The view to add
*/
void addScrapView(View scrap) {
if (scrap == null) {
return;
}
mScrapViews.add(scrap);
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}
/**
* Move all views remaining in mActiveViews to mScrapViews.
*/
void scrapActiveViews() {
final View[] activeViews = mActiveViews;
final boolean hasListener = mRecyclerListener != null;
ArrayList<View> scrapViews = mScrapViews;
final int count = activeViews.length;
for (int i = 0; i < count; ++i) {
final View victim = activeViews[i];
if (victim != null) {
activeViews[i] = null;
scrapViews = mScrapViews;
scrapViews.add(victim);
if (hasListener) {
mRecyclerListener.onMovedToScrapHeap(victim);
}
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(victim,
ViewDebug.RecyclerTraceType.MOVE_FROM_ACTIVE_TO_SCRAP_HEAP,
mFirstActivePosition + i, -1);
}
}
}
pruneScrapViews();
}
/**
* Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
* (This can happen if an adapter does not recycle its views).
*/
private void pruneScrapViews() {
final int maxViews = mActiveViews.length;
final ArrayList<View> scrapViews = mScrapViews;
final ArrayList<View> scrapPile = scrapViews;
int size = scrapPile.size();
final int extras = size - maxViews;
size--;
for (int j = 0; j < extras; j++) {
mContainer.removeDetachedView(scrapPile.remove(size--), false);
}
}
/**
* Puts all views in the scrap heap into the supplied list.
*/
void reclaimScrapViews(List<View> views) {
views.addAll(mScrapViews);
}
}
private int mSyncMode;
class AdapterDataSetObserver extends DataSetObserver {
private Parcelable mInstanceState = null;
@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
// Detect the case where a cursor that was previously invalidated has
// been repopulated with new data.
if (mAdapter.hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
HorizontalSnapView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
layoutChildren();
}
private void rememberSyncState() {
if (mContainer.getChildCount() > 0) {
mNeedSync = true;
if (mSelectedPosition >= 0) {
mSyncRowId = mSelectedRowId;
mSyncMode = SYNC_SELECTED_POSITION;
mSyncPosition = mSelectedPosition;
}
}
else {
// Sync the based on the offset of the first view
ListAdapter adapter = mAdapter;
if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
mSyncRowId = adapter.getItemId(mFirstPosition);
} else {
mSyncRowId = NO_ID;
}
mSyncPosition = mFirstPosition;
mSyncMode = SYNC_FIRST_POSITION;
}
}
@Override
public void onInvalidated() {
mDataChanged = true;
if (mAdapter.hasStableIds()) {
// Remember the current state for the case where our hosting activity is being
// stopped and later restarted
mInstanceState = HorizontalSnapView.this.onSaveInstanceState();
}
// Data is invalid so we should reset our state
mOldItemCount = mItemCount;
mItemCount = 0;
mSelectedPosition = AdapterView.INVALID_POSITION;
mSelectedRowId =AdapterView.INVALID_ROW_ID;
mNeedSync = false;
layoutChildren();
}
public void clearSavedState() {
mInstanceState = null;
}
}
}