《Android 自己动手写ListView学习其原理 1 显示第一屏Item》
《Android 自己动手写ListView学习其原理 2 上下滚动》
本篇主要是添加ItemClick,ItemLongClick,View复用,都比较简单前两个点击事件是在onTouchEvent里面处理,View复用与onLayout先关的时候使用,代码里面注释比较完整,直接上代码把。
一、有图有真相

二、ItemClick 与 ItemLongClick
1. 启动LongClick子线程,在onTouchEvent Down事件时执行
/**
* 开启异步线程,条件允许时调用LongClickListener
*/
private void startLongPressCheck() {
// 创建子线程
if (mLongPressRunnable == null) {
mLongPressRunnable = new Runnable() {
@Override
public void run() {
if (mTouchMode == TOUCH_MODE_DOWN) {
final int index = getContainingChildIndex(
mTouchStartX, mTouchStartY);
if (index != INVALID_INDEX) {
longClickChild(index);
}
}
}
};
}
// ViewConfiguration.getLongPressTimeout() 获取系统配置的长按的时间间隔
// 如果点击已经超过长按要求时间,才开始执行此线程
postDelayed(mLongPressRunnable, ViewConfiguration.getLongPressTimeout());
}
/**
* 调用ItemLongClickListener提供点击位置等信息
*
* @param index Item索引值
*/
private void longClickChild(final int index) {
final View itemView = getChildAt(index);
final int position = mFirstItemPosition + index;
final long id = mAdapter.getItemId(position);
// 从父类获取绑定的OnItemLongClickListener
OnItemLongClickListener listener = getOnItemLongClickListener();
if (listener != null) {
listener.onItemLongClick(this, itemView, position, id);
}
}2. itemClick,在onTouchEvent UP事件中触发(只有手指抬起时才会触发)
case MotionEvent.ACTION_UP:
// 如果当前触摸没有触发滚动,状态依然是DOWN
// 说明是点击某一个Item
if (mTouchMode == TOUCH_MODE_DOWN) {
clickChildAt((int)event.getX(), y);
}具体执行
/**
* 调用ItemClickListener提供当前点击位置
*
* @param x 触摸点X轴值
* @param y 触摸点Y轴值
*/
private void clickChildAt(int x, int y) {
// 触摸点在当前显示所有Item中哪一个
final int itemIndex = getContainingChildIndex(x, y);
if (itemIndex != INVALID_INDEX) {
final View itemView = getChildAt(itemIndex);
// 当前Item在ListView所有Item中的位置
final int position = mFirstItemPosition + itemIndex;
final long id = mAdapter.getItemId(position);
// 调用父类方法,会触发ListView ItemClickListener
performItemClick(itemView, position, id);
}
}3. 如何使用呢? 直接在Activity中添加相关内部类即可,和ListView自身的itemclick和longclick相同
// item Click
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
String itemName = (String) customAdapter.getItem(position);
Toast.makeText(getBaseContext(), "点击 " + itemName,
Toast.LENGTH_SHORT).show();
}
});
listView.setOnItemLongClickListener(new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
String itemName = (String) customAdapter.getItem(position);
Toast.makeText(getBaseContext(), "长按 " + itemName,
Toast.LENGTH_SHORT).show();
return true;
}
});三、视图复用
如何向知道ListView到底如何复用视图的,可以看看之前的博文《Android ListView使用Holder优化原理》,下面实现的是一个简化版,其复用原理都是一样的,把移除屏幕的视图都保存起来,如果有需要新视图对象可以从保存视图中取。
1. 新来看数据结构
// View复用当前仅支持一种类型Item视图复用
// 想更多了解ListView视图如何复用可以看AbsListView内部类RecycleBin
private final LinkedList<View> mCachedItemViews = new LinkedList<View>(); /**
* 删除当前已经移除可视范围的Item View
*
* @param offset 可视区域偏移量
*/
private void removeNonVisibleViews(final int offset) {
int childCount = getChildCount();
/** ListView向上滚动,删除顶部移除可视区域的所有视图 **/
// 不在ListView底部,子视图大于1
if (mLastItemPosition != mAdapter.getCount() -1 && childCount > 1) {
View firstChild = getChildAt(0);
// 通过第二条件判断当前最上面的视图是否被移除可是区域
while (firstChild != null && firstChild.getBottom() + offset < 0) {
// 既然顶部第一个视图已经移除可视区域从当前ViewGroup中删除掉
removeViewInLayout(firstChild);
// 用于下次判断,是否当前顶部还有需要移除的视图
childCount--;
// View对象回收,目的是为了复用
mCachedItemViews.addLast(firstChild);
// 既然最上面的视图被干掉了,当前ListView第一个显示视图也需要+1
mFirstItemPosition++;
// 同上更新
mListTopOffset += firstChild.getMeasuredHeight();
// 为下一次while遍历获取参数
if (childCount > 1) {
// 当前已经删除第一个,再接着去除删除后剩余的第一个
firstChild = getChildAt(0);
} else {
// 没啦
firstChild = null;
}
}
}
/** ListView向下滚动,删除底部移除可视区域的所有视图 **/
// 与上面操作一样,只是方向相反一个顶部操作一个底部操作
if (mFirstItemPosition != 0 && childCount > 1) {
View lastChild = getChildAt(childCount - 1);
while (lastChild != null && lastChild.getTop() + offset > getHeight()) {
removeViewInLayout(lastChild);
childCount--;
mCachedItemViews.addLast(lastChild);
mLastItemPosition--;
if (childCount > 1) {
lastChild = getChildAt(childCount - 1);
} else {
lastChild = null;
}
}
}
}
/**
* 获取一个可以复用的Item View
*
* @return view 可以复用的视图或者null
*/
private View getCachedView() {
if (mCachedItemViews.size() != 0) {
return mCachedItemViews.removeFirst();
}
return null;
}3. 怎么用呢?removeNonVisibleViews在onLayout中使用
@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;
}
// 当前ListView没有任何子视图(Item),所以依次在从上向下填充子视图
if (getChildCount() == 0) {
mLastItemPosition = -1;
// add and measure
fillListDown(mListTop, 0);
} else {
final int offset = mListTop + mListTopOffset - getChildAt(0).getTop();
// 移除可视区域的都干掉
removeNonVisibleViews(offset);
fillList(offset);
}
// layout,添加测量完后,获取视图摆放位置
positioinItems();
// draw, 上面子视图都添加完了,重绘布局把子视图绘制出来吧
invalidate();
}4. getCachedView在fillUp与fillDown中使用,只贴出fillDown,fillUp使用方式相同
/**
* 向当前最后一个子视图下面添加,填充到当前ListView底部无再可填充区域为止
*
* @param bottomEdge 当前最后一个子视图底部边界值
* @param offset 显示区域偏移量
*/
private void fillListDown(int bottomEdge, int offset) {
while (bottomEdge + offset < getHeight() && mLastItemPosition < mAdapter.getCount() - 1) {
// 现在添加的视图时当前子视图后面,所以位置+1
mLastItemPosition++;
// 数据和视图通过Adapter适配,此处从Adapter获取视图。
// 第二个参数传入复用的View对象,先出入null,之后再添加View对象复用机制
View newBottomChild = mAdapter.getView(mLastItemPosition, getCachedView(), this);
// **具体添加视图处理
addAndMeasureChild(newBottomChild, LAYOUT_MODE_BELOW);
// 添加一个子视图(Item),随之底部边界也发生改变
bottomEdge += newBottomChild.getMeasuredHeight();
}
}四、源码下载
参考资料:
Making your own 3D list – Part 1
转载请注明出处:http://blog.csdn.net/love_world_/article/details/8744423
版权声明:本文为love_world_原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。