QQ的一個聊天界面的listview每一行向左滑動的時候,會出現刪除的按鈕,特別炫酷,這個效果可以有,今天跟大家分享下。
先上demo的效果圖
界面很醜,因爲主要是介紹功能,界面什麼的,搞那麼複雜,下demo的時候還浪費資源,哈哈哈。
用到的幾個類(4個)
SwipeItemLayout,SwipeListView,SwipeAdapter,FragmentTestActivity.
SwipeItemLayout就是listView的一個item,這個類集成了FrameLayout。SwipeListView是重寫的一個ListView,其實主要在她的OnTouch事件的處理上。SwipeAdapter是一個adapter,這個不用解釋了,FragmenTestActivity這個就是怎麼用的了。
好,一個一個來
首先我們看一個item怎麼寫,先上代碼,代碼裏面基本上有逐行的解釋。
public class SwipeItemLayout extends FrameLayout {
//這個是內容的item,也就是不左滑的時候的佈局
private View contentView = null;
//這個是左滑之後顯示的那個部分,即多出的部分
private View menuView = null;
//這個是動畫的速度控制器,其實沒用到
private Interpolator closeInterpolator = null;
private Interpolator openInterpolator = null;
//控制控件滑動的,會平滑滑動,一個開一個關
private ScrollerCompat mOpenScroller;
private ScrollerCompat mCloseScroller;
//左滑之後,contentView左邊距離屏幕左邊的距離,基線,用於滑回
private int mBaseX;
//手指點擊的初始位置
private int mDownX;
//當前item的狀態,open和close兩種
private int state = STATE_CLOSE;
private static final int STATE_CLOSE = 0;
private static final int STATE_OPEN = 1;
//構造函數
public SwipeItemLayout(View contentView,View menuView,Interpolator closeInterpolator, Interpolator openInterpolator){
super(contentView.getContext());
this.contentView = contentView;
this.menuView = menuView;
this.closeInterpolator = closeInterpolator;
this.openInterpolator = openInterpolator;
init();
}
private void init(){
//設置一個item的寬和高,其實就是設置寬充滿而已
setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT));
//初始化mColoseScroller和mOpenScroller
if (closeInterpolator != null) {
mCloseScroller = ScrollerCompat.create(getContext(),
closeInterpolator);
} else {
mCloseScroller = ScrollerCompat.create(getContext());
}
if (openInterpolator != null) {
mOpenScroller = ScrollerCompat.create(getContext(),
openInterpolator);
} else {
mOpenScroller = ScrollerCompat.create(getContext());
}
//這也是設置寬和高
LayoutParams contentParams = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
contentView.setLayoutParams(contentParams);
menuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
//將這兩個佈局都add到這個view中
addView(contentView);
addView(menuView);
}
//這個類就是當用戶在界面上滑動的時候,通過ListView的onTouch方法,將MotionEvent的動作傳到這裏來,通過這個函數執行操作。
public boolean onSwipe(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//記錄當前手指點擊的x的座標
mDownX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
//當手指移動的時候,獲取這個差值
int dis = (int) (mDownX - event.getX());
//這個地方,當狀態是open的時候,爲啥要執行 += 這個操作,我們在下面就會找到答案的
if (state == STATE_OPEN) {
dis += menuView.getWidth();
}
//這個函數在下面說
swipe(dis);
break;
case MotionEvent.ACTION_UP:
//這裏其實是一個判斷,當用戶滑了menuView的一半的時候,自動滑出來,否則滑進去。
if ((mDownX - event.getX()) > (menuView.getWidth() / 2)) {
// 平滑的滑出
smoothOpenMenu();
} else {
// 平滑的滑進
smoothCloseMenu();
return false;
}
break;
}
//這個地方一定要return true,才能保證這個動作不會繼續往下傳遞
return true;
}
// 判斷是否滑出的狀態
public boolean isOpen() {
return state == STATE_OPEN;
}
//這個方法就是滑動dis的距離,還記得那個 += 嗎,如果dis > menuView.getWidth()的 話,dis = menuView.getWidth().這樣,當滑到最大限度的時候,就不會滑動了
private void swipe(int dis) {
if (dis > menuView.getWidth()) {
dis = menuView.getWidth();
}
if (dis < 0) {
dis = 0;
}
// layout的四個參數分別是(l,t,r,b),這樣實現contentView的移動,這個應該沒問題的吧?
contentView.layout(-dis, contentView.getTop(),
contentView.getWidth() - dis, getMeasuredHeight());
// 這個跟上面方法一樣
menuView.layout(contentView.getWidth() - dis, menuView.getTop(),
contentView.getWidth() + menuView.getWidth() - dis,
menuView.getBottom());
}
//這個方法是系統的方法,就是執行一個刷新而已
@Override
public void computeScroll() {
if (state == STATE_OPEN) {
if (mOpenScroller.computeScrollOffset()) {
swipe(mOpenScroller.getCurrX());
postInvalidate();
}
} else {
if (mCloseScroller.computeScrollOffset()) {
swipe(mBaseX - mCloseScroller.getCurrX());
postInvalidate();
}
}
}
// 額,這個不用解釋了
public void smoothCloseMenu() {
state = STATE_CLOSE;
mBaseX = -contentView.getLeft();
mCloseScroller.startScroll(0, 0, mBaseX, 0, 350);
postInvalidate();
}
// 額 這個也不用解釋了
public void smoothOpenMenu() {
state = STATE_OPEN;
mOpenScroller.startScroll(-contentView.getLeft(), 0,
menuView.getWidth(), 0, 350);
postInvalidate();
}
// 這個也懶得解釋了
public void closeMenu() {
if (mCloseScroller.computeScrollOffset()) {
mCloseScroller.abortAnimation();
}
if (state == STATE_OPEN) {
state = STATE_CLOSE;
swipe(0);
}
}
// 各位碼大大最棒了
public void openMenu() {
if (state == STATE_CLOSE) {
state = STATE_OPEN;
swipe(menuView.getWidth());
}
}
public View getContentView() {
return contentView;
}
public View getMenuView() {
return menuView;
}
//這個方法 其實就是獲取menuView的寬和高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
menuView.measure(MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(
getMeasuredHeight(), MeasureSpec.EXACTLY));
}
//這個方法就把兩個控件的相對佈局表現出來了
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
contentView.layout(0, 0, getMeasuredWidth(),
contentView.getMeasuredHeight());
menuView.layout(getMeasuredWidth(), 0,
getMeasuredWidth() + menuView.getMeasuredWidth(),
contentView.getMeasuredHeight());
}
}
接下來就是另一個重要的類了,那就是ListView到底怎樣,之後我還是把源碼放上去,絕對不要積分,有需要的可以自己下了看看。這裏看下核心的代碼(主要原因是加班時間快要到了,再不回去的話就出不去了,哈哈哈)
這是變量,額,寫代碼的時候沒寫註釋,我怕你們看不懂額,就在這裏寫算了
//表示沒有觸摸的時候
private static final int TOUCH_STATE_NONE = 0;
// 水平滑動的時候哦
private static final int TOUCH_STATE_X = 1;
// 垂直滑動的時候
private static final int TOUCH_STATE_Y = 2;
//這是設置的兩個方向的閥值
private int MAX_Y = 5;
private int MAX_X = 3;
// 記錄初始時候的座標
private float mDownX;
//狀態標誌符
private int mTouchState;
// 觸摸的位置
private int mTouchPosition;
private SwipeItemLayout mTouchView;
//private OnSwipeListener mOnSwipeListener;
private Interpolator mCloseInterpolator;
private Interpolator mOpenInterpolator;
說完了變量之後,核心的就兩個方法
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null)
return super.onTouchEvent(ev);
int action = MotionEventCompat.getActionMasked(ev);
action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
int oldPos = mTouchPosition;
mDownX = ev.getX();
mDownY = ev.getY();
mTouchState = TOUCH_STATE_NONE;
//這個方法就是獲取當前的x,y座標對應的是listView中的哪個position,是系統方法。
mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());
if (mTouchPosition == oldPos && mTouchView != null
&& mTouchView.isOpen()) {
mTouchState = TOUCH_STATE_X;
mTouchView.onSwipe(ev);
return true;
}
//這個方法獲取當前的item的View,也是系統的方法
View view = getChildAt(mTouchPosition - getFirstVisiblePosition());
if (mTouchView != null && mTouchView.isOpen()) {
mTouchView.smoothCloseMenu();
mTouchView = null;
return super.onTouchEvent(ev);
}
if (view instanceof SwipeItemLayout) {
mTouchView = (SwipeItemLayout) view;
}
if (mTouchView != null) {
mTouchView.onSwipe(ev);
}
break;
case MotionEvent.ACTION_MOVE:
float dy = Math.abs((ev.getY() - mDownY));
float dx = Math.abs((ev.getX() - mDownX));
if (mTouchState == TOUCH_STATE_X) {
if (mTouchView != null) {
mTouchView.onSwipe(ev);
}
getSelector().setState(new int[] { 0 });
ev.setAction(MotionEvent.ACTION_CANCEL);
super.onTouchEvent(ev);
return true;
} else if (mTouchState == TOUCH_STATE_NONE) {
if (Math.abs(dy) > MAX_Y) {
mTouchState = TOUCH_STATE_Y;
} else if (dx > MAX_X) {
mTouchState = TOUCH_STATE_X;
// if (mOnSwipeListener != null) {
// mOnSwipeListener.onSwipeStart(mTouchPosition);
// }
}
}
break;
case MotionEvent.ACTION_UP:
if (mTouchState == TOUCH_STATE_X) {
if (mTouchView != null) {
mTouchView.onSwipe(ev);
if (!mTouchView.isOpen()) {
mTouchPosition = -1;
mTouchView = null;
}
}
// if (mOnSwipeListener != null) {
// mOnSwipeListener.onSwipeEnd(mTouchPosition);
// }
ev.setAction(MotionEvent.ACTION_CANCEL);
super.onTouchEvent(ev);
return true;
}
break;
}
return super.onTouchEvent(ev);
}
public void smoothOpenMenu(int position) {
if (position >= getFirstVisiblePosition()
&& position <= getLastVisiblePosition()) {
View view = getChildAt(position - getFirstVisiblePosition());
if (view instanceof SwipeItemLayout) {
mTouchPosition = position;
if (mTouchView != null && mTouchView.isOpen()) {
mTouchView.smoothCloseMenu();
}
mTouchView = (SwipeItemLayout) view;
mTouchView.smoothOpenMenu();
}
}
}
唉,實在是很麻煩,其實代碼不難,就是很繁瑣,各種控制,各種判斷,大家把這段代碼中的核心的幾個方法搞明白了,也就沒問題了。我就不多說了哈。。。
好了,剩下的兩個類,一個是adapter類,一個是用法,我就只說adapter中的一個getView方法了哈,用法的話跟一般的ListView的用法一樣,對了,說adapter不就是再說用法麼,哈哈哈。
@Override
public View getView(int position, View contentView, ViewGroup arg2) {
ViewHolder holder = null;
if(contentView==null){
holder = new ViewHolder();
View view01 = LayoutInflater.from(mContext).inflate(R.layout.test01, null);
View view02 = LayoutInflater.from(mContext).inflate(R.layout.test2, null);
//這個地方就用到了我們自己寫的那個類了,後面兩個參數我上面已經說了,沒用到,用到的話也可以,自己改下代碼就好了。其他的沒什麼區別吧
contentView = new SwipeItemLayout(view01, view02, null, null);
contentView.setTag(holder);
}else{
holder = (ViewHolder) contentView.getTag();
}
//這個地方如果你的menu裏面有button什麼的,就可以在這個地方註冊監聽,或者,你直接將view02(上面聲明的)自定義也行,在自定義的類中實現onClickListener 方法
// holder.btn.setOnClickListener(new OnClickListener() {
//
// @Override
// public void onClick(View arg0) {
// // TODO Auto-generated method stub
// Toast.makeText(mContext, "click", Toast.LENGTH_LONG).show();
// }
// });
return contentView;
}
是不是看完整個過程有點點暈,其實很簡單的,你下了源碼,自己去看就好了,真的簡單,各位大神不要見怪.