侧滑删除

效果图:

android studioQQ左滑 新版qq左滑功能关闭_侧滑删除

实现步骤:

1. 创建SwipeLayout

public class SwipeLayout extends FrameLayout {
    public SwipeLayout(Context context) {
        super(context);
    }
    public SwipeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

2. 创建SwipeLayout的布局

<com.itcast.jq.swipelayout.view.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="55dp" >

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="right"
        android:orientation="horizontal" >

        <TextView
            android:layout_width="55dp"
            android:layout_height="match_parent"
            android:background="#44ff0000"
            android:gravity="center"
            android:text="删除" />

        <TextView
            android:layout_width="55dp"
            android:layout_height="match_parent"
            android:background="#4400ff00"
            android:gravity="center"
            android:text="取消" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#33ff0000" >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:paddingLeft="10dp"
            android:text="帅的人" />
    </LinearLayout>

</com.itcast.jq.swipelayout.view.SwipeLayout>

3. 重写回调方法获取控件尺寸及初始化位置

@Override
protected void onFinishInflate() {
    super.onFinishInflate();
    // System.out.println("---------onFinishInflate");

    if (getChildCount() < 2) {
        throw new IllegalStateException("SwipeLayout must has at least 2 children");
    }

    mBackView = getChildAt(0);
    mFrontView = getChildAt(1);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    // System.out.println("---------onMeasure");

    // 注意不能在onFinishInflate方法中获取宽高
    mWidth = getMeasuredWidth();
    mHeight = getMeasuredHeight();

    mBackWidth = mBackView.getMeasuredWidth();
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    // System.out.println("---------onMeasure");
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    // System.out.println("---------onMeasure");

    // 设置两个子控件的初始化位置
    mFrontView.layout(0, 0, mWidth, mHeight);
    mBackView.layout(mWidth, 0, mWidth + mBackWidth, mHeight);
}

4. 使用ViewDragHelper实现拖动效果

public SwipeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        // 1. 创建ViewDragHelper对象
        mDragHelper = ViewDragHelper.create(this, mCallback);
    }

    // 4. 处理回调方法
    Callback mCallback = new Callback() {

        // 根据返回决定是否可以拖动
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        };

        // 根据返回值决定被拖动的视图将要显示的位置
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return left;
        };  
    };

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 2. 让ViewDragHelper决定是否拦截事件
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 3. 传触摸事件传给ViewDragHelper处理
        mDragHelper.processTouchEvent(event);

        // 按下时需要返回true以便能持续地接收到触摸事件
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            return true;
        }       
        return super.onTouchEvent(event);
    }

5. 限制滑动范围

在clampViewPositionHorizontal方法中进行两个View的滑动范围限制

// 根据返回值决定被拖动的视图将要显示的位置
public int clampViewPositionHorizontal(View child, int left, int dx) {  
    // 限制frontView拖动的范围, [-mBackWidth, 0]
    if (child == mFrontView) {
        if (left < -mBackWidth) {
            left = -mBackWidth;
        } else if (left > 0) {
            left = 0;
        }
    } 

    if (child == mBackView) { 
        // 限制backView拖动的范围: [mWidth-mBackWidth, mWidth]
        if (left < mWidth-mBackWidth) {
            left = mWidth-mBackWidth;
        } else if (left > mWidth) {
            left = mWidth;
        }
    }   
    return left;
}

6. 关联滑动效果

在onViewPositionChanged方法通过offsetLeftAndRight方法关联滑动

// 当子控件位置发生改变后调用
public void onViewPositionChanged(View changedView, 
                                     int left, int top, int dx, int dy) {
    // 关联两个子控件的滑动
    if (changedView == mFrontView) { // 滑动mFrontView时,同时滑动mBackView
        mBackView.offsetLeftAndRight(dx);
    } else if (changedView == mBackView) {// 滑动mBackView时,同时滑动mFrontView
        mFrontView.offsetLeftAndRight(dx);
    }

    // 解决往左滑时mBackView空白的问题,同时兼容低版本2.3
    invalidate();
};

7. 打开或关闭SwipeLayout

// 松开手释放子控件时回调
// xvel: 往右甩速度大于0
public void onViewReleased(View releasedChild, float xvel, float yvel) {    
    int left = mFrontView.getLeft();
    if (xvel == 0 && left < -mBackWidth /2) {
        open();
    } else if (xvel < 0) { // 往左甩
        open();
    } else {
        close();
    }
};
/** 关闭SwipeLayout*/
public void close() {
    // 平滑滚动到某一位置: 第一步
    mDragHelper.smoothSlideViewTo(mFrontView, 0, 0);
    ViewCompat.postInvalidateOnAnimation(this);
}

/** 打开SwipeLayout */
public void open() {
    mDragHelper.smoothSlideViewTo(mFrontView, -mBackWidth, 0);
    ViewCompat.postInvalidateOnAnimation(this);
}

// 平滑滚动到某一位置: 第二步
@Override
public void computeScroll() {
    super.computeScroll();
    if (mDragHelper.continueSettling(true)) {
        ViewCompat.postInvalidateOnAnimation(this);
    }
}

8. 监听滑动状态(打开、关闭、准备打开、准备关闭)

// 当子控件位置发生改变后调用
public void onViewPositionChanged(View changedView, 
                                     int left, int top, int dx, int dy) {
    ... 
    // 监听滑动状态(打开,关闭,准备打开,准备关闭)
    listenDragStatus(); 
};
// ===========监听拖动状态(begin)============
public enum DragStatus {
    OPEN, CLOSE, DRAGGING
}
private DragStatus mCurrentStatus = DragStatus.CLOSE;

public DragStatus getDragStatus() {
    return mCurrentStatus;
}

public interface OnDragListener {
    public void open(SwipeLayout swipeLayout);
    public void close(SwipeLayout swipeLayout);

    /** 准备打开 */
    public void onStartOpen(SwipeLayout swipeLayout);
    /** 准备关闭 */
    public void onStartClose(SwipeLayout swipeLayout);
}

private OnDragListener mOnDragListener;

public void setOnDragListener(OnDragListener onDragListener) {
    this.mOnDragListener = onDragListener;
}

/** 监听拖动状态  */
protected void listenDragStatus() {
    int left = mFrontView.getLeft();    
    DragStatus status;
    if (left == 0) {
        status = DragStatus.CLOSE;
    } else if (left == -mBackWidth) {
        status = DragStatus.OPEN;
    } else {
        status = DragStatus.DRAGGING;
    }

    if (mOnDragListener != null) {
        if (status == DragStatus.OPEN) {
            mOnDragListener.open(this);
        } else if (status == DragStatus.CLOSE) {
            mOnDragListener.close(this);
        } else { // 当前状态为拖动状态           
            // 上一次状态为关闭,当前状态为拖动,则表示准备打开
            if (mCurrentStatus == DragStatus.CLOSE) {
                mOnDragListener.onStartOpen(this);
            } else if (mCurrentStatus == DragStatus.OPEN) {
                // 上一次状态为打开,当前状态为拖动,则表示准备关闭
                mOnDragListener.onStartClose(this);
            }
        }
    }
    mCurrentStatus = status;
}
// ===========监听拖动状态(end)============

9. 集成到ListView(显示列表)

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mListView = (ListView) findViewById(R.id.list_view);
    mAdapter = new ContactAdapter(this, 
            Arrays.asList(Constant.LIST_DATAS2));
    mListView.setAdapter(mAdapter);
}
public class ContactAdapter extends MyBaseAdapter<String> {

    public ContactAdapter(Context context, List<String> listDatas) {
        super(context, listDatas);
    }

    @Override
    public BaseHolder<String> createViewHolder(Context context, 
            ViewGroup parent, String bean, int position) {
        return new ContactHolder(context, parent, this, position, bean);
    }
}
public class ContactHolder extends BaseHolder<String> {
    private TextView tvName;

    public ContactHolder(Context context, ViewGroup parent, 
            MyBaseAdapter<String> adapter, int position, String bean) {
        super(context, parent, adapter, position, bean);
    }

    @Override
    public View createView(Context context, ViewGroup parent, int position, String bean) {
        return super.inflater.inflate(R.layout.item_list, parent, false);
    }

    @Override
    protected void initView() {
        tvName = (TextView) super.itemView.findViewById(R.id.tv_name);
    }

    @Override
    protected void onRefreshViews(String bean, int position) {
        tvName.setText(bean);
    }
}

10. 只打开一个SwipeLayout

public class SwipeLayoutManager {
    // 单例
    private static SwipeLayoutManager instance = new SwipeLayoutManager();
    private SwipeLayoutManager() {
    }
    public static SwipeLayoutManager getInstance() {
        return instance;
    }

    /** 用来记录打开的SwipeLayout */
    private SwipeLayout mOpenSwipeLayout;

    public SwipeLayout getOpenSwipeLayout() {
        return mOpenSwipeLayout;
    }

    /** 记录列表中打开的SwipeLayout */
    public void setOpenSwipeLayout(SwipeLayout swipeLayout) {
        this.mOpenSwipeLayout = swipeLayout;
    }

    /** 关闭之前打开的SwipeLayout */
    public void closeSwipeLayout() {
        if (mOpenSwipeLayout != null) {
            mOpenSwipeLayout.close();
            mOpenSwipeLayout = null; // 关闭后需要置为空
        }
    }
}
// 列表的Holder类的查找item子控件的方法
protected void initView() {
    ... 
    SwipeLayout layout =  (SwipeLayout) super.itemView;
    layout.setOnDragListener(new OnDragListener() {     
        @Override
        public void onOpen(SwipeLayout swipeLayout) {
        }
        // 准备打开某一个列表项的SwipeLayout时,先关闭之前打开的,再保存当前打开的
        @Override
        public void onStartOpen(SwipeLayout swipeLayout) {
            if (SwipeLayoutManager.getInstance().getOpenSwipeLayout() 
                != swipeLayout) {
                SwipeLayoutManager.getInstance().closeSwipeLayout();
            }
            SwipeLayoutManager.getInstance().setOpenSwipeLayout(swipeLayout);
        }

        @Override
        public void onStartClose(SwipeLayout swipeLayout) {
        }       
        @Override
        public void onClose(SwipeLayout swipeLayout) {
        }
    });
}

11. 优化体验:如果左右滑动请求父控件不要拦截事件

当左右滑动时,请求列表不要拦截事件
// 当子控件位置发生改变后调用
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
    ...
    // 如果是左右滑动SwipeLayout,请求父控件ListView不要拦截事件,则
    // ListView不会上下滚动,并且会把触摸事件传递下来,让SwipeLayout处理
    if (Math.abs(dx) > Math.abs(dy)) {
        requestDisallowInterceptTouchEvent(true);
    } 
}

12. 滚动列表时关闭打开的SwipeLayout

// 滚动列表时,关闭打开的SwipeLayout
mListView.setOnScrollListener(new OnScrollListener() {
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
            SwipeLayoutManager.getInstance().closeSwipeLayout();
        }
    }   
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    }
});

13. 列表项中的操作菜单点击事件

// 在列表的Holder类中添加
protected void initView() {
    tvName = (TextView) super.itemView.findViewById(R.id.tv_name);
    tv_delete = (TextView) super.itemView.findViewById(R.id.tv_delete);
    tv_cancel = (TextView) super.itemView.findViewById(R.id.tv_cancel);

    OnClickListener onClickListener = new OnClickListener() {       
        @Override
        public void onClick(View v) {
            SwipeLayoutManager.getInstance().closeSwipeLayout();
            switch (v.getId()) {
            case R.id.tv_delete:
                showToast("取消:" + bean);
                break;
            case R.id.tv_cancel:
                showToast("删除:" + bean);
                break;
            }
        }
    };  
    tv_delete.setOnClickListener(onClickListener);
    tv_cancel.setOnClickListener(onClickListener);
    ...
}

完整