先上圖:
解決的問題有兩個:
1)實現水平滑動的ListView。重寫AdapterView,上代碼:
-
package com.liucanwen.horizontallistview.view;
-
-
import java.util.LinkedList;
-
import java.util.Queue;
-
-
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.View.MeasureSpec;
-
import android.view.MotionEvent;
-
import android.view.View;
-
import android.widget.AdapterView;
-
import android.widget.ListAdapter;
-
import android.widget.Scroller;
-
-
/**
-
* 重寫ListView,以達到水平滑動
-
*/
-
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(
-
AdapterView.OnItemSelectedListener listener)
-
{
-
mOnItemSelected = listener;
-
}
-
-
@Override
-
public void setOnItemClickListener(AdapterView.OnItemClickListener listener)
-
{
-
mOnItemClicked = listener;
-
}
-
-
@Override
-
public void setOnItemLongClickListener(
-
AdapterView.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.FILL_PARENT,
-
LayoutParams.FILL_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);
-
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());
-
}
-
};
-
-
}
2)第一步實現了水平滑動,往往我們會把這個水平ListView放到ScrollView裏面(見截圖實現),而這兩個控件恰好滑動會有衝突,滑動水平ListView時會有卡頓,因此重寫ScrollView,以達到流暢滑動:
-
package com.liucanwen.horizontallistview.view;
-
-
import android.content.Context;
-
import android.util.AttributeSet;
-
import android.view.GestureDetector;
-
import android.view.GestureDetector.SimpleOnGestureListener;
-
import android.view.MotionEvent;
-
import android.view.View;
-
import android.widget.ScrollView;
-
-
/**
-
* 重寫ScrollView,以解決ScrollView與水平listView滑動時衝突
-
*/
-
public class MyScrollView extends ScrollView
-
{
-
private GestureDetector mGestureDetector;
-
View.OnTouchListener mGestureListener;
-
-
public MyScrollView(Context context, AttributeSet attrs)
-
{
-
super(context, attrs);
-
mGestureDetector = new GestureDetector(new YScrollDetector());
-
setFadingEdgeLength(0);
-
}
-
-
@Override
-
public boolean onInterceptTouchEvent(MotionEvent ev)
-
{
-
return super.onInterceptTouchEvent(ev)
-
&& mGestureDetector.onTouchEvent(ev);
-
}
-
-
class YScrollDetector extends SimpleOnGestureListener
-
{
-
@Override
-
public boolean onScroll(MotionEvent e1, MotionEvent e2,
-
float distanceX, float distanceY)
-
{
-
if (Math.abs(distanceY) > Math.abs(distanceX))
-
{
-
return true;
-
}
-
return false;
-
}
-
}
-
}