Android 自己动手写ListView学习其原理 3 ItemClick,ItemLongClick,View复用



《Android 自己动手写ListView学习其原理 1 显示第一屏Item》

 《Android 自己动手写ListView学习其原理 2 上下滚动》


     本篇主要是添加ItemClick,ItemLongClick,View复用,都比较简单前两个点击事件是在onTouchEvent里面处理,View复用与onLayout先关的时候使用,代码里面注释比较完整,直接上代码把。


一、有图有真相

Android 自己动手写ListView学习其原理 3 ItemClick,ItemLongClick,View复用_第1张图片



二、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>();


2. 数据维护

	/**
	 * 删除当前已经移除可视范围的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





你可能感兴趣的:(Android 自己动手写ListView学习其原理 3 ItemClick,ItemLongClick,View复用)