1. 程式人生 > >下拉重新整理控制元件包裹下的ListView側滑出選單的實現

下拉重新整理控制元件包裹下的ListView側滑出選單的實現

package com.moopoo.widget;


import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Scroller;


/**
 * 側向滑出選單的ListView
 * 使用請注意與ListView的Item的佈局配合,
 * 該效果的實現是基於在Item的佈局中通過設定PaddingLeft和PaddingRight來隱藏左右選單的,
 * 所以使用此ListView時,請務必在佈局Item時使用PaddingLeft和PaddingRight;
 * 或者自己改寫此ListView,已達到想要的實現方式
 */
public class SlideListView extends ListView {

    /**下拉重新整理view*/
    private SwipeRefreshLayout mSwipeLayout;
    /**禁止側滑模式*/
    public static int MOD_FORBID = 0;
    /**從左向右滑出選單模式*/
    public static int MOD_LEFT = 1;
    /**從右向左滑出選單模式*/
    public static int MOD_RIGHT = 2;
    /**左右均可以滑出選單模式*/
    public static int MOD_BOTH = 3;
    /**當前的模式*/
    private int mode = MOD_FORBID;
    /**左側選單的長度*/
    private int leftLength = 0;
    /**右側選單的長度*/
    private int rightLength = 0;

    /**
     * 當前滑動的ListView position
     */
    private int slidePosition;
    /**
     * 手指按下X的座標
     */
    private int downY;
    /**
     * 手指按下Y的座標
     */
    private int downX;
    /**
     * ListView的item
     */
    private View itemView;
    /**
     * 滑動類
     */
    private Scroller scroller;
    /**
     * 認為是使用者滑動的最小距離
     */
    private int mTouchSlop;

    /**
     * 判斷是否可以側向滑動
     */
    private boolean canMove = false;
    /**
     * 標示是否完成側滑
     */
    private boolean isSlided = false;
    

    public SlideListView(Context context) {
        this(context, null);
    }

    public SlideListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlideListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        scroller = new Scroller(context);
        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    }

    /**
     * 初始化選單的滑出模式
     * @param mode
     */
    public void initSlideMode(int mode){
        this.mode = mode;
    }

    /**
     * 處理我們拖動ListView item的邏輯
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        final int action = ev.getAction();
        int lastX = (int) ev.getX();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
			/*當前模式不允許滑動,則直接返回,交給ListView自身去處理*/
                if(this.mode == MOD_FORBID){
                    return super.onTouchEvent(ev);
                }

                // 如果處於側滑完成狀態,側滑回去,並直接返回
                if (isSlided) {
                    scrollBack();
                    return false;
                }
                // 假如scroller滾動還沒有結束,我們直接返回
                if (!scroller.isFinished()) {
                    return false;
                }
                downX = (int) ev.getX();
                downY = (int) ev.getY();

                slidePosition = pointToPosition(downX, downY);

                // 無效的position, 不做任何處理
                if (slidePosition == AdapterView.INVALID_POSITION) {
                    return super.onTouchEvent(ev);
                }

                // 獲取我們點選的item view
                itemView = getChildAt(slidePosition - getFirstVisiblePosition());

			/*此處根據設定的滑動模式,自動獲取左側或右側選單的長度*/
                if(this.mode == MOD_BOTH){
                    this.leftLength = -itemView.getPaddingLeft();
                    this.rightLength = -itemView.getPaddingRight();
                }else if(this.mode == MOD_LEFT){
                    this.leftLength = -itemView.getPaddingLeft();
                }else if(this.mode == MOD_RIGHT){
                    this.rightLength = -itemView.getPaddingRight();
                }

                break;
            case MotionEvent.ACTION_MOVE:
                if (!canMove
                        && slidePosition != AdapterView.INVALID_POSITION
                        && (Math.abs(ev.getX() - downX) > mTouchSlop && Math.abs(ev
                        .getY() - downY) < mTouchSlop)) {
                    // 禁用下拉重新整理控制元件,避免其攔截onTouch事件
                    mSwipeLayout.setEnabled(false);

                    int offsetX = downX - lastX;
                    if(offsetX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){
					/*從右向左滑*/
                        canMove = true;
                    }else if(offsetX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){
					/*從左向右滑*/
                        canMove = true;
                    }else{
                        canMove = false;
                    }
				/*此段程式碼是為了避免我們在側向滑動時同時觸發ListView的OnItemClickListener時間*/
                    MotionEvent cancelEvent = MotionEvent.obtain(ev);
                    cancelEvent
                            .setAction(MotionEvent.ACTION_CANCEL
                                    | (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
                    onTouchEvent(cancelEvent);
                }
                if (canMove) {
				/*設定此屬性,可以在側向滑動時,保持ListView不會上下滾動*/
                    requestDisallowInterceptTouchEvent(true);

                    // 手指拖動itemView滾動, deltaX大於0向左滾動,小於0向右滾
                    int deltaX = downX - lastX;
                    if(deltaX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){
					/*向左滑*/
                        itemView.scrollTo(deltaX, 0);
                    }else if(deltaX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){
					/*向右滑*/
                        itemView.scrollTo(deltaX, 0);
                    }else{
                        itemView.scrollTo(0, 0);
                    }
                    return true; // 拖動的時候ListView不滾動
                }
            case MotionEvent.ACTION_UP:
                mSwipeLayout.setEnabled(true);
                //requestDisallowInterceptTouchEvent(false);
                if (canMove){
                    canMove = false;
                    scrollByDistanceX();
                }
                break;
        }

        // 否則直接交給ListView來處理onTouchEvent事件
        return super.onTouchEvent(ev);
    }

    /**
     * 根據手指滾動itemView的距離來判斷是滾動到開始位置還是向左或者向右滾動
     */
    private void scrollByDistanceX() {
		/*當前模式不允許滑動,則直接返回*/
        if(this.mode == MOD_FORBID){
            return;
        }
        if(itemView.getScrollX() > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){
			/*從右向左滑*/
            if (itemView.getScrollX() >= rightLength / 2) {
                scrollLeft();
            }  else {
                // 滾回到原始位置
                scrollBack();
            }
        }else if(itemView.getScrollX() < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){
			/*從左向右滑*/
            if (itemView.getScrollX() <= -leftLength / 2) {
                scrollRight();
            } else {
                // 滾回到原始位置
                scrollBack();
            }
        }else{
            // 滾回到原始位置
            scrollBack();
        }

    }

    /**
     * 往右滑動,getScrollX()返回的是左邊緣的距離,就是以View左邊緣為原點到開始滑動的距離,所以向右邊滑動為負值
     */
    private void scrollRight() {
        isSlided = true;
        final int delta = (leftLength + itemView.getScrollX());
        // 呼叫startScroll方法來設定一些滾動的引數,我們在computeScroll()方法中呼叫scrollTo來滾動item
        scroller.startScroll(itemView.getScrollX(), 0, -delta, 0,
                Math.abs(delta));
        postInvalidate(); // 重新整理itemView
    }

    /**
     * 向左滑動,根據上面我們知道向左滑動為正值
     */
    private void scrollLeft() {
        isSlided = true;
        final int delta = (rightLength - itemView.getScrollX());
        // 呼叫startScroll方法來設定一些滾動的引數,我們在computeScroll()方法中呼叫scrollTo來滾動item
        scroller.startScroll(itemView.getScrollX(), 0, delta, 0,
                Math.abs(delta));
        postInvalidate(); // 重新整理itemView
    }

    /**
     * 滑動會原來的位置
     */
    private void scrollBack() {
        isSlided = false;
        scroller.startScroll(itemView.getScrollX(), 0, -itemView.getScrollX(),
                0, Math.abs(itemView.getScrollX()));
        postInvalidate(); // 重新整理itemView
    }

    @Override
    public void computeScroll() {
        // 呼叫startScroll的時候scroller.computeScrollOffset()返回true,
        if (scroller.computeScrollOffset()) {
            // 讓ListView item根據當前的滾動偏移量進行滾動
            itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY());

            postInvalidate();
        }
    }

    /**
     * 提供給外部呼叫,用以將側滑出來的滑回去
     */
    public void slideBack() {
        this.scrollBack();
    }

    /**
     * 外部傳進下拉重新整理view
     * by skypupil
     */
    public void setParentView(SwipeRefreshLayout swipeLayout){
        mSwipeLayout = swipeLayout;
    }

}