SlideAndDragListView简介

SlideAndDragListView,可排序、可滑动item显示”菜单”的ListView。

SlideAndDragListView(SDLV)继承于Android的ListView,SDLV可以拖动item到SDLV的任意位置,其中包括了拖动item往上滑和往下滑;SDLV可以向右滑动item,像Android的QQ那样(QQ是向左滑),然后显现出来"菜单”之类的按钮。

github地址:https://github.com/yydcdut/SlideAndDragListView
开源中国:http://git.oschina.net/yydcdut/SlideAndDragListView

怎么使用

XML

  1. <com.yydcdut.sdlv.SlideAndDragListView
  2. xmlns:sdlv="http://schemas.android.com/apk/res-auto"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:divider="@android:color/black"
  6. android:dividerHeight="0.5dip"
  7. android:paddingLeft="8dip"
  8. android:paddingRight="8dip"
  9. sdlv:item_background="@android:color/white"
  10. sdlv:item_btn1_background="@drawable/btn1_drawable"
  11. sdlv:item_btn1_text="Delete1"
  12. sdlv:item_btn1_text_color="#00ff00"
  13. sdlv:item_btn2_background="@drawable/btn2_drawable"
  14. sdlv:item_btn2_text="Rename1"
  15. sdlv:item_btn2_text_color="#ff0000"
  16. sdlv:item_btn_number=""
  17. sdlv:item_btn_width="70dip"
  18. sdlv:item_height="80dip">
  19. </com.yydcdut.sdlv.SlideAndDragListView>

attributes

item_background - item滑开那部分的背景。

item_btn1_background - "菜单"中第一个button的背景。

item_btn1_text - "菜单"中第一个button的text。

item_btn1_text_color - "菜单"中第一个button的字体颜色。

item_btn2_background - "菜单"中第二个button的背景。

item_btn2_text - "菜单"中第二个button的text。

item_btn2_text_color - "菜单"中第二个button的字体颜色。

item_btn_number - 要显示出来的”菜单”中的button的个数,在0~2之间。

item_btn_width - “菜单”中button的宽度。

item_height - item的高度。

监听器

  1. SlideAndDragListView.OnListItemLongClickListener
  1. sdlv.setOnListItemLongClickListener(new SlideAndDragListView.OnListItemLongClickListener() {
  2. @Override
  3. public void onListItemLongClick(View view, int position) {
  4.  
  5. }
  6. });

public void onListItemLongClick(View view, int position) . 参数 view 是被长点击的item, 参数 position 是item在SDLV中的位置。

  1. SlideAndDragListView.OnListItemClickListener
  1. sdlv.setOnListItemClickListener(new SlideAndDragListView.OnListItemClickListener() {
  2. @Override
  3. public void onListItemClick(View v, int position) {
  4.  
  5. }
  6. });

public void onListItemClick(View view, int position) . 参数 view 是被点击的item, 参数 position 是item在SDLV中的位置。

  1. SlideAndDragListView.OnDragListener
  1. sdlv.setOnDragListener(new SlideAndDragListView.OnDragListener() {
  2. @Override
  3. public void onDragViewMoving(int position) {
  4.  
  5. }
  6.  
  7. @Override
  8. public void onDragViewDown(int position) {
  9.  
  10. }
  11. });

public void onDragViewMoving(int position) .参数 position 是被拖动的item的现在所在的位置,同时onDragViewMoving这个方法会被不停的调用,因为一直在拖动,同时position也会改变。

public void onDragViewDown(int position) . 参数 position 是被拖动的item被放下的时候在SDLV中的位置。

  1. SlideAndDragListView.OnSlideListener
  1. sdlv.setOnSlideListener(new SlideAndDragListView.OnSlideListener() {
  2. @Override
  3. public void onSlideOpen(View view, int position) {
  4.  
  5. }
  6.  
  7. @Override
  8. public void onSlideClose(View view, int position) {
  9.  
  10. }
  11. });

public void onSlideOpen(View view, int position). 参数 view 是滑动开的那个item, 同时 position 是那个item在SDLV中的位置。

public void onSlideClose(View view, int position).参数 view 是滑动关闭的那个item, 同时 position 是那个item在SDLV中的位置。

  1. SlideAndDragListView.OnButtonClickListenerProxy
  1. sdlv.setOnButtonClickListenerProxy(new SlideAndDragListView.OnButtonClickListenerProxy() {
  2. @Override
  3. public void onClick(View view, int position, int number) {
  4.  
  5. }
  6. });

public void onClick(View view, int position, int number) . 参数 view 是”菜单”中被点击的button,参数 position 这个button所在的item在SDLV中的位置,参数, number 代表哪一个被点击了,因为可能会有2个。

权限

  1. <uses-permission android:name="android.permission.VIBRATE"/>

简单的实现

SDLV用的是最基本的Android API来实现的,所以很好理解。其中各个功能的实现分别是:

  • 拖动item:Android的View.OnDragListener接口。
  • 向右滑动item显示”菜单”:Android的Scroller类和View的scrollTo方法。
  • 拖动item往上或往下:ListView的smoothScrollToPosition方法。
  • 适配器:BaseAdapter类和ViewHolder。
  • item的长点击事件:因为系统的onItemLongClick事件与View.OnDragListener接口中的事件有冲突,所以我SDLV中通过Handler在手势事件中发送Message模拟onItemLongClick事件。
  • 模拟onItemLongClick中的振动:Context.VIBRATOR_SERVICE。
  • 手势事件:系统的dispatchTouchEvent。

结构

各个击破

里面有几个SDItemXXXX的控件,主要是应对于item的高度而重写了onMeasure

方法。这里就不说了哈。

从layout布局开始说吧:

item_sdlv.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <com.yydcdut.sdlv.SDItemLayout
  3. android:id="@+id/layout_item_main"
  4. xmlns:android="http://schemas.android.com/apk/res/android"
  5. xmlns:tools="http://schemas.android.com/tools"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. tools:ignore="MissingPrefix">
  9.  
  10. <com.yydcdut.sdlv.SDItemLayout
  11. android:id="@+id/layout_item_bg"
  12. android:layout_width="fill_parent"
  13. android:layout_height="@dimen/slv_item_height"
  14. android:background="@android:color/transparent">
  15.  
  16. <com.yydcdut.sdlv.SDItemBGImage
  17. android:id="@+id/img_item_bg"
  18. android:layout_width="fill_parent"
  19. android:layout_height="@dimen/slv_item_height"
  20. android:background="@android:color/white"/>
  21.  
  22. <com.yydcdut.sdlv.SDItemText
  23. android:id="@+id/txt_item_edit_btn1"
  24. android:layout_width="@dimen/slv_item_bg_btn_width"
  25. android:layout_height="@dimen/slv_item_height"
  26. android:layout_alignParentLeft="true"
  27. android:background="@android:color/holo_red_light"
  28. android:gravity="center"
  29. android:lines=""
  30. android:text="@string/btn1"
  31. android:textColor="@android:color/white"
  32. android:textSize="@dimen/txt_size"/>
  33.  
  34. <com.yydcdut.sdlv.SDItemText
  35. android:id="@+id/txt_item_edit_btn2"
  36. android:layout_width="@dimen/slv_item_bg_btn_width"
  37. android:layout_height="@dimen/slv_item_height"
  38. android:layout_toRightOf="@+id/txt_item_edit_btn1"
  39. android:background="@android:color/darker_gray"
  40. android:gravity="center"
  41. android:lines=""
  42. android:text="@string/btn2"
  43. android:textColor="@android:color/white"
  44. android:textSize="@dimen/txt_size"/>
  45.  
  46. </com.yydcdut.sdlv.SDItemLayout>
  47.  
  48. <com.yydcdut.sdlv.SDItemLayout
  49. android:id="@+id/layout_item_scroll"
  50. android:layout_width="match_parent"
  51. android:layout_height="@dimen/slv_item_height"
  52. android:background="@android:color/transparent">
  53.  
  54. <com.yydcdut.sdlv.SDItemBGImage
  55. android:id="@+id/img_item_scroll_bg"
  56. android:layout_width="fill_parent"
  57. android:layout_height="@dimen/slv_item_height"
  58. android:background="@android:color/white"/>
  59.  
  60. <FrameLayout
  61. android:id="@+id/layout_custom"
  62. android:layout_width="fill_parent"
  63. android:layout_height="@dimen/slv_item_height">
  64. </FrameLayout>
  65.  
  66. </com.yydcdut.sdlv.SDItemLayout>
  67.  
  68. </com.yydcdut.sdlv.SDItemLayout>

根是一个RelativeLayout,里面有两个大的RelativeLayout子跟。底层那个RelativeLayout是有三个控件,分别是一个长度宽度都和父Layout一样的ImageView,这个是就前面讲的item_background的背景设置的地方,另外两个是TextView,就是前面讲到的”菜单”中的button。上面那层也有个ImageView,主要是覆盖住下面那层Layout,因为什么不直接用Layout的background呢,因为当时发现scrollTo之后下面那层是没有显示出来的,还是被挡住了的。另外一个是一个FrameLayout,这里是用户自定义的item显示的地方。

看完了item的布局,那么来看看Adapter吧。

  1. public abstract class SDAdapter<T> extends BaseAdapter implements View.OnClickListener {
  2. /* 上下文 */
  3. private final Context mContext;
  4. /* 数据 */
  5. private List<T> mDataList;
  6. /* Drag的位置 */
  7. private int mDragPosition = -;
  8. /* 点击button的位置 */
  9. private int mBtnPosition = -;
  10. /* button的单击监听器 */
  11. private OnButtonClickListener mOnButtonClickListener;
  12. /* 当前滑开的item的位置 */
  13. private int mSlideOpenItemPosition;
  14. /* ---------- attrs ----------- */
  15. private float mItemHeight;
  16. private int mItemBtnNumber;
  17. private String mItemBtn1Text;
  18. private String mItemBtn2Text;
  19. private float mItemBtnWidth;
  20. private Drawable mItemBGDrawable;
  21. private int mItemBtn1TextColor;
  22. private int mItemBtn2TextColor;
  23. private Drawable mItemBtn1Drawable;
  24. private Drawable mItemBtn2Drawable;
  25. /* ---------- attrs ----------- */
  26.  
  27. public SDAdapter(Context context, List<T> dataList) {
  28. mContext = context;
  29. mDataList = dataList;
  30. }
  31.  
  32. @Override
  33. public int getCount() {
  34. return mDataList.size();
  35. }
  36.  
  37. @Override
  38. public Object getItem(int position) {
  39. return mDataList.get(position);
  40. }
  41.  
  42. @Override
  43. public long getItemId(int position) {
  44. return position;
  45. }
  46.  
  47. @Override
  48. public View getView(int position, View convertView, ViewGroup parent) {
  49. ViewHolder holder;
  50. if (convertView == null) {
  51. holder = new ViewHolder();
  52. convertView = LayoutInflater.from(mContext).inflate(R.layout.item_sdlv, null);
  53. holder.layoutMain = (SDItemLayout) convertView.findViewById(R.id.layout_item_main);
  54. holder.layoutMain.setItemHeight((int) mItemHeight);
  55. holder.layoutScroll = (SDItemLayout) convertView.findViewById(R.id.layout_item_scroll);
  56. holder.layoutScroll.setItemHeight((int) mItemHeight);
  57. holder.layoutBG = (SDItemLayout) convertView.findViewById(R.id.layout_item_bg);
  58. holder.layoutBG.setItemHeight((int) mItemHeight);
  59. holder.imgBGScroll = (SDItemBGImage) convertView.findViewById(R.id.img_item_scroll_bg);
  60. holder.imgBGScroll.setItemHeight((int) mItemHeight);
  61. holder.imgBG = (SDItemBGImage) convertView.findViewById(R.id.img_item_bg);
  62. holder.imgBG.setItemHeight((int) mItemHeight);
  63. holder.layoutCustom = (FrameLayout) convertView.findViewById(R.id.layout_custom);
  64. holder.btn1 = (SDItemText) convertView.findViewById(R.id.txt_item_edit_btn1);
  65. holder.btn2 = (SDItemText) convertView.findViewById(R.id.txt_item_edit_btn2);
  66. holder.btn1.setBtnWidth((int) mItemBtnWidth);
  67. holder.btn1.setBtnHeight((int) mItemHeight);
  68. holder.btn2.setBtnWidth((int) mItemBtnWidth);
  69. holder.btn2.setBtnHeight((int) mItemHeight);
  70. //如果用户设置了背景的话就用用户的背景
  71. if (mItemBGDrawable != null) {
  72. holder.imgBG.setBackgroundDrawable(mItemBGDrawable);
  73. holder.imgBGScroll.setBackgroundDrawable(mItemBGDrawable);
  74. }
  75. //判断哪些隐藏哪些显示
  76. checkVisible(holder);
  77. //设置text
  78. holder.btn1.setText(mItemBtn1Text);//setText有容错处理
  79. holder.btn2.setText(mItemBtn2Text);//setText有容错处理
  80. //设置监听器
  81. holder.btn1.setOnClickListener(this);
  82. holder.btn2.setOnClickListener(this);
  83. //一开始加载的时候都不可点击
  84. holder.btn1.setClickable(false);
  85. holder.btn2.setClickable(false);
  86. //背景和字体颜色
  87. holder.btn1.setBackgroundDrawable(mItemBtn1Drawable);
  88. holder.btn2.setBackgroundDrawable(mItemBtn2Drawable);
  89. holder.btn1.setTextColor(mItemBtn1TextColor);
  90. holder.btn2.setTextColor(mItemBtn2TextColor);
  91. convertView.setTag(holder);
  92. } else {
  93. holder = (ViewHolder) convertView.getTag();
  94. }
  95.  
  96. //没有展开的item里面的btn是不可点击的
  97. if (mSlideOpenItemPosition == position) {
  98. holder.btn1.setClickable(true);
  99. holder.btn2.setClickable(true);
  100. } else {
  101. holder.btn1.setClickable(false);
  102. holder.btn2.setClickable(false);
  103. }
  104.  
  105. //用户的view
  106. View customView = getView(mContext, holder.layoutCustom.getChildAt(), position, mDragPosition);
  107. if (holder.layoutCustom.getChildAt() == null) {
  108. holder.layoutCustom.addView(customView);
  109. } else {
  110. holder.layoutCustom.removeViewAt();
  111. holder.layoutCustom.addView(customView);
  112. }
  113.  
  114. //所有的都归位
  115. holder.layoutScroll.scrollTo(, );
  116.  
  117. //把背景显示出来(因为在drag的时候会将背景透明,因为好看)
  118. holder.imgBGScroll.setVisibility(View.VISIBLE);
  119. holder.layoutBG.setVisibility(View.VISIBLE);
  120. return convertView;
  121. }
  122.  
  123. /**
  124. * 与BaseAdapter类似
  125. *
  126. * @param context
  127. * @param convertView
  128. * @param position
  129. * @param dragPosition 当前拖动的item的位置,如果没有拖动item的话值是-1
  130. * @return
  131. */
  132. public abstract View getView(Context context, View convertView, int position, int dragPosition);
  133.  
  134. @Override
  135. public void onClick(View v) {
  136. if (v.getId() == R.id.txt_item_edit_btn1) {
  137. if (mOnButtonClickListener != null && mBtnPosition != -) {
  138. mOnButtonClickListener.onClick(v, mBtnPosition, );
  139. }
  140. } else if (v.getId() == R.id.txt_item_edit_btn2) {
  141. if (mOnButtonClickListener != null && mBtnPosition != -) {
  142. mOnButtonClickListener.onClick(v, mBtnPosition, );
  143. }
  144. }
  145. }
  146.  
  147. class ViewHolder {
  148. public SDItemLayout layoutMain;
  149. public SDItemLayout layoutScroll;
  150. public SDItemLayout layoutBG;
  151. public SDItemBGImage imgBGScroll;
  152. public SDItemBGImage imgBG;
  153. public SDItemText btn1;
  154. public SDItemText btn2;
  155. public FrameLayout layoutCustom;
  156. }
  157.  
  158. /**
  159. * 判断用户要几个button
  160. *
  161. * @param vh
  162. */
  163. private void checkVisible(ViewHolder vh) {
  164. switch (mItemBtnNumber) {
  165. case :
  166. vh.btn1.setVisibility(View.GONE);
  167. vh.btn2.setVisibility(View.GONE);
  168. break;
  169. case :
  170. vh.btn1.setVisibility(View.VISIBLE);
  171. vh.btn2.setVisibility(View.GONE);
  172. break;
  173. case :
  174. vh.btn1.setVisibility(View.VISIBLE);
  175. vh.btn2.setVisibility(View.VISIBLE);
  176. break;
  177. default:
  178. throw new IllegalArgumentException("");
  179. }
  180. vh.btn1.setClickable(false);
  181. vh.btn2.setClickable(false);
  182. }
  183.  
  184. //...............................
  185. }

Adapter里面的作用就是把item的layout显示出来,然后设置高度,某些控件需要设置宽度,然后设置一些其他参数,比如背景啊等等。其中要注意的是holder.btn1.setClickable(false); 和 holder.btn2.setClickable(false);,因为不设置clickable为false的话就出当看不见的时间点击那个位置也会触发onClick事件。第二个就是:holder.layoutScroll.scrollTo(0, 0); 这个地方,当ListView滑走的时候就把这个归位回到0,0的位置,不然回出现顺序错乱。第三个地方是:

  1. //用户的view
  2. View customView = getView(mContext, holder.layoutCustom.getChildAt(), position, mDragPosition);
  3. if (holder.layoutCustom.getChildAt() == null) {
  4. holder.layoutCustom.addView(customView);
  5. } else {
  6. holder.layoutCustom.removeViewAt();
  7. holder.layoutCustom.addView(customView);
  8. }

这里的customView是通过一个abstract方法,用户只需要实现这个Adapter中的这个方法就行了。其次就是getChildAt、addView和removeViewAt这三个方法,主要是不同的position有显示不同的用户的信息。

在onClick事件中要去判断当前点击的是不是已经在item中显现出来的,是的话才回掉出去。

接下来讲讲SDLV吧,我把重要部分的代码贴出来。

  1. public class SlideAndDragListView<T> extends ListView implements Handler.Callback, View.OnDragListener,
  2. SDAdapter.OnButtonClickListener, AdapterView.OnItemClickListener {
  3. //....................
  4. /* onTouch里面的状态 */
  5. private static final int STATE_NOTHING = -;//抬起状态
  6. private static final int STATE_DOWN = ;//按下状态
  7. private static final int STATE_LONG_CLICK = ;//长点击状态
  8. private static final int STATE_SCROLL = ;//SCROLL状态
  9. private static final int STATE_LONG_CLICK_FINISH = ;//长点击已经触发完成
  10. private int mState = STATE_NOTHING;
  11. //.....................
  12. @Override
  13. public boolean handleMessage(Message msg) {
  14. switch (msg.what) {
  15. case MSG_WHAT_LONG_CLICK:
  16. if (mState == STATE_LONG_CLICK) {//如果得到msg的时候state状态是Long Click的话
  17. //改为long click触发完成
  18. mState = STATE_LONG_CLICK_FINISH;
  19. //得到长点击的位置
  20. int position = msg.arg1;
  21. //找到那个位置的view
  22. View view = getChildAt(mSlideTargetPosition - getFirstVisiblePosition());
  23. //通知adapter
  24. mSDAdapter.setDragPosition(position);
  25. //如果设置了监听器的话,就触发
  26. if (mOnListItemLongClickListener != null) {
  27. scrollBack();
  28. mVibrator.vibrate();
  29. mOnListItemLongClickListener.onListItemLongClick(view, position);
  30. }
  31. mCurrentPosition = position;
  32. mBeforeCurrentPosition = position;
  33. mBeforeBeforePosition = position;
  34. //把背景给弄透明,这样drag的时候要好看些
  35. view.findViewById(R.id.layout_item_bg).setVisibility(INVISIBLE);
  36. view.findViewById(R.id.img_item_scroll_bg).setVisibility(INVISIBLE);
  37. //drag
  38. ClipData.Item item = new ClipData.Item("");
  39. ClipData data = new ClipData("", new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}, item);
  40. view.startDrag(data, new View.DragShadowBuilder(view), null, );
  41. //通知adapter变颜色
  42. mSDAdapter.notifyDataSetChanged();
  43. }
  44. break;
  45. }
  46. return true;
  47. }
  48. //.....................
  49. @Override
  50. public boolean dispatchTouchEvent(MotionEvent ev) {
  51. switch (ev.getAction()) {
  52. case MotionEvent.ACTION_DOWN:
  53. if (mIsScrollerScrolling) {//scroll正在滑动的话就不要做其他处理了
  54. return false;
  55. }
  56. //获取出坐标来
  57. mXDown = (int) ev.getX();
  58. mYDown = (int) ev.getY();
  59.  
  60. //通过坐标找到在ListView中的位置
  61. mSlideTargetPosition = pointToPosition(mXDown, mYDown);
  62. if (mSlideTargetPosition == AdapterView.INVALID_POSITION) {
  63. return super.dispatchTouchEvent(ev);
  64. }
  65.  
  66. //通过位置找到要slide的view
  67. View view = getChildAt(mSlideTargetPosition - getFirstVisiblePosition());
  68. if (view == null) {
  69. return super.dispatchTouchEvent(ev);
  70. }
  71. mSlideTargetView = view.findViewById(R.id.layout_item_scroll);
  72. if (mSlideTargetView != null) {
  73. //如果已经是滑开了的或者没有滑开的
  74. mXScrollDistance = mSlideTargetView.getScrollX();
  75. } else {
  76. mXScrollDistance = ;
  77. }
  78. //当前state状态味按下
  79. mState = STATE_DOWN;
  80. break;
  81. case MotionEvent.ACTION_MOVE:
  82. if (mIsScrollerScrolling) {//scroll正在滑动的话就不要做其他处理了
  83. return false;
  84. }
  85. if (fingerNotMove(ev)) {//手指的范围在50以内
  86. if (mState != STATE_SCROLL && mState != STATE_LONG_CLICK_FINISH && mState != STATE_LONG_CLICK) {//状态不为滑动状态且不为已经触发完成
  87. sendLongClickMessage();
  88. mState = STATE_LONG_CLICK;
  89. } else if (mState == STATE_SCROLL) {//当为滑动状态的时候
  90. //有滑动,那么不再触发长点击
  91. removeLongClickMessage();
  92. }
  93. } else if (fingerLeftAndRightMove(ev) && mSlideTargetView != null) {//上下范围在50,主要检测左右滑动
  94. boolean bool = false;
  95. //这次位置与上一次的不一样,那么要滑这个之前把之前的归位
  96. if (mLastPosition != mSlideTargetPosition) {
  97. mLastPosition = mSlideTargetPosition;
  98. bool = scrollBack();
  99. }
  100. //如果有scroll归位的话的话先跳过这次move
  101. if (bool) {
  102. return super.dispatchTouchEvent(ev);
  103. }
  104. //scroll当前的View
  105. int moveDistance = (int) ev.getX() - mXDown;//这个往右是正,往左是负
  106. int distance = mXScrollDistance - moveDistance < ? mXScrollDistance - moveDistance : ;
  107. mSlideTargetView.scrollTo(distance, );
  108. mState = STATE_SCROLL;
  109. }
  110. break;
  111. case MotionEvent.ACTION_UP:
  112. case MotionEvent.ACTION_CANCEL:
  113. if (mIsScrollerScrolling) {//scroll正在滑动的话就不要做其他处理了
  114. return false;
  115. }
  116. if (mSlideTargetView != null && mState == STATE_SCROLL) {
  117. //如果滑出的话,那么就滑到固定位置(只要滑出了 mBGWidth / 2 ,就算滑出去了)
  118. if (Math.abs(mSlideTargetView.getScrollX()) > mBGWidth / ) {
  119. //通知adapter
  120. mSDAdapter.setBtnPosition(mSlideTargetPosition);
  121. //不触发onListItemClick事件
  122. mOnListItemClickListener = null;
  123. mSDAdapter.setSlideOpenItemPosition(mSlideTargetPosition);
  124. if (mOnSlideListener != null) {
  125. mOnSlideListener.onSlideOpen(mSlideTargetView, mSlideTargetPosition);
  126. }
  127. //滑出
  128. int delta = mBGWidth - Math.abs(mSlideTargetView.getScrollX());
  129. if (Math.abs(mSlideTargetView.getScrollX()) < mBGWidth) {
  130. mScroller.startScroll(mSlideTargetView.getScrollX(), , -delta, , SCROLL_QUICK_TIME);
  131. } else {
  132. mScroller.startScroll(mSlideTargetView.getScrollX(), , -delta, , SCROLL_TIME);
  133. }
  134. postInvalidate();
  135. } else {
  136. //通知adapter
  137. mSDAdapter.setBtnPosition(-);
  138. mSDAdapter.setSlideOpenItemPosition(-);
  139. //如果有onListItemClick事件的话,就赋值过去,代表可以触发了
  140. if (mTempListItemClickListener != null && mOnListItemClickListener == null) {
  141. mOnListItemClickListener = mTempListItemClickListener;
  142. }
  143. //滑回去,归位
  144. if (mOnSlideListener != null) {
  145. mOnSlideListener.onSlideClose(mSlideTargetView, mSlideTargetPosition);
  146. }
  147. mScroller.startScroll(mSlideTargetView.getScrollX(), , -mSlideTargetView.getScrollX(), , SCROLL_QUICK_TIME);
  148. postInvalidate();
  149. }
  150. mState = STATE_NOTHING;
  151. removeLongClickMessage();
  152. //更新last的值
  153. mLastPosition = mSlideTargetPosition;
  154. //设置为无效的
  155. mSlideTargetPosition = AdapterView.INVALID_POSITION;
  156. return false;
  157. }
  158. mState = STATE_NOTHING;
  159. removeLongClickMessage();
  160. //更新last的值
  161. mLastPosition = mSlideTargetPosition;
  162. //设置为无效的
  163. mSlideTargetPosition = AdapterView.INVALID_POSITION;
  164. break;
  165. default:
  166. removeLongClickMessage();
  167. mState = STATE_NOTHING;
  168. break;
  169. }
  170. return super.dispatchTouchEvent(ev);
  171. }
  172. //.....................
  173. @Override
  174. public boolean onDrag(View v, DragEvent event) {
  175. final int action = event.getAction();
  176. switch (action) {
  177. case DragEvent.ACTION_DRAG_STARTED:
  178. return true;
  179. case DragEvent.ACTION_DRAG_ENTERED:
  180. return true;
  181. case DragEvent.ACTION_DRAG_LOCATION:
  182. //当前移动的item在ListView中的position
  183. int position = pointToPosition((int) event.getX(), (int) event.getY());
  184. //如果位置发生了改变
  185. if (mBeforeCurrentPosition != position) {
  186. //有时候得到的position是-1(AdapterView.INVALID_POSITION),忽略掉
  187. if (position >= ) {
  188. //判断是往上了还是往下了
  189. mUp = position - mBeforeCurrentPosition <= ;
  190. //记录移动之后上一次的位置
  191. mBeforeBeforePosition = mBeforeCurrentPosition;
  192. //记录当前位置
  193. mBeforeCurrentPosition = position;
  194. }
  195. }
  196. moveListViewUpOrDown(position);
  197. //有时候为-1(AdapterView.INVALID_POSITION)的情况,忽略掉
  198. if (position >= ) {
  199. //判断是不是已经换过位置了,如果没有换过,则进去换
  200. if (position != mCurrentPosition) {
  201. if (mUp) {//往上
  202. //只是移动了一格
  203. if (position - mBeforeBeforePosition == -) {
  204. T t = mDataList.get(position);
  205. mDataList.set(position, mDataList.get(position + ));
  206. mDataList.set(position + , t);
  207. } else {//一下子移动了好几个位置,其实可以和上面那个方法合并起来的
  208. T t = mDataList.get(mBeforeBeforePosition);
  209. for (int i = mBeforeBeforePosition; i > position; i--) {
  210. mDataList.set(i, mDataList.get(i - ));
  211. }
  212. mDataList.set(position, t);
  213. }
  214. } else {
  215. if (position - mBeforeBeforePosition == ) {
  216. T t = mDataList.get(position);
  217. mDataList.set(position, mDataList.get(position - ));
  218. mDataList.set(position - , t);
  219. } else {
  220. T t = mDataList.get(mBeforeBeforePosition);
  221. for (int i = mBeforeBeforePosition; i < position; i++) {
  222. mDataList.set(i, mDataList.get(i + ));
  223. }
  224. mDataList.set(position, t);
  225. }
  226. }
  227. mSDAdapter.notifyDataSetChanged();
  228. //更新位置
  229. mCurrentPosition = position;
  230. }
  231. }
  232. //通知adapter
  233. mSDAdapter.setDragPosition(position);
  234. if (mOnDragListener != null) {
  235. mOnDragListener.onDragViewMoving(mCurrentPosition);
  236. }
  237. return true;
  238. case DragEvent.ACTION_DRAG_EXITED:
  239. return true;
  240. case DragEvent.ACTION_DROP:
  241. mSDAdapter.notifyDataSetChanged();
  242. //通知adapter
  243. mSDAdapter.setDragPosition(-);
  244. if (mOnDragListener != null) {
  245. mOnDragListener.onDragViewDown(mCurrentPosition);
  246. }
  247. return true;
  248. case DragEvent.ACTION_DRAG_ENDED:
  249. return true;
  250. default:
  251. break;
  252. }
  253. return false;
  254. }
  255. //.....................
  256. /**
  257. * 如果到了两端,判断ListView是往上滑动还是ListView往下滑动
  258. *
  259. * @param position
  260. */
  261. private void moveListViewUpOrDown(int position) {
  262. //ListView中最上面的显示的位置
  263. int firstPosition = getFirstVisiblePosition();
  264. //ListView中最下面的显示的位置
  265. int lastPosition = getLastVisiblePosition();
  266. //能够往上的话往上
  267. if ((position == firstPosition || position == firstPosition + ) && firstPosition != ) {
  268. smoothScrollToPosition(firstPosition - );
  269. }
  270. //能够往下的话往下
  271. if ((position == lastPosition || position == lastPosition - ) && lastPosition != getCount() - ) {
  272. smoothScrollToPosition(lastPosition + );
  273. }
  274. }
  275. //.....................
  276. }

首先看到的前面一堆声明的STATE状态,这是我给dispatchTouchEvent设置的状态机,理解了设定的状态之后,了解了不同的状态下能做什么不能做什么之后,在dispatchTouchEvent代码里面就可以看起来很简单了。

首先,当手指按下的时候,回去取出X,Y坐标保存下来,通过X,Y坐标和pointToPosition()方法来确定当前这个左边是哪个item,得到item的位置,有些情况下返回的是-1,所以这里进行判断如果是-1(AdapterView.INVALID_POSITION)的话就先跳过。如果不是,那么得到这个item的View,判断这个item的View有没有scroll过,scroll的距离是多少。此时将state的状态变为DOWN

到MOVE的情况了。首先判断scroller的computeScroll方法是不是正在被调用,是的话返回false,代表事件不再往下传递,不是的话继续往下走,判断MOVE情况下手指偏移量有哆嗦,如果上下左右都是在50以内的话,并且state不为SCROLL和LONG_CLICK_FINISH,判定为用户有长点击的趋势,那么发送一个长点击的Message出去,此事state状态变为LONG_CLICK,如果后面一直是这样的话,Handler取出消息进行处理,如果是LONG_CLICK的话就进行长点击的事件处理,此时状态变为LONG_CLICK_FINISH;如果之前是有那个趋势,但是长点击的触发时间没到,就滑动的了,状态变为了SCROLL了,就把那条长点击的Message的时间从MessageQueue中取消掉。现在说如果变成SCROLL状态,如果手指上下偏移唉50以内,并且左右偏移超过50,那么可以定义为SCROLL状态。在此状态中需要判断是否已经有View被Slide Open了,有的话将其归位,回到0,0处,然后跳过,如果没有的话,则进行View的scrollTo操作,此时state的状态变为SCROLL

到了手指抬起的情况了。首先判断scroller的computeScroll方法是不是正在被调用。之后去判断当前的这个View的Scroll了的距离,如果超出了我们所规定了,通过Scroller滚到指定地方。在这里,规定了”菜单”中的距离的一半不到,滚到0,0处,超过一半或者远远超过距离,则滚到”菜单宽度的距离处”。之后将state状态变为NOTHING。返回false不向下传递事件了。

dispatchTouchEvent简单的分析完了,回过头来说为什么要用dispatchTouchEvent而不是onTouchEvent,我是这样想的:dispatchTouchEvent和onTouchEvent差不多,但是onTouchEvent做了很多其他的处理,比如系统的单击和长点击事件等等,我在dispatchTouchEvent做出来,返回true或者false还可以控制去不去触发onTouchEvent中的系统事件。所以选择了dispatchTouchEvent。至少我是这么理解的,对Touch这块还不是特别熟悉,有不对的地方请指出。

好,现在分析拖动。拖动的开始是在这里:

  1. //drag
  2. ClipData.Item item = new ClipData.Item("");
  3. ClipData data = new ClipData("", new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}, item);
  4. view.startDrag(data, new View.DragShadowBuilder(view), null, );
  5. //通知adapter变颜色
  6. mSDAdapter.notifyDataSetChanged();

响应事件是在这里:

  1. public boolean onDrag(View v, DragEvent event) {
  2. return false;
  3. }

其中DragEvent中有许多ACTION,而我们只需要用到DragEvent.ACTION_DRAG_LOCATIONDragEvent.ACTION_DROP

在DRAG_LOCATION当中,首先是确定位置。然后记录位置,通过这个位置与之前记录的位置判断现在的操作是要往上拖动还是往下拖动,如果位置发生变化那么就在存放数据恩List里面调换位置,然后notify一下dataChange了。在这个过程中还要一个判断,就是在moveListViewUpOrDown(position);这个方法里面,这里面主要是判断这个position是不是到了顶端或者底端,是的话就让listview往上滑或者往下滑。在ACTION_DROP中就是释放了拖放的item。

总结

其实整个控件并不是那么复杂,只是有些地方脑子绕不过弯来,但是这样的地方也不多。往上也有很多这样类似的控件,有一个动画做的超级好,我还没有去读过他们的代码。有人问我最近在做什么,我就说最近自己在搞一个APP,然后把一些控件抽出来开源,就比如这个,他说这个往上有很多,干嘛自己写,当时我简单的回答说写着好玩。但是现在发现很多东西实践了才真正理解了。

谢谢大家,控件地址在:https://github.com/yydcdut/SlideAndDragListView
开源中国:http://git.oschina.net/yydcdut/SlideAndDragListView

我还在不断的改进,比如两边都可以滑之类的。

Android SlideAndDragListView,一个可排序可滑动item的ListView的更多相关文章

  1. SlideAndDragListView,一个可排序可滑动item的ListView

    SlideAndDragListView简介 SlideAndDragListView,可排序.可滑动item显示"菜单"的ListView. SlideAndDragListVi ...

  2. Android开发学习之路-RecyclerView滑动删除和拖动排序

    Android开发学习之路-RecyclerView使用初探 Android开发学习之路-RecyclerView的Item自定义动画及DefaultItemAnimator源码分析 Android开 ...

  3. RecyclerView拖拽排序和滑动删除实现

    效果图 如何实现 那么是如何实现的呢?主要就要使用到ItemTouchHelper ,ItemTouchHelper 一个帮助开发人员处理拖拽和滑动删除的实现类,它能够让你非常容易实现侧滑删除.拖拽的 ...

  4. RecyclerView实现拖动排序和滑动删除功能

    RecyclerView 的拖动排序需要借助一下 ItemTouchHelper 这个类,ItemTouchHelper 类是 Google 提供的一个支持 RecyclerView 滑动和拖动的一个 ...

  5. Android Studio 一个完整的APP实例(附源码和数据库)

    前言: 这是我独立做的第一个APP,是一个记账本APP. This is the first APP, I've ever done on my own. It's a accountbook APP ...

  6. Android 自定义Adapter实现多视图Item的ListView

    自定义Adapter实现多视图Item的ListView http://www.devdiv.com/adapter_item_listview-blog-20-7539.html 1.原理分析 Ad ...

  7. ItemTouchHelper(实现RecyclerView上添加拖动排序与滑动删除的所有事情)

    简单介绍: ItemTouchHelper是一个强大的工具,它处理好了关于在RecyclerView上添加拖动排序与滑动删除的所有事情.它是RecyclerView.ItemDecoration的子类 ...

  8. Android 判断一个 View 是否可见 getLocalVisibleRect(rect) 与 getGlobalVisibleRect(rect)

    Android 判断一个 View 是否可见 getLocalVisibleRect(rect) 与 getGlobalVisibleRect(rect) [TOC] 这两个方法的区别 View.ge ...

  9. WP8.1 侧边滑动Item

    效果图 我看ios 和安卓上有好多类似的Item的效果,UWP上有微软官方的库,其中也有类似得效果,看样子WP8.1没有啊,顺便我的程序也是需要,我也就仿了一个. 具体思路是: 触摸控制GRId在CA ...

随机推荐

  1. caioj 1066 动态规划入门(一维一边推4:护卫队)(分组型dp总结)

    很容易想到f[i]为前i项的最优价值,但是我一直在纠结如果重量满了该怎么办. 正解有点枚举的味道. 就是枚举当前这辆车与这辆车以前的组合一组,在能组的里面取最优的. 然后要记得初始化,因为有min,所 ...

  2. 【Henu ACM Round#16 B】 Bear and Colors

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] O(n^2)枚举每一个区间. 然后维护这个区间里面的"统治数字"是什么. 对于每个区间cnt[统治数字]++; ...

  3. Nagle和Cork

    我觉得这篇讲的不错. http://blog.csdn.net/c_cyoxi/article/details/8673645 Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段. 关闭 ...

  4. Python学习笔记3:简单文件操作

    # -*- coding: cp936 -*- # 1 打开文件 # open(fileName, mode) # 參数:fileName文件名称 # mode打开方式 # w     以写方式打开. ...

  5. MyReport.Form表单引擎

    MyReport.Form表单引擎.主要提供表单模板的设计以及表单模板的预览填报等功能集合. 支持文本框.选择框.数字框.日期框.图片框.组合框.弹出框等经常使用控件. watermark/2/tex ...

  6. Unix/Linux环境C编程新手教程(37) shell经常使用命令演练

     cat命令 cat命令能够用来查看文件内容. cat [參数] 文件名称. grep-指定文件里搜索指定字符内容. Linux的文件夹或文件. -path '字串' 查找路径名匹配所给字串的全部 ...

  7. vue -- config index.js 配置文件详解

    此文章介绍vue-cli脚手架config目录下index.js配置文件 此配置文件是用来定义开发环境和生产环境中所需要的参数 关于注释 当涉及到较复杂的解释我将通过标识的方式(如(1))将解释写到单 ...

  8. (MySQL里的数据)通过Sqoop Import HBase 里 和 通过Sqoop Export HBase 里的数据到(MySQL)

    Sqoop 可以与HBase系统结合,实现数据的导入和导出,用户需要在 sqoop-env.sh 中添加HBASE_HOME的环境变量. 具体,见我的如下博客: hadoop2.6.0(单节点)下Sq ...

  9. javafx style and cssFile

    public class EffectTest extends Application { public static void main(String[] args) { launch(args); ...

  10. Logistic Regression and Newton's Method

    Data For this exercise, suppose that a high school has a dataset representing 40 students who were a ...