当前位置:   article > 正文

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

can not find swipelayout in target view

    转载请注明出处: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一共有三个构造函数,但是实际上就是一个,因为最终调用的都是下面的这个构造函数

  1. public ZSwipeItem(Context context, AttributeSet attrs, int defStyle) {
  2. super(context, attrs, defStyle);
  3. mDragHelper = ViewDragHelper.create(this, mDragHelperCallback);
  4. TypedArray a = context.obtainStyledAttributes(attrs,
  5. R.styleable.ZSwipeItem);
  6. // 默认是右边缘检测
  7. int ordinal = a.getInt(R.styleable.ZSwipeItem_drag_edge,
  8. DragEdge.Right.ordinal());
  9. mDragEdge = DragEdge.values()[ordinal];
  10. // 默认模式是拉出
  11. ordinal = a.getInt(R.styleable.ZSwipeItem_show_mode,
  12. ShowMode.PullOut.ordinal());
  13. mShowMode = ShowMode.values()[ordinal];
  14. mHorizontalSwipeOffset = a.getDimension(
  15. R.styleable.ZSwipeItem_horizontalSwipeOffset, 0);
  16. mVerticalSwipeOffset = a.getDimension(
  17. R.styleable.ZSwipeItem_verticalSwipeOffset, 0);
  18. a.recycle();
  19. }

    在构造函数的第一行代码中,利用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里面,才能进行处理,就像下面这样

  1. @Override
  2. public boolean onInterceptTouchEvent(MotionEvent ev) {
  3. return mDragHelper.shouldInterceptTouchEvent(ev);
  4. }

  1. @Override
  2. public boolean onTouchEvent(MotionEvent event) {
  3. mDragHelper.processTouchEvent(event);
  4. return true;
  5. }

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

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

  1. /**
  2. * 进行拖拽的主要类
  3. */
  4. private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() {
  5. /**
  6. * 计算被横向拖动view的left
  7. */
  8. @Override
  9. public int clampViewPositionHorizontal(View child, int left, int dx) {
  10. if (child == getSurfaceView()) {
  11. switch (mDragEdge) {
  12. case Top:
  13. case Bottom:
  14. return getPaddingLeft();
  15. case Left:
  16. if (left < getPaddingLeft())
  17. return getPaddingLeft();
  18. if (left > getPaddingLeft() + mDragDistance)
  19. return getPaddingLeft() + mDragDistance;
  20. break;
  21. case Right:
  22. if (left > getPaddingLeft())
  23. return getPaddingLeft();
  24. if (left < getPaddingLeft() - mDragDistance)
  25. return getPaddingLeft() - mDragDistance;
  26. break;
  27. }
  28. } else if (child == getBottomView()) {
  29. switch (mDragEdge) {
  30. case Top:
  31. case Bottom:
  32. return getPaddingLeft();
  33. case Left:
  34. if (mShowMode == ShowMode.PullOut) {
  35. if (left > getPaddingLeft())
  36. return getPaddingLeft();
  37. }
  38. break;
  39. case Right:
  40. if (mShowMode == ShowMode.PullOut) {
  41. if (left < getMeasuredWidth() - mDragDistance) {
  42. return getMeasuredWidth() - mDragDistance;
  43. }
  44. }
  45. break;
  46. }
  47. }
  48. return left;
  49. }
  50. /**
  51. * 计算被纵向拖动的view的top
  52. */
  53. @Override
  54. public int clampViewPositionVertical(View child, int top, int dy) {
  55. if (child == getSurfaceView()) {
  56. switch (mDragEdge) {
  57. case Left:
  58. case Right:
  59. return getPaddingTop();
  60. case Top:
  61. if (top < getPaddingTop())
  62. return getPaddingTop();
  63. if (top > getPaddingTop() + mDragDistance)
  64. return getPaddingTop() + mDragDistance;
  65. break;
  66. case Bottom:
  67. if (top < getPaddingTop() - mDragDistance) {
  68. return getPaddingTop() - mDragDistance;
  69. }
  70. if (top > getPaddingTop()) {
  71. return getPaddingTop();
  72. }
  73. }
  74. } else {
  75. switch (mDragEdge) {
  76. case Left:
  77. case Right:
  78. return getPaddingTop();
  79. case Top:
  80. if (mShowMode == ShowMode.PullOut) {
  81. if (top > getPaddingTop())
  82. return getPaddingTop();
  83. } else {
  84. if (getSurfaceView().getTop() + dy < getPaddingTop())
  85. return getPaddingTop();
  86. if (getSurfaceView().getTop() + dy > getPaddingTop()
  87. + mDragDistance)
  88. return getPaddingTop() + mDragDistance;
  89. }
  90. break;
  91. case Bottom:
  92. if (mShowMode == ShowMode.PullOut) {
  93. if (top < getMeasuredHeight() - mDragDistance)
  94. return getMeasuredHeight() - mDragDistance;
  95. } else {
  96. if (getSurfaceView().getTop() + dy >= getPaddingTop())
  97. return getPaddingTop();
  98. if (getSurfaceView().getTop() + dy <= getPaddingTop()
  99. - mDragDistance)
  100. return getPaddingTop() - mDragDistance;
  101. }
  102. }
  103. }
  104. return top;
  105. }
  106. /**
  107. * 确定要进行拖动的view
  108. */
  109. @Override
  110. public boolean tryCaptureView(View child, int pointerId) {
  111. return child == getSurfaceView() || child == getBottomView();
  112. }
  113. /**
  114. * 确定横向拖动边界
  115. */
  116. @Override
  117. public int getViewHorizontalDragRange(View child) {
  118. return mDragDistance;
  119. }
  120. /**
  121. * 确定纵向拖动边界
  122. */
  123. @Override
  124. public int getViewVerticalDragRange(View child) {
  125. return mDragDistance;
  126. }
  127. /**
  128. * 当子控件被释放的时候调用,可以获取加速度的数据,来判断用户意图
  129. */
  130. @Override
  131. public void onViewReleased(View releasedChild, float xvel, float yvel) {
  132. super.onViewReleased(releasedChild, xvel, yvel);
  133. for (SwipeListener l : swipeListeners) {
  134. l.onHandRelease(ZSwipeItem.this, xvel, yvel);
  135. }
  136. if (releasedChild == getSurfaceView()) {
  137. processSurfaceRelease(xvel, yvel);
  138. } else if (releasedChild == getBottomView()) {
  139. if (getShowMode() == ShowMode.PullOut) {
  140. processBottomPullOutRelease(xvel, yvel);
  141. } else if (getShowMode() == ShowMode.LayDown) {
  142. processBottomLayDownMode(xvel, yvel);
  143. }
  144. }
  145. invalidate();
  146. }
  147. /**
  148. * 当view的位置发生变化的时候调用,可以设置view的位置跟随手指移动
  149. */
  150. @Override
  151. public void onViewPositionChanged(View changedView, int left, int top,
  152. int dx, int dy) {
  153. int evLeft = getSurfaceView().getLeft();
  154. int evTop = getSurfaceView().getTop();
  155. if (changedView == getSurfaceView()) {
  156. if (mShowMode == ShowMode.PullOut) {
  157. if (mDragEdge == DragEdge.Left
  158. || mDragEdge == DragEdge.Right) {
  159. getBottomView().offsetLeftAndRight(dx);
  160. } else {
  161. getBottomView().offsetTopAndBottom(dy);
  162. }
  163. }
  164. } else if (changedView == getBottomView()) {
  165. if (mShowMode == ShowMode.PullOut) {
  166. getSurfaceView().offsetLeftAndRight(dx);
  167. getSurfaceView().offsetTopAndBottom(dy);
  168. } else {
  169. Rect rect = computeBottomLayDown(mDragEdge);
  170. getBottomView().layout(rect.left, rect.top, rect.right,
  171. rect.bottom);
  172. int newLeft = getSurfaceView().getLeft() + dx;
  173. int newTop = getSurfaceView().getTop() + dy;
  174. if (mDragEdge == DragEdge.Left
  175. && newLeft < getPaddingLeft())
  176. newLeft = getPaddingLeft();
  177. else if (mDragEdge == DragEdge.Right
  178. && newLeft > getPaddingLeft())
  179. newLeft = getPaddingLeft();
  180. else if (mDragEdge == DragEdge.Top
  181. && newTop < getPaddingTop())
  182. newTop = getPaddingTop();
  183. else if (mDragEdge == DragEdge.Bottom
  184. && newTop > getPaddingTop())
  185. newTop = getPaddingTop();
  186. getSurfaceView().layout(newLeft, newTop,
  187. newLeft + getMeasuredWidth(),
  188. newTop + getMeasuredHeight());
  189. }
  190. }
  191. // 及时派发滑动事件
  192. dispatchSwipeEvent(evLeft, evTop, dx, dy);
  193. invalidate();
  194. }
  195. };

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

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

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

  1. public ViewGroup getSurfaceView() {
  2. return (ViewGroup) getChildAt(1);
  3. }
  4. public ViewGroup getBottomView() {
  5. return (ViewGroup) getChildAt(0);
  6. }
    其实,在onLayout()里面也保证了必须这样做,要不然就崩掉了!
  1. @Override
  2. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  3. int childCount = getChildCount();
  4. if (childCount != 2) {
  5. throw new IllegalStateException("You need 2 views in SwipeLayout");
  6. }
  7. if (!(getChildAt(0) instanceof ViewGroup)
  8. || !(getChildAt(1) instanceof ViewGroup)) {
  9. throw new IllegalArgumentException(
  10. "The 2 children in SwipeLayout must be an instance of ViewGroup");
  11. }
  12. if (mShowMode == ShowMode.PullOut) {
  13. layoutPullOut();
  14. } else if (mShowMode == ShowMode.LayDown) {
  15. layoutLayDown();
  16. }
  17. safeBottomView();
  18. if (mOnLayoutListeners != null)
  19. for (int i = 0; i < mOnLayoutListeners.size(); i++) {
  20. mOnLayoutListeners.get(i).onLayout(this);
  21. }
  22. }

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

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

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

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

  1. if (changedView == getSurfaceView()) {
  2. if (mShowMode == ShowMode.PullOut) {
  3. if (mDragEdge == DragEdge.Left
  4. || mDragEdge == DragEdge.Right) {
  5. getBottomView().offsetLeftAndRight(dx);
  6. } else {
  7. getBottomView().offsetTopAndBottom(dy);
  8. }
  9. }
  10. }
    但是如果移动的View是后面的布局的话,就复杂一点了。如果是PullOut,操作是一样的。但是如果是LayDown模式,需要通过layout()来控制显示的位置,具体的计算就不详细说了。
  1. else if (changedView == getBottomView()) {
  2. if (mShowMode == ShowMode.PullOut) {
  3. getSurfaceView().offsetLeftAndRight(dx);
  4. getSurfaceView().offsetTopAndBottom(dy);
  5. } else {
  6. Rect rect = computeBottomLayDown(mDragEdge);
  7. getBottomView().layout(rect.left, rect.top, rect.right,
  8. rect.bottom);
  9. int newLeft = getSurfaceView().getLeft() + dx;
  10. int newTop = getSurfaceView().getTop() + dy;
  11. if (mDragEdge == DragEdge.Left
  12. && newLeft < getPaddingLeft())
  13. newLeft = getPaddingLeft();
  14. else if (mDragEdge == DragEdge.Right
  15. && newLeft > getPaddingLeft())
  16. newLeft = getPaddingLeft();
  17. else if (mDragEdge == DragEdge.Top
  18. && newTop < getPaddingTop())
  19. newTop = getPaddingTop();
  20. else if (mDragEdge == DragEdge.Bottom
  21. && newTop > getPaddingTop())
  22. newTop = getPaddingTop();
  23. getSurfaceView().layout(newLeft, newTop,
  24. newLeft + getMeasuredWidth(),
  25. newTop + getMeasuredHeight());
  26. }
  27. }
    在这个方法的最后,调用了dispatchSwipeEvent(), dispatchSwipeEvent()有两个重载的方法,第一个方法根据滑动边缘方向和移动距离,来判断是否是想要打开,然后就调用了另外一个dispatchSwipeEvent(),代码如下所示。

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

  1. protected void dispatchSwipeEvent(int surfaceLeft, int surfaceTop,
  2. boolean open) {
  3. safeBottomView();
  4. Status status = getOpenStatus();
  5. if (!swipeListeners.isEmpty()) {
  6. mEventCounter++;
  7. if (mEventCounter == 1) {
  8. if (open) {
  9. swipeListeners.get(0).onStartOpen(ZSwipeItem.this);
  10. swipeListeners.get(swipeListeners.size() - 1).onStartOpen(
  11. ZSwipeItem.this);
  12. } else {
  13. swipeListeners.get(0).onStartClose(ZSwipeItem.this);
  14. swipeListeners.get(swipeListeners.size() - 1).onStartClose(
  15. ZSwipeItem.this);
  16. }
  17. }
  18. for (SwipeListener l : swipeListeners) {
  19. l.onUpdate(ZSwipeItem.this, surfaceLeft - getPaddingLeft(),
  20. surfaceTop - getPaddingTop());
  21. }
  22. if (status == Status.Close) {
  23. swipeListeners.get(0).onClose(ZSwipeItem.this);
  24. swipeListeners.get(swipeListeners.size() - 1).onClose(
  25. ZSwipeItem.this);
  26. mEventCounter = 0;
  27. } else if (status == Status.Open) {
  28. getBottomView().setEnabled(true);
  29. swipeListeners.get(0).onOpen(ZSwipeItem.this);
  30. swipeListeners.get(swipeListeners.size() - 1).onOpen(
  31. ZSwipeItem.this);
  32. mEventCounter = 0;
  33. }
  34. }
  35. }

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

  1. /**
  2. * 当子控件被释放的时候调用,可以获取加速度的数据,来判断用户意图
  3. */
  4. @Override
  5. public void onViewReleased(View releasedChild, float xvel, float yvel) {
  6. super.onViewReleased(releasedChild, xvel, yvel);
  7. for (SwipeListener l : swipeListeners) {
  8. l.onHandRelease(ZSwipeItem.this, xvel, yvel);
  9. }
  10. if (releasedChild == getSurfaceView()) {
  11. processSurfaceRelease(xvel, yvel);
  12. } else if (releasedChild == getBottomView()) {
  13. if (getShowMode() == ShowMode.PullOut) {
  14. processBottomPullOutRelease(xvel, yvel);
  15. } else if (getShowMode() == ShowMode.LayDown) {
  16. processBottomLayDownMode(xvel, yvel);
  17. }
  18. }
  19. invalidate();
  20. }
    在这里面,首先调用了SwipeListener的onHandRelease(),然后根据用户所触摸的布局的不同,分配给了其他的方法,我们以processSurfaceRelease(xvel, yvel)为例,说明到底做了什么。代码如下:
  1. /**
  2. * 执行前布局的释放过程
  3. *
  4. * @param xvel
  5. * @param yvel
  6. */
  7. private void processSurfaceRelease(float xvel, float yvel) {
  8. if (xvel == 0 && getOpenStatus() == Status.Middle)
  9. close();
  10. if (mDragEdge == DragEdge.Left || mDragEdge == DragEdge.Right) {
  11. if (xvel > 0) {
  12. if (mDragEdge == DragEdge.Left)
  13. open();
  14. else
  15. close();
  16. }
  17. if (xvel < 0) {
  18. if (mDragEdge == DragEdge.Left)
  19. close();
  20. else
  21. open();
  22. }
  23. } else {
  24. if (yvel > 0) {
  25. if (mDragEdge == DragEdge.Top)
  26. open();
  27. else
  28. close();
  29. }
  30. if (yvel < 0) {
  31. if (mDragEdge == DragEdge.Top)
  32. close();
  33. else
  34. open();
  35. }
  36. }
  37. }
    在这个方法里面,根据用户拖动方向和x或者是y加速度的大小,调用了open()和close()方法,那么我们在进入这些方法看看。
  1. public void open() {
  2. open(true, true);
  3. }
  4. public void open(boolean smooth) {
  5. open(smooth, true);
  6. }
  7. public void open(boolean smooth, boolean notify) {
  8. ViewGroup surface = getSurfaceView(), bottom = getBottomView();
  9. int dx, dy;
  10. Rect rect = computeSurfaceLayoutArea(true);
  11. if (smooth) {
  12. mDragHelper
  13. .smoothSlideViewTo(getSurfaceView(), rect.left, rect.top);
  14. } else {
  15. dx = rect.left - surface.getLeft();
  16. dy = rect.top - surface.getTop();
  17. surface.layout(rect.left, rect.top, rect.right, rect.bottom);
  18. if (getShowMode() == ShowMode.PullOut) {
  19. Rect bRect = computeBottomLayoutAreaViaSurface(
  20. ShowMode.PullOut, rect);
  21. bottom.layout(bRect.left, bRect.top, bRect.right, bRect.bottom);
  22. }
  23. if (notify) {
  24. dispatchSwipeEvent(rect.left, rect.top, dx, dy);
  25. } else {
  26. safeBottomView();
  27. }
  28. }
  29. invalidate();
  30. }
    上面的3个open()实现了方法重载,最后调用的是最后一个。smooth代表是否是平滑移动的,如果是的话,就调用了ViewDragHelper.smoothSlideViewTo()。其实在ViewDragHelper里面有一个Scroller,这个方法就是通过Scroller类来实现的,但是只这样写还不行,我们还需要重写computeScroll(),然后用下面的代码,让滚动一直持续下去,否则View是不会滚动起来的。如果不是smooth的话,就直接layout(),把View的位置定位过去了。
  1. @Override
  2. public void computeScroll() {
  3. super.computeScroll();
  4. // 让滚动一直进行下去
  5. if (mDragHelper.continueSettling(true)) {
  6. ViewCompat.postInvalidateOnAnimation(this);
  7. }
  8. }

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

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

  1. private GestureDetector gestureDetector = new GestureDetector(getContext(),
  2. new SwipeDetector());
  3. /**
  4. * 手势监听器,通过调用performItemClick、performItemLongClick,来解决item的点击问题,
  5. *
  6. * @class: com.socks.zlistview.SwipeDetector
  7. * @author zhaokaiqiang
  8. * @date 2015-1-7 下午3:44:09
  9. *
  10. */
  11. private class SwipeDetector extends GestureDetector.SimpleOnGestureListener {
  12. @Override
  13. public boolean onDown(MotionEvent e) {
  14. return true;
  15. }
  16. @Override
  17. public boolean onSingleTapUp(MotionEvent e) {
  18. // 当用户单击之后,手指抬起的时候调用,如果没有双击监听器,就直接调用
  19. performAdapterViewItemClick(e);
  20. return true;
  21. }
  22. @Override
  23. public boolean onSingleTapConfirmed(MotionEvent e) {
  24. // 这个方法只有在确认用户不会发生双击事件的时候调用
  25. return false;
  26. }
  27. @Override
  28. public void onLongPress(MotionEvent e) {
  29. // 长按事件
  30. performLongClick();
  31. }
  32. @Override
  33. public boolean onDoubleTap(MotionEvent e) {
  34. return false;
  35. }
  36. }
    在上面的代码里面,我们通过手势监听,然后手动的去调用item的点击事件和长按事件,如果需要双击事件,也可以添加。
  1. private void performAdapterViewItemClick(MotionEvent e) {
  2. ViewParent t = getParent();
  3. Log.d(TAG, "performAdapterViewItemClick()");
  4. while (t != null) {
  5. if (t instanceof AdapterView) {
  6. @SuppressWarnings("rawtypes")
  7. AdapterView view = (AdapterView) t;
  8. int p = view.getPositionForView(ZSwipeItem.this);
  9. if (p != AdapterView.INVALID_POSITION
  10. && view.performItemClick(
  11. view.getChildAt(p
  12. - view.getFirstVisiblePosition()), p,
  13. view.getAdapter().getItemId(p)))
  14. return;
  15. } else {
  16. if (t instanceof View && ((View) t).performClick())
  17. return;
  18. }
  19. t = t.getParent();
  20. }
  21. }
    
    其实ZSwipeItem代码里面还有好多需要介绍的东西,但是确实篇幅有限,大家还是去自己去看吧!

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

    我们直接从getView()开始看

  1. @Override
  2. public final View getView(int position, View convertView, ViewGroup parent) {
  3. if (convertView == null) {
  4. convertView = generateView(position, parent);
  5. initialize(convertView, position);
  6. } else {
  7. updateConvertView(convertView, position);
  8. }
  9. fillValues(position, convertView);
  10. return convertView;
  11. }
    在这里面,我们实现了convertView的复用,减少布局初始化的消耗。initialize()是用来初始化布局的,下面是代码实现。在这里面添加了一个SwipeMemory,它是一个SwipeListener的实现类,这也就是为什么在ZSwipeItem里面不只有一个监听器了。同时,还添加了一个onLayoutListener,然后封装到一个ValueBox里面,添加了tag上。
  1. public void initialize(View target, int position) {
  2. int resId = getSwipeLayoutResourceId(position);
  3. OnLayoutListener onLayoutListener = new OnLayoutListener(position);
  4. ZSwipeItem swipeLayout = (ZSwipeItem) target.findViewById(resId);
  5. if (swipeLayout == null)
  6. throw new IllegalStateException(
  7. "can not find SwipeLayout in target view");
  8. SwipeMemory swipeMemory = new SwipeMemory(position);
  9. // 添加滑动监听器
  10. swipeLayout.addSwipeListener(swipeMemory);
  11. // 添加布局监听器
  12. swipeLayout.addOnLayoutListener(onLayoutListener);
  13. swipeLayout.setTag(resId, new ValueBox(position, swipeMemory,
  14. onLayoutListener));
  15. mShownLayouts.add(swipeLayout);
  16. }
    而在复用的问题上,则是通过下面的代码完成的,主要是更新了position属性。
  1. public void updateConvertView(View target, int position) {
  2. int resId = getSwipeLayoutResourceId(position);
  3. ZSwipeItem swipeLayout = (ZSwipeItem) target.findViewById(resId);
  4. if (swipeLayout == null)
  5. throw new IllegalStateException(
  6. "can not find SwipeLayout in target view");
  7. ValueBox valueBox = (ValueBox) swipeLayout.getTag(resId);
  8. valueBox.swipeMemory.setPosition(position);
  9. valueBox.onLayoutListener.setPosition(position);
  10. valueBox.position = position;
  11. Log.d(TAG, "updateConvertView=" + position);
  12. }
    SwipeMerory是干了什么呢?从下面的代码中,我们根据不同的模式,维护着当前打开的item的position属性。为什么维护position呢?这是因为getView()的复用会导致我们刚打开的item,如果离开当前屏幕然后再返回的话,就会恢复原状,因此我们需要自己维护这个状态。
  1. class SwipeMemory extends SimpleSwipeListener {
  2. private int position;
  3. SwipeMemory(int position) {
  4. this.position = position;
  5. }
  6. @Override
  7. public void onClose(ZSwipeItem layout) {
  8. if (mode == Mode.Multiple) {
  9. openPositions.remove(position);
  10. } else {
  11. openPosition = INVALID_POSITION;
  12. }
  13. }
  14. @Override
  15. public void onStartOpen(ZSwipeItem layout) {
  16. if (mode == Mode.Single) {
  17. closeAllExcept(layout);
  18. }
  19. }
  20. @Override
  21. public void onOpen(ZSwipeItem layout) {
  22. if (mode == Mode.Multiple)
  23. openPositions.add(position);
  24. else {
  25. closeAllExcept(layout);
  26. openPosition = position;
  27. }
  28. }
  29. public void setPosition(int position) {
  30. this.position = position;
  31. }
  32. }
    既然维护了position,我们就要用啊!在什么地方用呢?我们看下下面的代码

  1. class OnLayoutListener implements OnSwipeLayoutListener {
  2. private int position;
  3. OnLayoutListener(int position) {
  4. this.position = position;
  5. }
  6. public void setPosition(int position) {
  7. this.position = position;
  8. }
  9. @Override
  10. public void onLayout(ZSwipeItem v) {
  11. if (isOpen(position)) {
  12. v.open(false, false);
  13. } else {
  14. v.close(false, false);
  15. }
  16. }
  17. }
    还记得ZSwipeItem里面的onLayout()吗?在那里面执行了onLayout的onLayout方法,因此,当我们的item被滑动的时候,就会不断的调用这个onLayout方法,我们判断当前打开的position,然后恢复现场即可!

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

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


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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/619701
推荐阅读
相关标签
  

闽ICP备14008679号