Created
January 22, 2014 15:48
-
-
Save sergiandreplace/8561120 to your computer and use it in GitHub Desktop.
An horizontal listview working just as a simple listview
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import android.database.DataSetObserver; | |
import android.graphics.Rect; | |
import android.util.AttributeSet; | |
import android.view.GestureDetector; | |
import android.view.GestureDetector.OnGestureListener; | |
import android.view.MotionEvent; | |
import android.view.View; | |
import android.widget.AdapterView; | |
import android.widget.ListAdapter; | |
import android.widget.Scroller; | |
public class HorizontalListView extends AdapterView<ListAdapter> { | |
public boolean mAlwaysOverrideTouch = true; | |
protected ListAdapter mAdapter; | |
private int mLeftViewIndex = -1; | |
private int mRightViewIndex = 0; | |
protected int mCurrentX; | |
protected int mNextX; | |
private int mMaxX = Integer.MAX_VALUE; | |
private int mDisplayOffset = 0; | |
protected Scroller mScroller; | |
private GestureDetector mGesture; | |
private Queue<View> mRemovedViewQueue = new LinkedList<View>(); | |
private OnItemSelectedListener mOnItemSelected; | |
private OnItemClickListener mOnItemClicked; | |
private OnItemLongClickListener mOnItemLongClicked; | |
private boolean mDataChanged = false; | |
public HorizontalListView(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
initView(); | |
} | |
private synchronized void initView() { | |
mLeftViewIndex = -1; | |
mRightViewIndex = 0; | |
mDisplayOffset = 0; | |
mCurrentX = 0; | |
mNextX = 0; | |
mMaxX = Integer.MAX_VALUE; | |
mScroller = new Scroller(getContext()); | |
mGesture = new GestureDetector(getContext(), mOnGesture); | |
} | |
@Override | |
public void setOnItemSelectedListener(OnItemSelectedListener listener) { | |
mOnItemSelected = listener; | |
} | |
@Override | |
public void setOnItemClickListener(OnItemClickListener listener){ | |
mOnItemClicked = listener; | |
} | |
@Override | |
public void setOnItemLongClickListener(OnItemLongClickListener listener) { | |
mOnItemLongClicked = listener; | |
} | |
private DataSetObserver mDataObserver = new DataSetObserver() { | |
@Override | |
public void onChanged() { | |
synchronized(HorizontalListView.this){ | |
mDataChanged = true; | |
} | |
invalidate(); | |
requestLayout(); | |
} | |
@Override | |
public void onInvalidated() { | |
reset(); | |
invalidate(); | |
requestLayout(); | |
} | |
}; | |
@Override | |
public ListAdapter getAdapter() { | |
return mAdapter; | |
} | |
@Override | |
public View getSelectedView() { | |
//TODO: implement | |
return null; | |
} | |
@Override | |
public void setAdapter(ListAdapter adapter) { | |
if(mAdapter != null) { | |
mAdapter.unregisterDataSetObserver(mDataObserver); | |
} | |
mAdapter = adapter; | |
mAdapter.registerDataSetObserver(mDataObserver); | |
reset(); | |
} | |
private synchronized void reset(){ | |
initView(); | |
removeAllViewsInLayout(); | |
requestLayout(); | |
} | |
@Override | |
public void setSelection(int position) { | |
//TODO: implement | |
} | |
private void addAndMeasureChild(final View child, int viewPos) { | |
LayoutParams params = child.getLayoutParams(); | |
if(params == null) { | |
params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); | |
} | |
addViewInLayout(child, viewPos, params, true); | |
child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), | |
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST)); | |
} | |
@Override | |
protected synchronized void onLayout(boolean changed, int left, int top, int right, int bottom) { | |
super.onLayout(changed, left, top, right, bottom); | |
if(mAdapter == null){ | |
return; | |
} | |
if(mDataChanged){ | |
int oldCurrentX = mCurrentX; | |
initView(); | |
removeAllViewsInLayout(); | |
mNextX = oldCurrentX; | |
mDataChanged = false; | |
} | |
if(mScroller.computeScrollOffset()){ | |
int scrollx = mScroller.getCurrX(); | |
mNextX = scrollx; | |
} | |
if(mNextX <= 0){ | |
mNextX = 0; | |
mScroller.forceFinished(true); | |
} | |
if(mNextX >= mMaxX) { | |
mNextX = mMaxX; | |
mScroller.forceFinished(true); | |
} | |
int dx = mCurrentX - mNextX; | |
removeNonVisibleItems(dx); | |
fillList(dx); | |
positionItems(dx); | |
mCurrentX = mNextX; | |
if(!mScroller.isFinished()){ | |
post(new Runnable(){ | |
@Override | |
public void run() { | |
requestLayout(); | |
} | |
}); | |
} | |
} | |
private void fillList(final int dx) { | |
int edge = 0; | |
View child = getChildAt(getChildCount()-1); | |
if(child != null) { | |
edge = child.getRight(); | |
} | |
fillListRight(edge, dx); | |
edge = 0; | |
child = getChildAt(0); | |
if(child != null) { | |
edge = child.getLeft(); | |
} | |
fillListLeft(edge, dx); | |
} | |
private void fillListRight(int rightEdge, final int dx) { | |
while(rightEdge + dx < getWidth() && mRightViewIndex < mAdapter.getCount()) { | |
View child = mAdapter.getView(mRightViewIndex, mRemovedViewQueue.poll(), this); | |
addAndMeasureChild(child, -1); | |
rightEdge += child.getMeasuredWidth(); | |
if(mRightViewIndex == mAdapter.getCount()-1) { | |
mMaxX = mCurrentX + rightEdge - getWidth(); | |
} | |
if (mMaxX < 0) { | |
mMaxX = 0; | |
} | |
mRightViewIndex++; | |
} | |
} | |
private void fillListLeft(int leftEdge, final int dx) { | |
while(leftEdge + dx > 0 && mLeftViewIndex >= 0) { | |
View child = mAdapter.getView(mLeftViewIndex, mRemovedViewQueue.poll(), this); | |
addAndMeasureChild(child, 0); | |
leftEdge -= child.getMeasuredWidth(); | |
mLeftViewIndex--; | |
mDisplayOffset -= child.getMeasuredWidth(); | |
} | |
} | |
private void removeNonVisibleItems(final int dx) { | |
View child = getChildAt(0); | |
while(child != null && child.getRight() + dx <= 0) { | |
mDisplayOffset += child.getMeasuredWidth(); | |
mRemovedViewQueue.offer(child); | |
removeViewInLayout(child); | |
mLeftViewIndex++; | |
child = getChildAt(0); | |
} | |
child = getChildAt(getChildCount()-1); | |
while(child != null && child.getLeft() + dx >= getWidth()) { | |
mRemovedViewQueue.offer(child); | |
removeViewInLayout(child); | |
mRightViewIndex--; | |
child = getChildAt(getChildCount()-1); | |
} | |
} | |
private void positionItems(final int dx) { | |
if(getChildCount() > 0){ | |
mDisplayOffset += dx; | |
int left = mDisplayOffset; | |
for(int i=0;i<getChildCount();i++){ | |
View child = getChildAt(i); | |
int childWidth = child.getMeasuredWidth(); | |
child.layout(left, 0, left + childWidth, child.getMeasuredHeight()); | |
left += childWidth + child.getPaddingRight(); | |
} | |
} | |
} | |
public synchronized void scrollTo(int x) { | |
mScroller.startScroll(mNextX, 0, x - mNextX, 0); | |
requestLayout(); | |
} | |
@Override | |
public boolean dispatchTouchEvent(MotionEvent ev) { | |
boolean handled = super.dispatchTouchEvent(ev); | |
handled |= mGesture.onTouchEvent(ev); | |
getParent().requestDisallowInterceptTouchEvent(true); | |
return handled; | |
} | |
protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, | |
float velocityY) { | |
synchronized(HorizontalListView.this){ | |
mScroller.fling(mNextX, 0, (int)-velocityX, 0, 0, mMaxX, 0, 0); | |
} | |
requestLayout(); | |
return true; | |
} | |
protected boolean onDown(MotionEvent e) { | |
mScroller.forceFinished(true); | |
return true; | |
} | |
private OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() { | |
@Override | |
public boolean onDown(MotionEvent e) { | |
return HorizontalListView.this.onDown(e); | |
} | |
@Override | |
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { | |
return HorizontalListView.this.onFling(e1, e2, velocityX, velocityY); | |
} | |
@Override | |
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { | |
synchronized(HorizontalListView.this){ | |
mNextX += (int)distanceX; | |
} | |
requestLayout(); | |
return true; | |
} | |
@Override | |
public boolean onSingleTapConfirmed(MotionEvent e) { | |
for(int i=0;i<getChildCount();i++){ | |
View child = getChildAt(i); | |
if (isEventWithinView(e, child)) { | |
if(mOnItemClicked != null){ | |
mOnItemClicked.onItemClick(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId( mLeftViewIndex + 1 + i )); | |
} | |
if(mOnItemSelected != null){ | |
mOnItemSelected.onItemSelected(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId( mLeftViewIndex + 1 + i )); | |
} | |
break; | |
} | |
} | |
return true; | |
} | |
@Override | |
public void onLongPress(MotionEvent e) { | |
int childCount = getChildCount(); | |
for (int i = 0; i < childCount; i++) { | |
View child = getChildAt(i); | |
if (isEventWithinView(e, child)) { | |
if (mOnItemLongClicked != null) { | |
mOnItemLongClicked.onItemLongClick(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId(mLeftViewIndex + 1 + i)); | |
} | |
break; | |
} | |
} | |
} | |
private boolean isEventWithinView(MotionEvent e, View child) { | |
Rect viewRect = new Rect(); | |
int[] childPosition = new int[2]; | |
child.getLocationOnScreen(childPosition); | |
int left = childPosition[0]; | |
int right = left + child.getWidth(); | |
int top = childPosition[1]; | |
int bottom = top + child.getHeight(); | |
viewRect.set(left, top, right, bottom); | |
return viewRect.contains((int) e.getRawX(), (int) e.getRawY()); | |
} | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
from @companygarcia. Thanks!