【Android界面实现】ZListView,一个最强大的刷新、加载、滑动删除的ListView控件(二)

    转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992  

    我们接着上篇的文章说,在前一篇文章中,我们学习了ZListView的使用,这一篇就开始说一些干货了,本篇文章将介绍ZListView的实现原理。

    其实说是ZListView的实现原理,不如说是ZSwipeItem的实现原理,因为ZSwipeItem才是滑动的关键所在。

    ZSwipeItem的滑动,主要是通过ViewDragHelper这个类实现的。在接触这个项目之前,我没听过,也从来碰到过这个类,ViewDragHelper是v4包中的一个帮助类,文档中是这样介绍的:

ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number of useful operations and state tracking for allowing a user to drag and reposition views within their parent ViewGroup.
    看明白的吧?ViewDragHelper就是专门用来帮助自定义我们的ViewGroup的工具类,它提供了一些非常有用的方法和状态堆栈,允许用户去拖拽并且改变ViewGroup里面的View的位置,也就是重定位。这样看来,可能还不是很明确,不着急,我们根据ZSwipeItem的实现代码一点点分析这个类的用法,一会儿你就明白了。
    下面的分析过程,请大家参照着ZListView项目的源代码来看,这样好理解一些。

    ZSwipeItem继承自Framelayout,所以我们实际上是在自定义一个ViewGroup。ZSwipeItem一共有三个构造函数,但是实际上就是一个,因为最终调用的都是下面的这个构造函数

public ZSwipeItem(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		mDragHelper = ViewDragHelper.create(this, mDragHelperCallback);

		TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.ZSwipeItem);
		// 默认是右边缘检测
		int ordinal = a.getInt(R.styleable.ZSwipeItem_drag_edge,
				DragEdge.Right.ordinal());
		mDragEdge = DragEdge.values()[ordinal];
		// 默认模式是拉出
		ordinal = a.getInt(R.styleable.ZSwipeItem_show_mode,
				ShowMode.PullOut.ordinal());
		mShowMode = ShowMode.values()[ordinal];

		mHorizontalSwipeOffset = a.getDimension(
				R.styleable.ZSwipeItem_horizontalSwipeOffset, 0);
		mVerticalSwipeOffset = a.getDimension(
				R.styleable.ZSwipeItem_verticalSwipeOffset, 0);

		a.recycle();
	}

    在构造函数的第一行代码中,利用ViewDragHelper的工厂方法,创建了一个ViewDragHelper对象。注意后面的第二个参数是ViewDragHelper.Callback对象,我们需要初始化一个这个对象,然后在这里面处理对应的操作。在Callback对象里面,下面几个方法非常重要。

(1) public int clampViewPositionHorizontal(View child, int left, int dx)这个是返回被横向移动的子控件child的左坐标left,和移动距离dx,我们可以根据这些值来返回child的新的left。这个方法必须重写,要不然就不能移动了。

(2)public int clampViewPositionVertical(View child, int top, int dy) 这个和上面的方法一个意思,就是换成了垂直方向的移动和top坐标。如果有垂直移动,这个也必须重写,要不默认返回0,也不能移动了。

(3)public abstract boolean tryCaptureView(View child, int pointerId) 这个方法用来返回可以被移动的View对象,我们可以通过判断child与我们想移动的View是的相等来控制谁能移动。

(4)public int getViewVerticalDragRange(View child) 这个用来控制垂直移动的边界范围,单位是像素。

(5)public int getViewHorizontalDragRange(View child) 和上面一样,就是是横向的边界范围。

(6)public void onViewReleased(View releasedChild, float xvel, float yvel) 当releasedChild被释放的时候,xvel和yvel是x和y方向的加速度

(7)public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) 这个是当changedView的位置发生变化时调用,我们可以在这里面控制View的显示位置和移动。

    我们前面虽然获取了ViewDragHelper的对象,但是现在我们还是不能接收到事件的,我们需要在onTouch()和onInterceptTouchEvent()里面,将触摸事件传入到ViewDragHelper里面,才能进行处理,就像下面这样

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {

	…

	return mDragHelper.shouldInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
	
	…
	mDragHelper.processTouchEvent(event);
	return true;
}

    传递给ViewDragHelper之后,我们就可以在Callback的各个事件里面进行处理了。

    了解了这些之后,我们再看下面的代码,应该就能够明白什么意思了。

/**
	 * 进行拖拽的主要类
	 */
	private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() {

		/**
		 * 计算被横向拖动view的left
		 */
		@Override
		public int clampViewPositionHorizontal(View child, int left, int dx) {
			if (child == getSurfaceView()) {
				switch (mDragEdge) {
				case Top:
				case Bottom:
					return getPaddingLeft();
				case Left:
					if (left < getPaddingLeft())
						return getPaddingLeft();
					if (left > getPaddingLeft() + mDragDistance)
						return getPaddingLeft() + mDragDistance;
					break;
				case Right:
					if (left > getPaddingLeft())
						return getPaddingLeft();
					if (left < getPaddingLeft() - mDragDistance)
						return getPaddingLeft() - mDragDistance;
					break;
				}
			} else if (child == getBottomView()) {

				switch (mDragEdge) {
				case Top:
				case Bottom:
					return getPaddingLeft();
				case Left:
					if (mShowMode == ShowMode.PullOut) {
						if (left > getPaddingLeft())
							return getPaddingLeft();
					}
					break;
				case Right:
					if (mShowMode == ShowMode.PullOut) {
						if (left < getMeasuredWidth() - mDragDistance) {
							return getMeasuredWidth() - mDragDistance;
						}
					}
					break;
				}
			}
			return left;
		}

		/**
		 * 计算被纵向拖动的view的top
		 */
		@Override
		public int clampViewPositionVertical(View child, int top, int dy) {
			if (child == getSurfaceView()) {
				switch (mDragEdge) {
				case Left:
				case Right:
					return getPaddingTop();
				case Top:
					if (top < getPaddingTop())
						return getPaddingTop();
					if (top > getPaddingTop() + mDragDistance)
						return getPaddingTop() + mDragDistance;
					break;
				case Bottom:
					if (top < getPaddingTop() - mDragDistance) {
						return getPaddingTop() - mDragDistance;
					}
					if (top > getPaddingTop()) {
						return getPaddingTop();
					}
				}
			} else {
				switch (mDragEdge) {
				case Left:
				case Right:
					return getPaddingTop();
				case Top:
					if (mShowMode == ShowMode.PullOut) {
						if (top > getPaddingTop())
							return getPaddingTop();
					} else {
						if (getSurfaceView().getTop() + dy < getPaddingTop())
							return getPaddingTop();
						if (getSurfaceView().getTop() + dy > getPaddingTop()
								+ mDragDistance)
							return getPaddingTop() + mDragDistance;
					}
					break;
				case Bottom:
					if (mShowMode == ShowMode.PullOut) {
						if (top < getMeasuredHeight() - mDragDistance)
							return getMeasuredHeight() - mDragDistance;
					} else {
						if (getSurfaceView().getTop() + dy >= getPaddingTop())
							return getPaddingTop();
						if (getSurfaceView().getTop() + dy <= getPaddingTop()
								- mDragDistance)
							return getPaddingTop() - mDragDistance;
					}
				}
			}
			return top;
		}

		/**
		 * 确定要进行拖动的view
		 */
		@Override
		public boolean tryCaptureView(View child, int pointerId) {
			return child == getSurfaceView() || child == getBottomView();
		}

		/**
		 * 确定横向拖动边界
		 */
		@Override
		public int getViewHorizontalDragRange(View child) {
			return mDragDistance;
		}

		/**
		 * 确定纵向拖动边界
		 */
		@Override
		public int getViewVerticalDragRange(View child) {
			return mDragDistance;
		}

		/**
		 * 当子控件被释放的时候调用,可以获取加速度的数据,来判断用户意图
		 */
		@Override
		public void onViewReleased(View releasedChild, float xvel, float yvel) {
			super.onViewReleased(releasedChild, xvel, yvel);

			for (SwipeListener l : swipeListeners) {
				l.onHandRelease(ZSwipeItem.this, xvel, yvel);
			}

			if (releasedChild == getSurfaceView()) {
				processSurfaceRelease(xvel, yvel);
			} else if (releasedChild == getBottomView()) {
				if (getShowMode() == ShowMode.PullOut) {
					processBottomPullOutRelease(xvel, yvel);
				} else if (getShowMode() == ShowMode.LayDown) {
					processBottomLayDownMode(xvel, yvel);
				}
			}

			invalidate();
		}

		/**
		 * 当view的位置发生变化的时候调用,可以设置view的位置跟随手指移动
		 */
		@Override
		public void onViewPositionChanged(View changedView, int left, int top,
				int dx, int dy) {

			int evLeft = getSurfaceView().getLeft();
			int evTop = getSurfaceView().getTop();

			if (changedView == getSurfaceView()) {

				if (mShowMode == ShowMode.PullOut) {
					if (mDragEdge == DragEdge.Left
							|| mDragEdge == DragEdge.Right) {
						getBottomView().offsetLeftAndRight(dx);
					} else {
						getBottomView().offsetTopAndBottom(dy);
					}
				}

			} else if (changedView == getBottomView()) {

				if (mShowMode == ShowMode.PullOut) {
					getSurfaceView().offsetLeftAndRight(dx);
					getSurfaceView().offsetTopAndBottom(dy);
				} else {
					Rect rect = computeBottomLayDown(mDragEdge);
					getBottomView().layout(rect.left, rect.top, rect.right,
							rect.bottom);

					int newLeft = getSurfaceView().getLeft() + dx;
					int newTop = getSurfaceView().getTop() + dy;

					if (mDragEdge == DragEdge.Left
							&& newLeft < getPaddingLeft())
						newLeft = getPaddingLeft();
					else if (mDragEdge == DragEdge.Right
							&& newLeft > getPaddingLeft())
						newLeft = getPaddingLeft();
					else if (mDragEdge == DragEdge.Top
							&& newTop < getPaddingTop())
						newTop = getPaddingTop();
					else if (mDragEdge == DragEdge.Bottom
							&& newTop > getPaddingTop())
						newTop = getPaddingTop();

					getSurfaceView().layout(newLeft, newTop,
							newLeft + getMeasuredWidth(),
							newTop + getMeasuredHeight());
				}
			}

			// 及时派发滑动事件
			dispatchSwipeEvent(evLeft, evTop, dx, dy);

			invalidate();
		}
	};

    在这里面有一个变量mDragDistance,这个就是我们进行滑动的范围,这个值的初始化在onMesure()里面

@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		// 初始化移动距离
		if (mDragEdge == DragEdge.Left || mDragEdge == DragEdge.Right)
			mDragDistance = getBottomView().getMeasuredWidth()
					- dp2px(mHorizontalSwipeOffset);
		else {
			mDragDistance = getBottomView().getMeasuredHeight()
					- dp2px(mVerticalSwipeOffset);
		}
	}
    因为没必要对onMeasure()进行自定义,所以调用了父类的onMeasure(),然后,根据后面布局的宽度或者是高度,对滑动范围进行了初始化。

    getBottomView()返回的就是后面的布局ViewGroup对象,而getSurfaceView()返回的则是前面的布局ViewGroup对象。下面是获取方法,这也就解释了为什么在item的布局文件里面,必须嵌套两个ViewGroup对象了。

public ViewGroup getSurfaceView() {
		return (ViewGroup) getChildAt(1);
	}

	public ViewGroup getBottomView() {
		return (ViewGroup) getChildAt(0);
	}
    其实,在onLayout()里面也保证了必须这样做,要不然就崩掉了!
@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		int childCount = getChildCount();
		if (childCount != 2) {
			throw new IllegalStateException("You need 2  views in SwipeLayout");
		}
		if (!(getChildAt(0) instanceof ViewGroup)
				|| !(getChildAt(1) instanceof ViewGroup)) {
			throw new IllegalArgumentException(
					"The 2 children in SwipeLayout must be an instance of ViewGroup");
		}

		if (mShowMode == ShowMode.PullOut) {
			layoutPullOut();
		} else if (mShowMode == ShowMode.LayDown) {
			layoutLayDown();
		}

		safeBottomView();

		if (mOnLayoutListeners != null)
			for (int i = 0; i < mOnLayoutListeners.size(); i++) {
				mOnLayoutListeners.get(i).onLayout(this);
			}

	}

    我们再回过头来仔细看一下Callback里面的一些回调方法。

    下面的办法返回是这样的,意思就是说,前面布局和后面布局都是可以移动的。

@Override
		public boolean tryCaptureView(View child, int pointerId) {
			return child == getSurfaceView() || child == getBottomView();
		}

    而在onViewPositionChange()中,如果位置发生变化的是前面的布局,并且展示方式是PullOut,那么就通过View.offsetLeftAndRight()、View.offsetTopAndBottom()来控制位置,这样,就能够实现前面布局随着手指移动的效果了!

if (changedView == getSurfaceView()) {

				if (mShowMode == ShowMode.PullOut) {
					if (mDragEdge == DragEdge.Left
							|| mDragEdge == DragEdge.Right) {
						getBottomView().offsetLeftAndRight(dx);
					} else {
						getBottomView().offsetTopAndBottom(dy);
					}
				}

			}
    但是如果移动的View是后面的布局的话,就复杂一点了。如果是PullOut,操作是一样的。但是如果是LayDown模式,需要通过layout()来控制显示的位置,具体的计算就不详细说了。
else if (changedView == getBottomView()) {

				if (mShowMode == ShowMode.PullOut) {
					getSurfaceView().offsetLeftAndRight(dx);
					getSurfaceView().offsetTopAndBottom(dy);
				} else {
					Rect rect = computeBottomLayDown(mDragEdge);
					getBottomView().layout(rect.left, rect.top, rect.right,
							rect.bottom);

					int newLeft = getSurfaceView().getLeft() + dx;
					int newTop = getSurfaceView().getTop() + dy;

					if (mDragEdge == DragEdge.Left
							&& newLeft < getPaddingLeft())
						newLeft = getPaddingLeft();
					else if (mDragEdge == DragEdge.Right
							&& newLeft > getPaddingLeft())
						newLeft = getPaddingLeft();
					else if (mDragEdge == DragEdge.Top
							&& newTop < getPaddingTop())
						newTop = getPaddingTop();
					else if (mDragEdge == DragEdge.Bottom
							&& newTop > getPaddingTop())
						newTop = getPaddingTop();

					getSurfaceView().layout(newLeft, newTop,
							newLeft + getMeasuredWidth(),
							newTop + getMeasuredHeight());
				}
			}
    在这个方法的最后,调用了dispatchSwipeEvent(),dispatchSwipeEvent()有两个重载的方法,第一个方法根据滑动边缘方向和移动距离,来判断是否是想要打开,然后就调用了另外一个dispatchSwipeEvent(),代码如下所示。

    在这个方法里面,完成了对swipeListener的各种方法的调用。这里大家可能有个疑问啊,其实每一个item并不是只有一个SwipeListener的,而是两个,一个是我们自己添加的,另外一个是在适配器中,自动添加的,因此这里我们需要遍历取得每一个SwipeListener,然后进行调用。之前的BUG也是因为这个地方出现了问题。

	protected void dispatchSwipeEvent(int surfaceLeft, int surfaceTop,
			boolean open) {

		safeBottomView();
		Status status = getOpenStatus();

		if (!swipeListeners.isEmpty()) {

			mEventCounter++;

			if (mEventCounter == 1) {
				if (open) {
					swipeListeners.get(0).onStartOpen(ZSwipeItem.this);
					swipeListeners.get(swipeListeners.size() - 1).onStartOpen(
							ZSwipeItem.this);
				} else {
					swipeListeners.get(0).onStartClose(ZSwipeItem.this);
					swipeListeners.get(swipeListeners.size() - 1).onStartClose(
							ZSwipeItem.this);
				}
			}

			for (SwipeListener l : swipeListeners) {
				l.onUpdate(ZSwipeItem.this, surfaceLeft - getPaddingLeft(),
						surfaceTop - getPaddingTop());
			}

			if (status == Status.Close) {
				swipeListeners.get(0).onClose(ZSwipeItem.this);
				swipeListeners.get(swipeListeners.size() - 1).onClose(
						ZSwipeItem.this);
				mEventCounter = 0;
			} else if (status == Status.Open) {
				getBottomView().setEnabled(true);
				swipeListeners.get(0).onOpen(ZSwipeItem.this);
				swipeListeners.get(swipeListeners.size() - 1).onOpen(
						ZSwipeItem.this);
				mEventCounter = 0;
			}
		}
	}

    分析了上面这些之后,我们明白了为什么手指在item上滑动的时候,前面布局为什么能跟随手指移动了,下面我们分析为什么放手之后,前面布局能够很优雅的会弹回去。
    如果要了解这个的实现原理,那么我们就需要分析onViewReleased()。这个方法就是在用户的手指离开屏幕的时候触发的,下面是ZSwipeItem中的代码实现

/**
		 * 当子控件被释放的时候调用,可以获取加速度的数据,来判断用户意图
		 */
		@Override
		public void onViewReleased(View releasedChild, float xvel, float yvel) {
			super.onViewReleased(releasedChild, xvel, yvel);

			for (SwipeListener l : swipeListeners) {
				l.onHandRelease(ZSwipeItem.this, xvel, yvel);
			}

			if (releasedChild == getSurfaceView()) {
				processSurfaceRelease(xvel, yvel);
			} else if (releasedChild == getBottomView()) {
				if (getShowMode() == ShowMode.PullOut) {
					processBottomPullOutRelease(xvel, yvel);
				} else if (getShowMode() == ShowMode.LayDown) {
					processBottomLayDownMode(xvel, yvel);
				}
			}

			invalidate();
		}
    在这里面,首先调用了SwipeListener的onHandRelease(),然后根据用户所触摸的布局的不同,分配给了其他的方法,我们以processSurfaceRelease(xvel, yvel)为例,说明到底做了什么。代码如下:
/**
	 * 执行前布局的释放过程
	 * 
	 * @param xvel
	 * @param yvel
	 */
	private void processSurfaceRelease(float xvel, float yvel) {
		if (xvel == 0 && getOpenStatus() == Status.Middle)
			close();

		if (mDragEdge == DragEdge.Left || mDragEdge == DragEdge.Right) {
			if (xvel > 0) {
				if (mDragEdge == DragEdge.Left)
					open();
				else
					close();
			}
			if (xvel < 0) {
				if (mDragEdge == DragEdge.Left)
					close();
				else
					open();
			}
		} else {
			if (yvel > 0) {
				if (mDragEdge == DragEdge.Top)
					open();
				else
					close();
			}
			if (yvel < 0) {
				if (mDragEdge == DragEdge.Top)
					close();
				else
					open();
			}
		}
	}
    在这个方法里面,根据用户拖动方向和x或者是y加速度的大小,调用了open()和close()方法,那么我们在进入这些方法看看。
public void open() {
		open(true, true);
	}

	public void open(boolean smooth) {
		open(smooth, true);
	}

	public void open(boolean smooth, boolean notify) {
		ViewGroup surface = getSurfaceView(), bottom = getBottomView();
		int dx, dy;
		Rect rect = computeSurfaceLayoutArea(true);
		if (smooth) {
			mDragHelper
					.smoothSlideViewTo(getSurfaceView(), rect.left, rect.top);
		} else {
			dx = rect.left - surface.getLeft();
			dy = rect.top - surface.getTop();
			surface.layout(rect.left, rect.top, rect.right, rect.bottom);
			if (getShowMode() == ShowMode.PullOut) {
				Rect bRect = computeBottomLayoutAreaViaSurface(
						ShowMode.PullOut, rect);
				bottom.layout(bRect.left, bRect.top, bRect.right, bRect.bottom);
			}
			if (notify) {
				dispatchSwipeEvent(rect.left, rect.top, dx, dy);
			} else {
				safeBottomView();
			}
		}
		invalidate();
	}
    上面的3个open()实现了方法重载,最后调用的是最后一个。smooth代表是否是平滑移动的,如果是的话,就调用了ViewDragHelper.smoothSlideViewTo()。其实在ViewDragHelper里面有一个Scroller,这个方法就是通过Scroller类来实现的,但是只这样写还不行,我们还需要重写computeScroll(),然后用下面的代码,让滚动一直持续下去,否则View是不会滚动起来的。如果不是smooth的话,就直接layout(),把View的位置定位过去了。
	@Override
	public void computeScroll() {
		super.computeScroll();
		// 让滚动一直进行下去
		if (mDragHelper.continueSettling(true)) {
			ViewCompat.postInvalidateOnAnimation(this);
		}
	}

    如果我们只做好这些,那么SwipeItem的侧滑功能已经差不多了,但是会有一个问题,因为我们重写了onTouch()和onInterceptTouchEvent(),所以如果我们给ListView设置onItemClickListener,是没有反应的,也就是说item的点击事件被我们破坏了!

    怎么办呢?我们可以通过GestureDetector,来模拟item的点击事件,这也就是为什么在ZSwipeItem中需要存在一个手势监听器了。

private GestureDetector gestureDetector = new GestureDetector(getContext(),
			new SwipeDetector());

	/**
	 * 手势监听器,通过调用performItemClick、performItemLongClick,来解决item的点击问题,
	 * 
	 * @class: com.socks.zlistview.SwipeDetector
	 * @author zhaokaiqiang
	 * @date 2015-1-7 下午3:44:09
	 * 
	 */
	private class SwipeDetector extends GestureDetector.SimpleOnGestureListener {
		@Override
		public boolean onDown(MotionEvent e) {
			return true;
		}

		@Override
		public boolean onSingleTapUp(MotionEvent e) {
			// 当用户单击之后,手指抬起的时候调用,如果没有双击监听器,就直接调用
			performAdapterViewItemClick(e);
			return true;
		}

		@Override
		public boolean onSingleTapConfirmed(MotionEvent e) {
			// 这个方法只有在确认用户不会发生双击事件的时候调用
			return false;
		}

		@Override
		public void onLongPress(MotionEvent e) {
			// 长按事件
			performLongClick();
		}

		@Override
		public boolean onDoubleTap(MotionEvent e) {
			return false;
		}
	}
    在上面的代码里面,我们通过手势监听,然后手动的去调用item的点击事件和长按事件,如果需要双击事件,也可以添加。
private void performAdapterViewItemClick(MotionEvent e) {
		ViewParent t = getParent();

		Log.d(TAG, "performAdapterViewItemClick()");

		while (t != null) {
			if (t instanceof AdapterView) {
				@SuppressWarnings("rawtypes")
				AdapterView view = (AdapterView) t;
				int p = view.getPositionForView(ZSwipeItem.this);
				if (p != AdapterView.INVALID_POSITION
						&& view.performItemClick(
								view.getChildAt(p
										- view.getFirstVisiblePosition()), p,
								view.getAdapter().getItemId(p)))
					return;
			} else {
				if (t instanceof View && ((View) t).performClick())
					return;
			}

			t = t.getParent();
		}
	}
    
    其实ZSwipeItem代码里面还有好多需要介绍的东西,但是确实篇幅有限,大家还是去自己去看吧!

    下面再简单的介绍下BaseSwipeAdapter的实现。

    我们直接从getView()开始看

@Override
	public final View getView(int position, View convertView, ViewGroup parent) {

		if (convertView == null) {
			convertView = generateView(position, parent);
			initialize(convertView, position);
		} else {
			updateConvertView(convertView, position);
		}
		fillValues(position, convertView);
		return convertView;

	}
    在这里面,我们实现了convertView的复用,减少布局初始化的消耗。initialize()是用来初始化布局的,下面是代码实现。在这里面添加了一个SwipeMemory,它是一个SwipeListener的实现类,这也就是为什么在ZSwipeItem里面不只有一个监听器了。同时,还添加了一个onLayoutListener,然后封装到一个ValueBox里面,添加了tag上。
public void initialize(View target, int position) {

		int resId = getSwipeLayoutResourceId(position);

		OnLayoutListener onLayoutListener = new OnLayoutListener(position);
		ZSwipeItem swipeLayout = (ZSwipeItem) target.findViewById(resId);
		if (swipeLayout == null)
			throw new IllegalStateException(
					"can not find SwipeLayout in target view");

		SwipeMemory swipeMemory = new SwipeMemory(position);
		// 添加滑动监听器
		swipeLayout.addSwipeListener(swipeMemory);
		// 添加布局监听器
		swipeLayout.addOnLayoutListener(onLayoutListener);
		swipeLayout.setTag(resId, new ValueBox(position, swipeMemory,
				onLayoutListener));

		mShownLayouts.add(swipeLayout);

	}
    而在复用的问题上,则是通过下面的代码完成的,主要是更新了position属性。
public void updateConvertView(View target, int position) {

		int resId = getSwipeLayoutResourceId(position);

		ZSwipeItem swipeLayout = (ZSwipeItem) target.findViewById(resId);
		if (swipeLayout == null)
			throw new IllegalStateException(
					"can not find SwipeLayout in target view");

		ValueBox valueBox = (ValueBox) swipeLayout.getTag(resId);
		valueBox.swipeMemory.setPosition(position);
		valueBox.onLayoutListener.setPosition(position);
		valueBox.position = position;

		Log.d(TAG, "updateConvertView=" + position);

	}
    SwipeMerory是干了什么呢?从下面的代码中,我们根据不同的模式,维护着当前打开的item的position属性。为什么维护position呢?这是因为getView()的复用会导致我们刚打开的item,如果离开当前屏幕然后再返回的话,就会恢复原状,因此我们需要自己维护这个状态。
class SwipeMemory extends SimpleSwipeListener {

		private int position;

		SwipeMemory(int position) {
			this.position = position;
		}

		@Override
		public void onClose(ZSwipeItem layout) {

			if (mode == Mode.Multiple) {
				openPositions.remove(position);
			} else {
				openPosition = INVALID_POSITION;
			}
		}

		@Override
		public void onStartOpen(ZSwipeItem layout) {

			if (mode == Mode.Single) {
				closeAllExcept(layout);
			}
		}

		@Override
		public void onOpen(ZSwipeItem layout) {

			if (mode == Mode.Multiple)
				openPositions.add(position);
			else {
				closeAllExcept(layout);
				openPosition = position;
			}
		}

		public void setPosition(int position) {
			this.position = position;
		}
	}
    既然维护了position,我们就要用啊!在什么地方用呢?我们看下下面的代码

class OnLayoutListener implements OnSwipeLayoutListener {

		private int position;

		OnLayoutListener(int position) {
			this.position = position;
		}

		public void setPosition(int position) {
			this.position = position;
		}

		@Override
		public void onLayout(ZSwipeItem v) {

			if (isOpen(position)) {
				v.open(false, false);
			} else {
				v.close(false, false);
			}
		}

	}
    还记得ZSwipeItem里面的onLayout()吗?在那里面执行了onLayout的onLayout方法,因此,当我们的item被滑动的时候,就会不断的调用这个onLayout方法,我们判断当前打开的position,然后恢复现场即可!

    说了这么多,不知道大家有没有学到新的东西,看SwipeLayout的源码看了三天,我才搞懂了这么些东西,感觉很有收获,所以想分享给大家,如果你有什么疑问或者是建议,请留言或者是私信。

    写的很累,我要休息去了,拜拜~


    项目的Github地址:https://github.com/ZhaoKaiQiang/ZListVIew

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。