《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); } }
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); } }
// 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; }
@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(); }
/** * 向当前最后一个子视图下面添加,填充到当前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