horizontallistview_perfect

import android.content.Context;import android.database.DataSetObserver;import android.graphics.Rect;import android.text.TextUtils;import android.util.AttributeSet;import android.util.Log;import android.view.GestureDetector;import android.view.GestureDetector.OnGestureListener;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.widget.AdapterView;import android.widget.ListAdapter;import android.widget.Scroller;import java.util.LinkedList;import java.util.Queue;public class HorizontalListView extends AdapterView { static final int LAYOUT_NORMAL = 0x00; static final int LAYOUT_SPECIFIC = 0x04; static final int LAYOUT_FREEZE = 0x08; int mLayoutMode = LAYOUT_NORMAL; public boolean mAlwaysOverrideTouch = true; protected ListAdapter mAdapter; protected Scroller mScroller; private GestureDetector mGesture; private int mLeftViewIndex = -1; private int mRightViewIndex = 0; private int mMaxX = Integer.MAX_VALUE; private int mMinX = 0; protected int mCurrentX; protected int mNextX; private int mDisplayOffset = 0; private Queue mRemovedViewQueue = new LinkedList(); private OnItemSelectedListener mOnItemSelected; private OnItemClickListener mOnItemClicked; private OnItemLongClickListener mOnItemLongClicked; private OnScrollListener mOnScrolled; private boolean mDataChanged = false; private int mFirstPosition = 0; public HorizontalListView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } private synchronized void initView() { mLeftViewIndex = -1; mRightViewIndex = 0; mDisplayOffset = 0; mCurrentX = 0; mNextX = 0; mFirstPosition = 0; mSpecificPosition = 0; mMaxX = Integer.MAX_VALUE; mMinX = Integer.MIN_VALUE; mScroller = new Scroller(getContext()); mGesture = new GestureDetector(getContext(), mOnGesture); } private synchronized void initViewForSpecific() { mLeftViewIndex = mSpecificPosition - 1; mRightViewIndex = mSpecificPosition + 1; mFirstPosition = mSpecificPosition; mDisplayOffset = 0; mCurrentX = 0; mNextX = 0; mMinX = 0; mMinX = Integer.MIN_VALUE; mMaxX = Integer.MAX_VALUE; 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; } public void setOnScrollListener(OnScrollListener listener) { mOnScrolled = listener; } private DataSetObserver mDataObserver = new DataSetObserver() { @Override public void onChanged() { synchronized (HorizontalListView.this) { mDataChanged = true; } invalidate(); requestLayout(); } @Override public void onInvalidated() { reset(); invalidate(); requestLayout(); } }; private int mSpecificPosition; private int mScrollStatus; private boolean mIsCancelOrUp; @Override public ListAdapter getAdapter() { return mAdapter; } @Override public View getSelectedView() { return null; } @Override public void setAdapter(ListAdapter adapter) { if (mAdapter != null) { mAdapter.unregisterDataSetObserver(mDataObserver); } mAdapter = adapter; mAdapter.registerDataSetObserver(mDataObserver); mDataChanged = true; requestLayout(); // reset(); } private synchronized void reset() { initView(); removeAllViewsInLayout(); requestLayout(); } @Override public int getFirstVisiblePosition() { return mFirstPosition; } @Override public int getLastVisiblePosition() { return mFirstPosition + getChildCount() - 1; } public boolean isLayoutRequestedBySelection() { return isFlagContain(mLayoutMode, LAYOUT_SPECIFIC); } public boolean isLayoutRequestByFreeze() { return isFlagContain(mLayoutMode, LAYOUT_FREEZE); } private void addAndMeasureChild(final View child, int viewPos) { LayoutParams params = (LayoutParams) child.getLayoutParams(); if (params == null) { params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); } addViewInLayout(child, viewPos, params, true); int heightMeasureSpec = MeasureSpec.makeMeasureSpec( getMeasuredHeight(), MeasureSpec.EXACTLY); int childHeightSpec = ViewGroup.getChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom(), params.height); int childWidthSpec = 0; if (params.width == LayoutParams.MATCH_PARENT) { childWidthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY); } else if (params.width == LayoutParams.WRAP_CONTENT) { childWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } else { childWidthSpec = MeasureSpec.makeMeasureSpec(params.width, MeasureSpec.EXACTLY); } child.measure(childWidthSpec, childHeightSpec); } @Override protected 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 <= mMinX) { mNextX = mMinX; mScroller.forceFinished(true); } if (mNextX >= mMaxX) { mNextX = mMaxX; mScroller.forceFinished(true); } int dx = 0; if (!isFlagContain(mLayoutMode, LAYOUT_SPECIFIC)) { dx = mCurrentX - mNextX; removeNonVisibleItems(dx); fillList(dx); positionItems(dx); // if (mMinX == 0 || mMaxX == 0) { // mNextX = mCurrentX; // if (!mScroller.isFinished()) { // mScroller.forceFinished(true); // } // } } else { removeAllViewsInLayout(); initViewForSpecific(); fillSpecificV2(mSpecificPosition, mSpecificLeft); positionItems(mSpecificLeft); if (mScroller.computeScrollOffset()) { int finalX = mScroller.getFinalX(); finalX = Math.min(finalX, mMaxX); finalX = Math.max(finalX, mMinX); mScroller.setFinalX(finalX); } mLayoutMode &= ~LAYOUT_SPECIFIC; } mCurrentX = mNextX; if (!mScroller.isFinished()) { post(new Runnable() { @Override public void run() { requestLayout(); } }); } } private void fillSpecificV2(int position, int delta) { View child = mAdapter.getView(position, mRemovedViewQueue.poll(), this); if (child == null) return; addAndMeasureChild(child, -1); if (child != null) { int leftEdge = delta, rightEdge = delta + child.getMeasuredWidth(); if (leftEdge + child.getMeasuredWidth() < 0 || rightEdge > getMeasuredWidth()) { mSpecificLeft = 0; leftEdge = 0; rightEdge = child.getMeasuredWidth(); } fillListRight(rightEdge, 0); int widthDelta = getMeasuredWidth() - getChildrenWidth(0, getChildCount()) - leftEdge; int childCountAfterFillRight = getChildCount(); if (widthDelta > 0) { // move to right edge if not fill right screen. leftEdge += widthDelta; mSpecificLeft += widthDelta; } fillListLeft(leftEdge, 0); widthDelta = leftEdge - getChildrenWidth(0, getChildCount() - childCountAfterFillRight); if (widthDelta > 0) { // move to left edge if not fill left screen. mSpecificLeft -= widthDelta; } } } 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); } int getChildrenWidth(int start, int end) { int allWidth = 0; for (int i = start; i < end; i++) { allWidth += getChildAt(i).getMeasuredWidth(); } return allWidth; } private void fillListRight(int rightEdge, final int dx) { if (mRightViewIndex >= mAdapter.getCount()) { mMaxX = mCurrentX + rightEdge - getWidth(); } 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++; } if (mMaxX < 0) mMaxX = 0; } private void fillListLeft(int leftEdge, final int dx) { if (mLeftViewIndex < 0) { mMinX = mCurrentX + leftEdge; } while (leftEdge + dx > 0 && mLeftViewIndex >= 0) { View child = mAdapter.getView(mLeftViewIndex, mRemovedViewQueue.poll(), this); addAndMeasureChild(child, 0); leftEdge -= child.getMeasuredWidth(); if (mLeftViewIndex == 0) { mMinX = mCurrentX + leftEdge; } mLeftViewIndex--; mDisplayOffset -= child.getMeasuredWidth(); } if (mMinX > 0) mMinX = 0; } 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; } } } /** * scroll to parameter x * * @param x * the destiny */ 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); mIsCancelOrUp = ev.getAction() == MotionEvent.ACTION_CANCEL || ev.getAction() == MotionEvent.ACTION_UP ? true : false; return handled; } public boolean isScrollFinish() { return mScroller.isFinished() && mIsCancelOrUp; } public boolean isCancelOrUpNow() { return mIsCancelOrUp; } void reportScroll(int status) { if (null != mOnScrolled && status != mScrollStatus) { final int first = getFirstVisiblePosition(); final int visibleCount = getLastVisiblePosition() - first; final int count = mAdapter.getCount(); mOnScrolled.onScroll(this, first, visibleCount, count); } } void reportScrollState(int status) { if (null != mOnScrolled && status != mScrollStatus) { mScrollStatus = status; mOnScrolled.onScrollStateChanged(this, status); } } protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { synchronized (HorizontalListView.this) { reportScrollState(OnScrollListener.SCROLL_FLING); mScroller.fling(mNextX, 0, (int) -velocityX, 0, mMinX, mMaxX, 0, 0); mScroller.computeScrollOffset(); } requestLayout(); return true; } protected boolean onDown(MotionEvent e) { mScroller.forceFinished(true); reportScrollState(OnScrollListener.SCROLL_IDLE); return false; } protected boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { synchronized (HorizontalListView.this) { reportScrollState(OnScrollListener.SCROLL_TOUCH_SCROLL); mNextX += (int) distanceX; } requestLayout(); 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) { return HorizontalListView.this.onScroll(e1, e2, distanceX, distanceY); } @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)); return true; } if (mOnItemSelected != null) { mOnItemSelected.onItemSelected(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId(mLeftViewIndex + 1 + i)); return true; } break; } } return false; } @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()); } }; public static interface OnScrollListener { public final static int SCROLL_IDLE = 0; public final static int SCROLL_TOUCH_SCROLL = 1; public final static int SCROLL_FLING = 2; void onScrollStateChanged(AdapterView<?> view, int status); public void onScroll(AdapterView<?> view, int firstVisibleItem, int visibleItemCount, int totalItemCount); } private static boolean isFlagContain(int sourceFlag, int compareFlag) { return (sourceFlag & compareFlag) == compareFlag; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mGesture.onTouchEvent(ev); } @Override public void setSelection(int paramInt) { if (setSelectionFrom(paramInt) >= 0) { requestLayout(); } } private int mSpecificLeft = 0; private int setSelectionFrom(int position) { if (mAdapter == null) return -1; if (position < 0 || position >= mAdapter.getCount()) return -1; if (position >= 0) { mLayoutMode |= LAYOUT_SPECIFIC; mSpecificPosition = position; mSpecificLeft = getPaddingLeft(); } return position; }}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值