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

 <com.yydcdut.sdlv.SlideAndDragListViewxmlns:sdlv="http://schemas.android.com/apk/res-auto"android:layout_width="fill_parent"android:layout_height="fill_parent"android:divider="@android:color/black"android:dividerHeight="0.5dip"android:paddingLeft="8dip"android:paddingRight="8dip"sdlv:item_background="@android:color/white"sdlv:item_btn1_background="@drawable/btn1_drawable"sdlv:item_btn1_text="Delete1"sdlv:item_btn1_text_color="#00ff00"sdlv:item_btn2_background="@drawable/btn2_drawable"sdlv:item_btn2_text="Rename1"sdlv:item_btn2_text_color="#ff0000"sdlv:item_btn_number="2"sdlv:item_btn_width="70dip"sdlv:item_height="80dip"></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的高度。

监听器

SlideAndDragListView.OnListItemLongClickListener

sdlv.setOnListItemLongClickListener(new SlideAndDragListView.OnListItemLongClickListener() {@Overridepublic void onListItemLongClick(View view, int position) {}});

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

SlideAndDragListView.OnListItemClickListener

sdlv.setOnListItemClickListener(new SlideAndDragListView.OnListItemClickListener() {@Overridepublic void onListItemClick(View v, int position) {}});

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

SlideAndDragListView.OnDragListener

sdlv.setOnDragListener(new SlideAndDragListView.OnDragListener() {@Overridepublic void onDragViewMoving(int position) {}@Overridepublic void onDragViewDown(int position) {}});

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

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

SlideAndDragListView.OnSlideListener

sdlv.setOnSlideListener(new SlideAndDragListView.OnSlideListener() {@Overridepublic void onSlideOpen(View view, int position) {}@Overridepublic void onSlideClose(View view, int position) {}});

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

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

SlideAndDragListView.OnButtonClickListenerProxy

sdlv.setOnButtonClickListenerProxy(new SlideAndDragListView.OnButtonClickListenerProxy() {@Overridepublic void onClick(View view, int position, int number) {}});

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

权限

<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

<?xml version="1.0" encoding="utf-8"?>
<com.yydcdut.sdlv.SDItemLayoutandroid:id="@+id/layout_item_main"xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:ignore="MissingPrefix"><com.yydcdut.sdlv.SDItemLayoutandroid:id="@+id/layout_item_bg"android:layout_width="fill_parent"android:layout_height="@dimen/slv_item_height"android:background="@android:color/transparent"><com.yydcdut.sdlv.SDItemBGImageandroid:id="@+id/img_item_bg"android:layout_width="fill_parent"android:layout_height="@dimen/slv_item_height"android:background="@android:color/white"/><com.yydcdut.sdlv.SDItemTextandroid:id="@+id/txt_item_edit_btn1"android:layout_width="@dimen/slv_item_bg_btn_width"android:layout_height="@dimen/slv_item_height"android:layout_alignParentLeft="true"android:background="@android:color/holo_red_light"android:gravity="center"android:lines="1"android:text="@string/btn1"android:textColor="@android:color/white"android:textSize="@dimen/txt_size"/><com.yydcdut.sdlv.SDItemTextandroid:id="@+id/txt_item_edit_btn2"android:layout_width="@dimen/slv_item_bg_btn_width"android:layout_height="@dimen/slv_item_height"android:layout_toRightOf="@+id/txt_item_edit_btn1"android:background="@android:color/darker_gray"android:gravity="center"android:lines="1"android:text="@string/btn2"android:textColor="@android:color/white"android:textSize="@dimen/txt_size"/></com.yydcdut.sdlv.SDItemLayout><com.yydcdut.sdlv.SDItemLayoutandroid:id="@+id/layout_item_scroll"android:layout_width="match_parent"android:layout_height="@dimen/slv_item_height"android:background="@android:color/transparent"><com.yydcdut.sdlv.SDItemBGImageandroid:id="@+id/img_item_scroll_bg"android:layout_width="fill_parent"android:layout_height="@dimen/slv_item_height"android:background="@android:color/white"/><FrameLayoutandroid:id="@+id/layout_custom"android:layout_width="fill_parent"android:layout_height="@dimen/slv_item_height"></FrameLayout></com.yydcdut.sdlv.SDItemLayout></com.yydcdut.sdlv.SDItemLayout>

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

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

public abstract class SDAdapter<T> extends BaseAdapter implements View.OnClickListener {/* 上下文 */private final Context mContext;/* 数据 */private List<T> mDataList;/* Drag的位置 */private int mDragPosition = -1;/* 点击button的位置 */private int mBtnPosition = -1;/* button的单击监听器 */private OnButtonClickListener mOnButtonClickListener;/* 当前滑开的item的位置 */private int mSlideOpenItemPosition;/* ---------- attrs ----------- */private float mItemHeight;private int mItemBtnNumber;private String mItemBtn1Text;private String mItemBtn2Text;private float mItemBtnWidth;private Drawable mItemBGDrawable;private int mItemBtn1TextColor;private int mItemBtn2TextColor;private Drawable mItemBtn1Drawable;private Drawable mItemBtn2Drawable;/* ---------- attrs ----------- */public SDAdapter(Context context, List<T> dataList) {mContext = context;mDataList = dataList;}@Overridepublic int getCount() {return mDataList.size();}@Overridepublic Object getItem(int position) {return mDataList.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder;if (convertView == null) {holder = new ViewHolder();convertView = LayoutInflater.from(mContext).inflate(R.layout.item_sdlv, null);holder.layoutMain = (SDItemLayout) convertView.findViewById(R.id.layout_item_main);holder.layoutMain.setItemHeight((int) mItemHeight);holder.layoutScroll = (SDItemLayout) convertView.findViewById(R.id.layout_item_scroll);holder.layoutScroll.setItemHeight((int) mItemHeight);holder.layoutBG = (SDItemLayout) convertView.findViewById(R.id.layout_item_bg);holder.layoutBG.setItemHeight((int) mItemHeight);holder.imgBGScroll = (SDItemBGImage) convertView.findViewById(R.id.img_item_scroll_bg);holder.imgBGScroll.setItemHeight((int) mItemHeight);holder.imgBG = (SDItemBGImage) convertView.findViewById(R.id.img_item_bg);holder.imgBG.setItemHeight((int) mItemHeight);holder.layoutCustom = (FrameLayout) convertView.findViewById(R.id.layout_custom);holder.btn1 = (SDItemText) convertView.findViewById(R.id.txt_item_edit_btn1);holder.btn2 = (SDItemText) convertView.findViewById(R.id.txt_item_edit_btn2);holder.btn1.setBtnWidth((int) mItemBtnWidth);holder.btn1.setBtnHeight((int) mItemHeight);holder.btn2.setBtnWidth((int) mItemBtnWidth);holder.btn2.setBtnHeight((int) mItemHeight);//如果用户设置了背景的话就用用户的背景if (mItemBGDrawable != null) {holder.imgBG.setBackgroundDrawable(mItemBGDrawable);holder.imgBGScroll.setBackgroundDrawable(mItemBGDrawable);}//判断哪些隐藏哪些显示
            checkVisible(holder);//设置textholder.btn1.setText(mItemBtn1Text);//setText有容错处理holder.btn2.setText(mItemBtn2Text);//setText有容错处理//设置监听器holder.btn1.setOnClickListener(this);holder.btn2.setOnClickListener(this);//一开始加载的时候都不可点击holder.btn1.setClickable(false);holder.btn2.setClickable(false);//背景和字体颜色
            holder.btn1.setBackgroundDrawable(mItemBtn1Drawable);holder.btn2.setBackgroundDrawable(mItemBtn2Drawable);holder.btn1.setTextColor(mItemBtn1TextColor);holder.btn2.setTextColor(mItemBtn2TextColor);convertView.setTag(holder);} else {holder = (ViewHolder) convertView.getTag();}//没有展开的item里面的btn是不可点击的if (mSlideOpenItemPosition == position) {holder.btn1.setClickable(true);holder.btn2.setClickable(true);} else {holder.btn1.setClickable(false);holder.btn2.setClickable(false);}//用户的viewView customView = getView(mContext, holder.layoutCustom.getChildAt(0), position, mDragPosition);if (holder.layoutCustom.getChildAt(0) == null) {holder.layoutCustom.addView(customView);} else {holder.layoutCustom.removeViewAt(0);holder.layoutCustom.addView(customView);}//所有的都归位holder.layoutScroll.scrollTo(0, 0);//把背景显示出来(因为在drag的时候会将背景透明,因为好看)
        holder.imgBGScroll.setVisibility(View.VISIBLE);holder.layoutBG.setVisibility(View.VISIBLE);return convertView;}/*** 与BaseAdapter类似** @param context* @param convertView* @param position* @param dragPosition 当前拖动的item的位置,如果没有拖动item的话值是-1* @return*/public abstract View getView(Context context, View convertView, int position, int dragPosition);@Overridepublic void onClick(View v) {if (v.getId() == R.id.txt_item_edit_btn1) {if (mOnButtonClickListener != null && mBtnPosition != -1) {mOnButtonClickListener.onClick(v, mBtnPosition, 0);}} else if (v.getId() == R.id.txt_item_edit_btn2) {if (mOnButtonClickListener != null && mBtnPosition != -1) {mOnButtonClickListener.onClick(v, mBtnPosition, 1);}}}class ViewHolder {public SDItemLayout layoutMain;public SDItemLayout layoutScroll;public SDItemLayout layoutBG;public SDItemBGImage imgBGScroll;public SDItemBGImage imgBG;public SDItemText btn1;public SDItemText btn2;public FrameLayout layoutCustom;}/*** 判断用户要几个button** @param vh*/private void checkVisible(ViewHolder vh) {switch (mItemBtnNumber) {case 0:vh.btn1.setVisibility(View.GONE);vh.btn2.setVisibility(View.GONE);break;case 1:vh.btn1.setVisibility(View.VISIBLE);vh.btn2.setVisibility(View.GONE);break;case 2:vh.btn1.setVisibility(View.VISIBLE);vh.btn2.setVisibility(View.VISIBLE);break;default:throw new IllegalArgumentException("");}vh.btn1.setClickable(false);vh.btn2.setClickable(false);}//...............................
}

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

//用户的viewView customView = getView(mContext, holder.layoutCustom.getChildAt(0), position, mDragPosition);if (holder.layoutCustom.getChildAt(0) == null) {holder.layoutCustom.addView(customView);} else {holder.layoutCustom.removeViewAt(0);holder.layoutCustom.addView(customView);}

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

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

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

public class SlideAndDragListView<T> extends ListView implements Handler.Callback, View.OnDragListener,SDAdapter.OnButtonClickListener, AdapterView.OnItemClickListener {//..................../* onTouch里面的状态 */private static final int STATE_NOTHING = -1;//抬起状态private static final int STATE_DOWN = 0;//按下状态private static final int STATE_LONG_CLICK = 1;//长点击状态private static final int STATE_SCROLL = 2;//SCROLL状态private static final int STATE_LONG_CLICK_FINISH = 3;//长点击已经触发完成private int mState = STATE_NOTHING;//.....................
    @Overridepublic boolean handleMessage(Message msg) {switch (msg.what) {case MSG_WHAT_LONG_CLICK:if (mState == STATE_LONG_CLICK) {//如果得到msg的时候state状态是Long Click的话//改为long click触发完成mState = STATE_LONG_CLICK_FINISH;//得到长点击的位置int position = msg.arg1;//找到那个位置的viewView view = getChildAt(mSlideTargetPosition - getFirstVisiblePosition());//通知adapter
                    mSDAdapter.setDragPosition(position);//如果设置了监听器的话,就触发if (mOnListItemLongClickListener != null) {scrollBack();mVibrator.vibrate(100);mOnListItemLongClickListener.onListItemLongClick(view, position);}mCurrentPosition = position;mBeforeCurrentPosition = position;mBeforeBeforePosition = position;//把背景给弄透明,这样drag的时候要好看些
                    view.findViewById(R.id.layout_item_bg).setVisibility(INVISIBLE);view.findViewById(R.id.img_item_scroll_bg).setVisibility(INVISIBLE);//dragClipData.Item item = new ClipData.Item("1");ClipData data = new ClipData("1", new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}, item);view.startDrag(data, new View.DragShadowBuilder(view), null, 0);//通知adapter变颜色
                    mSDAdapter.notifyDataSetChanged();}break;}return true;}//.....................
    @Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:if (mIsScrollerScrolling) {//scroll正在滑动的话就不要做其他处理了return false;}//获取出坐标来mXDown = (int) ev.getX();mYDown = (int) ev.getY();//通过坐标找到在ListView中的位置mSlideTargetPosition = pointToPosition(mXDown, mYDown);if (mSlideTargetPosition == AdapterView.INVALID_POSITION) {return super.dispatchTouchEvent(ev);}//通过位置找到要slide的viewView view = getChildAt(mSlideTargetPosition - getFirstVisiblePosition());if (view == null) {return super.dispatchTouchEvent(ev);}mSlideTargetView = view.findViewById(R.id.layout_item_scroll);if (mSlideTargetView != null) {//如果已经是滑开了的或者没有滑开的mXScrollDistance = mSlideTargetView.getScrollX();} else {mXScrollDistance = 0;}//当前state状态味按下mState = STATE_DOWN;break;case MotionEvent.ACTION_MOVE:if (mIsScrollerScrolling) {//scroll正在滑动的话就不要做其他处理了return false;}if (fingerNotMove(ev)) {//手指的范围在50以内if (mState != STATE_SCROLL && mState != STATE_LONG_CLICK_FINISH && mState != STATE_LONG_CLICK) {//状态不为滑动状态且不为已经触发完成
                        sendLongClickMessage();mState = STATE_LONG_CLICK;} else if (mState == STATE_SCROLL) {//当为滑动状态的时候//有滑动,那么不再触发长点击
                        removeLongClickMessage();}} else if (fingerLeftAndRightMove(ev) && mSlideTargetView != null) {//上下范围在50,主要检测左右滑动boolean bool = false;//这次位置与上一次的不一样,那么要滑这个之前把之前的归位if (mLastPosition != mSlideTargetPosition) {mLastPosition = mSlideTargetPosition;bool = scrollBack();}//如果有scroll归位的话的话先跳过这次moveif (bool) {return super.dispatchTouchEvent(ev);}//scroll当前的Viewint moveDistance = (int) ev.getX() - mXDown;//这个往右是正,往左是负int distance = mXScrollDistance - moveDistance < 0 ? mXScrollDistance - moveDistance : 0;mSlideTargetView.scrollTo(distance, 0);mState = STATE_SCROLL;}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:if (mIsScrollerScrolling) {//scroll正在滑动的话就不要做其他处理了return false;}if (mSlideTargetView != null && mState == STATE_SCROLL) {//如果滑出的话,那么就滑到固定位置(只要滑出了 mBGWidth / 2 ,就算滑出去了)if (Math.abs(mSlideTargetView.getScrollX()) > mBGWidth / 2) {//通知adapter
                        mSDAdapter.setBtnPosition(mSlideTargetPosition);//不触发onListItemClick事件mOnListItemClickListener = null;mSDAdapter.setSlideOpenItemPosition(mSlideTargetPosition);if (mOnSlideListener != null) {mOnSlideListener.onSlideOpen(mSlideTargetView, mSlideTargetPosition);}//滑出int delta = mBGWidth - Math.abs(mSlideTargetView.getScrollX());if (Math.abs(mSlideTargetView.getScrollX()) < mBGWidth) {mScroller.startScroll(mSlideTargetView.getScrollX(), 0, -delta, 0, SCROLL_QUICK_TIME);} else {mScroller.startScroll(mSlideTargetView.getScrollX(), 0, -delta, 0, SCROLL_TIME);}postInvalidate();} else {//通知adaptermSDAdapter.setBtnPosition(-1);mSDAdapter.setSlideOpenItemPosition(-1);//如果有onListItemClick事件的话,就赋值过去,代表可以触发了if (mTempListItemClickListener != null && mOnListItemClickListener == null) {mOnListItemClickListener = mTempListItemClickListener;}//滑回去,归位if (mOnSlideListener != null) {mOnSlideListener.onSlideClose(mSlideTargetView, mSlideTargetPosition);}mScroller.startScroll(mSlideTargetView.getScrollX(), 0, -mSlideTargetView.getScrollX(), 0, SCROLL_QUICK_TIME);postInvalidate();}mState = STATE_NOTHING;removeLongClickMessage();//更新last的值mLastPosition = mSlideTargetPosition;//设置为无效的mSlideTargetPosition = AdapterView.INVALID_POSITION;return false;}mState = STATE_NOTHING;removeLongClickMessage();//更新last的值mLastPosition = mSlideTargetPosition;//设置为无效的mSlideTargetPosition = AdapterView.INVALID_POSITION;break;default:removeLongClickMessage();mState = STATE_NOTHING;break;}return super.dispatchTouchEvent(ev);}//.....................
    @Overridepublic boolean onDrag(View v, DragEvent event) {final int action = event.getAction();switch (action) {case DragEvent.ACTION_DRAG_STARTED:return true;case DragEvent.ACTION_DRAG_ENTERED:return true;case DragEvent.ACTION_DRAG_LOCATION://当前移动的item在ListView中的positionint position = pointToPosition((int) event.getX(), (int) event.getY());//如果位置发生了改变if (mBeforeCurrentPosition != position) {//有时候得到的position是-1(AdapterView.INVALID_POSITION),忽略掉if (position >= 0) {//判断是往上了还是往下了mUp = position - mBeforeCurrentPosition <= 0;//记录移动之后上一次的位置mBeforeBeforePosition = mBeforeCurrentPosition;//记录当前位置mBeforeCurrentPosition = position;}}moveListViewUpOrDown(position);//有时候为-1(AdapterView.INVALID_POSITION)的情况,忽略掉if (position >= 0) {//判断是不是已经换过位置了,如果没有换过,则进去换if (position != mCurrentPosition) {if (mUp) {//往上//只是移动了一格if (position - mBeforeBeforePosition == -1) {T t = mDataList.get(position);mDataList.set(position, mDataList.get(position + 1));mDataList.set(position + 1, t);} else {//一下子移动了好几个位置,其实可以和上面那个方法合并起来的T t = mDataList.get(mBeforeBeforePosition);for (int i = mBeforeBeforePosition; i > position; i--) {mDataList.set(i, mDataList.get(i - 1));}mDataList.set(position, t);}} else {if (position - mBeforeBeforePosition == 1) {T t = mDataList.get(position);mDataList.set(position, mDataList.get(position - 1));mDataList.set(position - 1, t);} else {T t = mDataList.get(mBeforeBeforePosition);for (int i = mBeforeBeforePosition; i < position; i++) {mDataList.set(i, mDataList.get(i + 1));}mDataList.set(position, t);}}mSDAdapter.notifyDataSetChanged();//更新位置mCurrentPosition = position;}}//通知adapter
                mSDAdapter.setDragPosition(position);if (mOnDragListener != null) {mOnDragListener.onDragViewMoving(mCurrentPosition);}return true;case DragEvent.ACTION_DRAG_EXITED:return true;case DragEvent.ACTION_DROP:mSDAdapter.notifyDataSetChanged();//通知adaptermSDAdapter.setDragPosition(-1);if (mOnDragListener != null) {mOnDragListener.onDragViewDown(mCurrentPosition);}return true;case DragEvent.ACTION_DRAG_ENDED:return true;default:break;}return false;}//...................../*** 如果到了两端,判断ListView是往上滑动还是ListView往下滑动** @param position*/private void moveListViewUpOrDown(int position) {//ListView中最上面的显示的位置int firstPosition = getFirstVisiblePosition();//ListView中最下面的显示的位置int lastPosition = getLastVisiblePosition();//能够往上的话往上if ((position == firstPosition || position == firstPosition + 1) && firstPosition != 0) {smoothScrollToPosition(firstPosition - 1);}//能够往下的话往下if ((position == lastPosition || position == lastPosition - 1) && lastPosition != getCount() - 1) {smoothScrollToPosition(lastPosition + 1);}}//.....................
}

首先看到的前面一堆声明的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这块还不是特别熟悉,有不对的地方请指出。

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

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

响应事件是在这里:

public boolean onDrag(View v, DragEvent event) {return false;
}

其中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. Android 写一个可以横向滑动条目的列表

    在开发中,会发现很多列表希望条目能够侧滑,侧滑出来一两个按钮什么的,例如QQ就可以侧滑出删除按钮.这边文章就是教大家写一个可以侧滑的自定义控件.另外,本文的内容不是属于Android中比较高深的内容, ...

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

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

  3. RecyclerView 梳理:点击长按事件、分割线、拖曳排序、滑动删除

    本文作者 作者:OCNYang 链接:http://www.jianshu.com/p/70788a7a5547 本文由作者投稿发布. 这次主要是把 RecyclerView 比较常用的基本的点,在这 ...

  4. RecyclerView 梳理:点击amp;长按事件、分割线、拖曳排序、滑动删除

    这次主要是把 RecyclerView 比较常用的基本的点,在这里集中整理一下.从这篇文章主要梳理以下几点: 优雅的实现:item 点击事件 & item 长点击事件 RecyclerView ...

  5. 向下滑动动画android_Android SwipeRefreshLayout – Android向下拉动/向下滑动即可刷新

    向下滑动动画android In this tutorial we'll discuss and implement Android Swipe Down to Refresh or Android ...

  6. Android实现一个简易的新闻列表APP(TabLayout+ViewPager+Fragment)

    Android实现一个简易的新闻列表APP(TabLayout+ViewPager+Fragment) 文章目录 Android实现一个简易的新闻列表APP(TabLayout+ViewPager+F ...

  7. android camera滑动,Android怎么实现小米相机底部滑动指示器

    Android怎么实现小米相机底部滑动指示器 发布时间:2021-04-15 14:39:38 来源:亿速云 阅读:94 作者:小新 这篇文章给大家分享的是有关Android怎么实现小米相机底部滑动指 ...

  8. Android 系统(218)---Android的事件分发机制以及滑动冲突的解决

    Android的事件分发机制以及滑动冲突的解决 声明:  本文主要涉及VIew的事件分发与滑动冲突的解决,关于View的事件分发流程的部分内容参考自:  Android事件分发机制详解:史上最全面.最 ...

  9. Android Studio kotlin编程实现图片滑动浏览 stepbystep

    1 基本介绍 参照 kotlin官网:使用 RecyclerView 显示可滚动列表的 案例实现 功能:采用RecyclerView 空间实现图片和文字的滚动浏览. 在下图显示的序列中,可以看到一个填 ...

最新文章

  1. Solr相似度算法一:Lucene TF-IDF 相关性算分公式
  2. POI导出excel日期格式
  3. 详解实时查看网卡流量的几款工具
  4. [学习笔记] 单位根反演
  5. 商场楼层导视牌图片_百宝图商场电子导视软件中预约产品功能简介
  6. nginx 和 nodejs配置使用搭建网站
  7. SparkSQL JSON数据操作(1.3-1.4)
  8. 对硬盘进行分区时,GPT和MBR有什么区别?
  9. 互联网十大网络流行语
  10. java内存泄露 垃圾回收_Java中内存泄露及垃圾回收机制
  11. 尝试手写一个框架(二)手写一个MVC的框架
  12. 年会特辑丨池龙:上海“一网通办”政务服务模式分享
  13. day2——泰波那契数列
  14. 记录AK7739-TDM调试
  15. 银联商务MISPOS接口开发demo 需要调用POSINF.DLL
  16. 联想存储8GB缓存00MJ101大量现货做工精细
  17. 如何去掉Windows7开机那个烦人的360安全卫士提示窗口
  18. Ubuntu 22.04 2023.2更新后xubuntu 进入不了桌面
  19. 【物联网】物联网学习学科主要课程
  20. 途家、木鸟、爱彼迎:国内三大民宿平台用户定位策略分析

热门文章

  1. 使用Linq Xml 来读取一个目录下所有的文件并保存为Xml文档
  2. 选红叶家装不就得了,用得着这么费事吗?!
  3. NEO智能合约反编译工具
  4. Solaris10 for x86网卡替换配置
  5. Java配置环境变量及其意义
  6. ros与下位机通信常用的c++ boost串口应用
  7. 简明 Git 命令速查表(中文版)
  8. linux学习中遇到的各种故障与解决方法
  9. 新员工安全生产知识学习读本
  10. 《从问题到程序:用Python学编程和计算》——3.4 定义函数