11
/* * HorizontalListView.java v1.5 * * * The MIT License * Copyright (c) 2011 Paul Soucy (paul@dev-smart.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ package com.devsmart.demo.fragment; 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.util.Log; 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 static String TAG = "dd"; public boolean mAlwaysOverrideTouch = false; protected ListAdapter mAdapter; // 可视区域开始左视图索引左 private int mLeftViewIndex = -1; // 可视区域右视图索引左,从1开始计算,当前屏幕显示5个 则为5 private int mRightViewIndex = 0; /** * 当前x坐标 */ protected int mCurrentX; /** * 需要移动到的下一个x坐标 */ protected int mNextX; private int mMaxX = Integer.MAX_VALUE; /** * 已经移动的x距离,从左侧第一个可用视图开始计算 */ 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); log("initView"); } private void log(String part) { Log.i(TAG,"part="+part+ " mLeftViewIndex=" + mLeftViewIndex + " mRightViewIndex=" + mRightViewIndex + " mDisplayOffset=" + mDisplayOffset + " mCurrentX=" + mCurrentX + " mNextX=" + mNextX + " mDataChanged=" + mDataChanged); } @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; } /** * DatasetObserver主要是当注册它的cursor中发生变动时会调用其中的方法,让用户做一些界面刷新等操作。 * 首先cursor通过registerDataSetObserver()注册DatasetObserver * 当cursor数据有变动时,例如调用了cursor的requery * (),会调用cursor的onChanged通知用户cursor中的内容有变动,用户可以在onChanged里做一些刷新界面的操作。 * 一般会在onChanged里调用notifyDataSetChanged通知framework * ,framework收到通知会调用CursorAdapter的getView来做界面刷新等工作。 * 用途:cursor中的数据有变动时通知用户刷新界面。 */ private DataSetObserver mDataObserver = new DataSetObserver() { @Override public void onChanged() { synchronized (HorizontalListView.this) { mDataChanged = true; } invalidate(); // 当一个View需要parent view重新调用他的onMeasure // onLayout来对重新设置自己位置时,特别是当你修改了view的layoutparameter,它的值还没能应用到view上,调用这个方法 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 } /** * measue(int widthMeasureSpec, int heightMeasureSpec)中的两个参数, * 这两个参数分别是父视图提供的测量规格,当父视图调用子视图的measure函数对子视图进行测量时, * 会传入这两个参数,通过这两个参数以及子视图本身的LayoutParams来共同决定子视图的测量规格 * MeasureSpec参数的值为int型,分为高32位和低16为 * ,高32位保存的是specMode,低16位表示specSize,specMode分三种: * * 1、MeasureSpec.UNSPECIFIED,父视图不对子视图施加任何限制,子视图可以得到任意想要的大小; * * 2、MeasureSpec.EXACTLY,父视图希望子视图的大小是specSize中指定的大小; * * 3、MeasureSpec.AT_MOST,子视图的大小最多是specSize中的大小。 * * @param child * @param viewPos */ 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)); } /** * layout的过程就是确定View在屏幕上显示的具体位置,在代码中就是设置其成员变量mLeft,mTop,mRight,mBottom的值 * ,这几个值构成的矩形区域就是该View显示的位置,不过这里的具体位置都是相对与父视图的位置。 * layout过程必须要依靠measure计算出来的mMeasuredWidth和mMeasuredHeight来决定视图的显示大小吗?事实并非如此 * , layout过程中的4个参数l, t, r, b完全可以由视图设计者任意指定,而最终视图的布局位置和大小完全由这4个参数决定, * measure过程得到的mMeasuredWidth和mMeasuredHeight提供了视图大小的值 * ,但我们完全可以不使用这两个值,可见measure过程并不是必须的。\\ * getWidth()、getHeight()和getMeasuredWidth * ()、getMeasuredHeight()这两对函数之间的区别,getMeasuredWidth()、getMeasuredHeight() * 返回的是measure过程得到的mMeasuredWidth和mMeasuredHeight的值 * ,而getWidth()和getHeight()返回的是mRight - mLeft和mBottom - mTop的值 * 总结:整个layout过程比较容易理解, * 一般情况下layout过程会参考measure过程中计算得到的mMeasuredWidth和mMeasuredHeight来安排子视图在父视图中显示的位置 * ,但这不是必须的,measure过程得到的结果可能完全没有实际用处,特别是对于一些自定义的ViewGroup,其子视图的个数、位置和大小都是固定的 * ,这时候我们可以忽略整个measure过程,只在layout函数中传入的4个参数来安排每个子视图的具体位置。 */ @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); /** * layout 子视图屏幕显示位置 */ positionItems(dx); mCurrentX = mNextX; if (!mScroller.isFinished()) { post(new Runnable() { @Override public void run() { requestLayout(); } }); } log("onLayout"); } 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); log("fillList"); } /** * 填充右边缘 * * @param rightEdge * @param 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++; log("fillListRight"); } } /** * 填充左边缘 * * @param leftEdge * @param dx */ 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(); log("fillListLeft"); } } /** * 移除无用子视图 * * @param dx */ 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); log("removeNonVisibleItems"); } child = getChildAt(getChildCount() - 1); while (child != null && child.getLeft() + dx >= getWidth()) { mRemovedViewQueue.offer(child); removeViewInLayout(child); mRightViewIndex--; child = getChildAt(getChildCount() - 1); log("removeNonVisibleItems"); } } 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(); log("positionItems"); } } } public synchronized void scrollTo(int x) { mScroller.startScroll(mNextX, 0, x - mNextX, 0); log("scrollTo"); 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) { /** * 开始滑动 mnext :开始滑动x坐标 滑动速度,px/s mmaxx:做多滑动限制 */ mScroller.fling(mNextX, 0, (int) -velocityX, 0, 0, mMaxX, 0, 0); log("onFling"); } 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()); } }; }
/*
* HorizontalListView.java v1.5
*
*
* The MIT License
* Copyright (c) 2011 Paul Soucy (paul@dev-smart.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.devsmart.demo.fragment;
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.util.Log;
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 static String TAG = "dd";
public boolean mAlwaysOverrideTouch = false;
protected ListAdapter mAdapter;
// 可视区域开始左视图索引左
private int mLeftViewIndex = -1;
// 可视区域右视图索引左,从1开始计算,当前屏幕显示5个 则为5
private int mRightViewIndex = 0;
/**
* 当前x坐标
*/
protected int mCurrentX;
/**
* 需要移动到的下一个x坐标
*/
protected int mNextX;
private int mMaxX = Integer.MAX_VALUE;
/**
* 已经移动的x距离,从左侧第一个可用视图开始计算
*/
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);
log("initView");
}
private void log(String part) {
Log.i(TAG,"part="+part+ " mLeftViewIndex=" + mLeftViewIndex + " mRightViewIndex="
+ mRightViewIndex + " mDisplayOffset=" + mDisplayOffset
+ " mCurrentX=" + mCurrentX + " mNextX=" + mNextX
+ " mDataChanged=" + mDataChanged);
}
@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;
}
/**
* DatasetObserver主要是当注册它的cursor中发生变动时会调用其中的方法,让用户做一些界面刷新等操作。
* 首先cursor通过registerDataSetObserver()注册DatasetObserver
* 当cursor数据有变动时,例如调用了cursor的requery
* (),会调用cursor的onChanged通知用户cursor中的内容有变动,用户可以在onChanged里做一些刷新界面的操作。
* 一般会在onChanged里调用notifyDataSetChanged通知framework
* ,framework收到通知会调用CursorAdapter的getView来做界面刷新等工作。
* 用途:cursor中的数据有变动时通知用户刷新界面。
*/
private DataSetObserver mDataObserver = new DataSetObserver() {
@Override
public void onChanged() {
synchronized (HorizontalListView.this) {
mDataChanged = true;
}
invalidate();
// 当一个View需要parent view重新调用他的onMeasure
// onLayout来对重新设置自己位置时,特别是当你修改了view的layoutparameter,它的值还没能应用到view上,调用这个方法
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
}
/**
* measue(int widthMeasureSpec, int heightMeasureSpec)中的两个参数,
* 这两个参数分别是父视图提供的测量规格,当父视图调用子视图的measure函数对子视图进行测量时,
* 会传入这两个参数,通过这两个参数以及子视图本身的LayoutParams来共同决定子视图的测量规格
* MeasureSpec参数的值为int型,分为高32位和低16为
* ,高32位保存的是specMode,低16位表示specSize,specMode分三种:
*
* 1、MeasureSpec.UNSPECIFIED,父视图不对子视图施加任何限制,子视图可以得到任意想要的大小;
*
* 2、MeasureSpec.EXACTLY,父视图希望子视图的大小是specSize中指定的大小;
*
* 3、MeasureSpec.AT_MOST,子视图的大小最多是specSize中的大小。
*
* @param child
* @param viewPos
*/
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));
}
/**
* layout的过程就是确定View在屏幕上显示的具体位置,在代码中就是设置其成员变量mLeft,mTop,mRight,mBottom的值
* ,这几个值构成的矩形区域就是该View显示的位置,不过这里的具体位置都是相对与父视图的位置。
* layout过程必须要依靠measure计算出来的mMeasuredWidth和mMeasuredHeight来决定视图的显示大小吗?事实并非如此
* , layout过程中的4个参数l, t, r, b完全可以由视图设计者任意指定,而最终视图的布局位置和大小完全由这4个参数决定,
* measure过程得到的mMeasuredWidth和mMeasuredHeight提供了视图大小的值
* ,但我们完全可以不使用这两个值,可见measure过程并不是必须的。\\
* getWidth()、getHeight()和getMeasuredWidth
* ()、getMeasuredHeight()这两对函数之间的区别,getMeasuredWidth()、getMeasuredHeight()
* 返回的是measure过程得到的mMeasuredWidth和mMeasuredHeight的值
* ,而getWidth()和getHeight()返回的是mRight - mLeft和mBottom - mTop的值
* 总结:整个layout过程比较容易理解,
* 一般情况下layout过程会参考measure过程中计算得到的mMeasuredWidth和mMeasuredHeight来安排子视图在父视图中显示的位置
* ,但这不是必须的,measure过程得到的结果可能完全没有实际用处,特别是对于一些自定义的ViewGroup,其子视图的个数、位置和大小都是固定的
* ,这时候我们可以忽略整个measure过程,只在layout函数中传入的4个参数来安排每个子视图的具体位置。
*/
@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);
/**
* layout 子视图屏幕显示位置
*/
positionItems(dx);
mCurrentX = mNextX;
if (!mScroller.isFinished()) {
post(new Runnable() {
@Override
public void run() {
requestLayout();
}
});
}
log("onLayout");
}
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);
log("fillList");
}
/**
* 填充右边缘
*
* @param rightEdge
* @param 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++;
log("fillListRight");
}
}
/**
* 填充左边缘
*
* @param leftEdge
* @param dx
*/
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();
log("fillListLeft");
}
}
/**
* 移除无用子视图
*
* @param dx
*/
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);
log("removeNonVisibleItems");
}
child = getChildAt(getChildCount() - 1);
while (child != null && child.getLeft() + dx >= getWidth()) {
mRemovedViewQueue.offer(child);
removeViewInLayout(child);
mRightViewIndex--;
child = getChildAt(getChildCount() - 1);
log("removeNonVisibleItems");
}
}
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();
log("positionItems");
}
}
}
public synchronized void scrollTo(int x) {
mScroller.startScroll(mNextX, 0, x - mNextX, 0);
log("scrollTo");
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) {
/**
* 开始滑动 mnext :开始滑动x坐标 滑动速度,px/s mmaxx:做多滑动限制
*/
mScroller.fling(mNextX, 0, (int) -velocityX, 0, 0, mMaxX, 0, 0);
log("onFling");
}
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());
}
};
}