ListView的item水平滑動(類QQ的左滑顯示刪除按鈕)

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;
    }

是不是看完整個過程有點點暈,其實很簡單的,你下了源碼,自己去看就好了,真的簡單,各位大神不要見怪.


demo下載鏈接(免費)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章