1. 程式人生 > >Android ListView 側滑效果實現(滑動展開、滑動刪除)

Android ListView 側滑效果實現(滑動展開、滑動刪除)

轉載請註明出處:http://blog.csdn.net/lonelyroamer/article/details/42439875

專案需要ListView滑動刪除的效果,首先肯定是拿來主義,在網上搜了一遍,發現這樣的東西真不少,比較有名的Github上的SwipeListView。但是個人嘗試了一下,發現它的bug不少,並且達不到我想要的效果。於是又嘗試了一下其他的例子,發現基本效果都有,但是都有不少問題。要麼事件衝突,要麼OnItemListView或者某個Button響應不了。沒辦法,只能自己著手寫一個。

最開始,寫了一個最簡單的,只支援右邊的側滑,並且只支援一種滑動的模式,先在專案裡面用上。然後想將這個專案完善起來,斷斷續續的寫了一段時間。期間Android5.0釋出了,新出的控制元件RecyclerView 將要取代ListView的地位。頓時覺得自己做了無用功,然後就中斷了。

但是後面發現RecyclerView畢竟是新出的控制元件,很多基於ListView的第三方庫,RecyclerView並沒有(用的人少,自然沒人去寫)。於是又把這個撿起來重新寫,說不定以後會用得到。

先看一下執行的demo效果,


實現的具體細節就不描述了,後面會上傳完整的程式碼。說一下使用的問題。

1、在你的layout檔案中新增改控制元件:

    <com.roamer.slidelistview.SlideListView
        xmlns:slide="http://schemas.android.com/apk/res-auto"
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        slide:slideAnimationTime="200"
        slide:slideLeftAction="scroll"
        slide:slideMode="both"
        slide:slideRightAction="scroll" />

2、繼承SlideBaseAdapter,重寫裡面的getFrontViewId(),getLeftBackViewId(),getRightBackViewId()方法。
	@Override
	public int getFrontViewId(int position) {
		return R.layout.row_front_view;
	}

	@Override
	public int getLeftBackViewId(int position) {
		return R.layout.row_left_back_view;
	}

	@Override
	public int getRightBackViewId(int position) {
		return R.layout.row_right_back_view;
	}
一個Item可不可以側滑,取決於三點,優先順序從上到下。

(1)、你是否為該Item提供了LeftBackView或者RightBackView

(2)、在SlideBaseAdapter中有一個方法,可以改變某個位置的SlideMode,你可以單獨改變某個position的SlideMode

	/**
	 * At first,your whole item slide mode is base on the SlideListView's
	 * SlideMode.<br/>
	 * but your can change the slide mode at one or more position in this
	 * adapter by override this method
	 * 
	 * @param position
	 * @return
	 */
	public SlideMode getSlideModeInPosition(int position) {
		return mSlideMode;
	}

(3)、你的SlideListView設定的SlideMode的值


3、SlideAction表示側滑展開的方式,目前有兩種SCROLL和REVEAL。效果分別如下:

SCROLL:


REVEAL:

4、如果你的front view 和back view組合起來有多種檢視,那麼你還必須重寫getItemViewType()和getViewTypeCount()方法,否則檢視重用的時候會混亂

	@Override
	public int getFrontViewId(int position) {
		return R.layout.row_front_view;
	}

	@Override
	public int getLeftBackViewId(int position) {
		if (position % 2 == 0) {
			return R.layout.row_left_back_view;
		}
		return R.layout.row_right_back_view;
	}

	@Override
	public int getRightBackViewId(int position) {
		return R.layout.row_right_back_view;
	}

	@Override
	public int getItemViewType(int position) {
		if (position % 2 == 0) {
			return 0;
		}
		return 1;
	}

	@Override
	public int getViewTypeCount() {
		return 2;
	}

如果該程式碼對你有幫助,不勝榮幸。如果有BUG,歡迎指正。

有人說你上傳個資源還要分,好吧,這次不要分。下載戳這裡

BUG FIX

1、OnScrollListener不正確回撥導致的問題(2015-01-06 10點)

這樣就會導致當滑動到頂部或者底部,然後在繼續滑動的時候,isInScrolling得不到正確的狀態值。下一次滑動的時候會導致Item不能滑動。

	private OnScrollListener mInnerOnScrollListener = new OnScrollListener() {
		@Override
		public void onScrollStateChanged(AbsListView view, int scrollState) {
			if (scrollState == SCROLL_STATE_IDLE) {
				isInScrolling = false;
			} else {
				isInScrolling = true;
			}
			if (mOnScrollListener != null) {
				mOnScrollListener.onScrollStateChanged(view, scrollState);
			}
		}

		@Override
		public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
			if (mOnScrollListener != null) {
				mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
			}
		}
	};

解決辦法:

在SlideListView中新增如下程式碼:

private static Field sTouch_Mode_Field;
	static {
		try {
			sTouch_Mode_Field = AbsListView.class
					.getDeclaredField("mTouchMode");
			sTouch_Mode_Field.setAccessible(true);
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		}
	}
	void checkScrolling() {
		if (!isInScrolling) {
			return;
		}
		if(sTouch_Mode_Field == null){
			return;
		}
		int touchMode = 0;
		try {
			touchMode = sTouch_Mode_Field.getInt(this);
		} catch (IllegalAccessException e1) {
			e1.printStackTrace();
		} catch (IllegalArgumentException e1) {
			e1.printStackTrace();
		}
		if (DEUBG) {
			Log.d(TAG, "mTouchMode:" + touchMode);
		}
		if (touchMode == -1) {// touchMode==TOUCH_MODE_REST
			isInScrolling = false;
		}
	}

在SlideTouchListener的onInterceptTouchEvent方法中新增下面標紅的程式碼:

                // don't allow swiping if this is on the header or footer or
                // IGNORE_ITEM_VIEW_TYPE or enabled is false on the adapter
                boolean allowSlide = mSlideListView.getAdapter().isEnabled(position) && mSlideListView.getAdapter().getItemViewType(position) >= 0;
                if (allowSlide) {
                    if (Build.VERSION.SDK_INT <= 11) {// below 3.0
                        mSlideListView.checkScrolling();
                    }

                    mDownPosition = position;
                    mActivePointerId = event.getPointerId(0);
                    mDownMotionX = (int) event.getX();
                    initOrResetVelocityTracker();
                    mVelocityTracker.addMovement(event);
                }

2、SlideItem不正常關閉(Adapter資料集的改變,action的改變、adapter的改變),導致Front  view不能響應事件的問題(2015-01-09)

解決辦法:將SlideListView的setAdapter方法改成如下:

	@Override
	public void setAdapter(ListAdapter adapter) {
		mAdapter = null;
		if (adapter != null && adapter instanceof SlideBaseAdapter) {
			mAdapter = (SlideBaseAdapter) adapter;
			mAdapter.setSlideMode(mSlideMode);
			mAdapter.setSlideLeftAction(mSlideLeftAction);
			mAdapter.setSlideRightAction(mSlideRightAction);
		}
		super.setAdapter(adapter);

		if (mTouchListener.isOpend()) {
			mTouchListener.closeOpenedItem();
		} else {
			mTouchListener.reset();
		}

		if (adapter != null) {
			adapter.registerDataSetObserver(new DataSetObserver() {
				@Override
				public void onChanged() {
					super.onChanged();
					if (DEUBG) {
						Log.e(TAG, "Adapter data has changed");
					}
					if (mTouchListener.isOpend()) {
						mTouchListener.closeOpenedItem();
					} else {
						mTouchListener.reset();
					}
				}
			});
		}
	}


3、Android2.3.3以下使用Nineoldandroids第三方庫中屬性動畫的bug,導致Item劃開後,backview不能點選。

這個問題找了很久,才終於發現是問題。屬性動畫是Android3.0以後才新增的,所以才有人寫了Nineoldandroids,用於3.0以下方便的使用屬性動畫。但是看了Nineoldandroids的程式碼,發現2.3.3以下並不是真正能達到屬性動畫的效果。運行了Github上面的SwipeListView(它也使用了Nineoldandroids庫),發現在2.3.3以下也有這個BUG。

這個BUG已經解決,2.3.3以下可以使用了,但是效果不算太好。

最新的版本已上傳到Github,雖然沒什麼人用,但是也算是自己的一番心血

主動開啟和關閉你可以參考下,我最近很忙,沒時間修改,可能這樣做還有問題,以後我會在自己把這個功能加上。

(1)、主動開啟

在SlideTouchListener加入下面的方法

	void openDirect(int position, boolean left) {
		mDownPosition = position;
		mSlideItem = new SlideItem(position);

		SlideMode slideMode = mSlideListView.getSlideAdapter().getSlideModeInPosition(mSlideItem.position - mSlideListView.getHeaderViewsCount());
		if (left) {// 要開啟左邊
			if (slideMode == SlideMode.LEFT || slideMode == SlideMode.BOTH) {// SlideMode support left
				mSlideItem.offset = 1;
			}
		} else {
			if (slideMode == SlideMode.RIGHT || slideMode == SlideMode.BOTH) {// SlideMode support right
				mSlideItem.offset = -1;
			}
		}
		move(mSlideItem.offset);
		autoScroll(mSlideItem.offset, true);
	}

在SlideListView中呼叫即可主動開啟指定位置
	public void openDirect(int position, boolean left) {
		int opendPosition = mTouchListener.getOpendPosition();
		if (opendPosition == position) {
			return;
		}
		closeDirect();
		mTouchListener.openDirect(position, left);
	}

(2)、主動關閉

將SlideListView中的 closeDirect() 改為public即可

(3)、滑動有時候會卡 具體原因我也不清楚。我應用在我的專案中,沒有發現。你看下Item的佈局複雜程式。還有可能和手機的系統、效能也有關。我在模擬器Android3.0以上,都發現挺好的。