Android开发之仿QQ侧滑删除实现(二)

来源:互联网 发布:蜂群算法代码 编辑:程序博客网 时间:2024/06/05 14:12

一、把SlideDelete的简单样式先做出来。

SlideDelete继承自ViewGroup,在引用SlideDelete的xml的位置include进两个layout,一个是内容,一个是删除

一、1、准备两个布局

内容部分

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="60dp"    android:background="#97d8da"    android:gravity="center"    android:orientation="vertical">    <TextView        android:id="@+id/mTvContent"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="文本"        android:gravity="center"        android:textSize="28dp"        /></LinearLayout>

内容部分.png

删除部分

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="130dp"    android:layout_height="60dp"    android:background="#ff0000"    android:gravity="center"    android:orientation="vertical">    <TextView        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="删除"        android:textSize="22dp"        android:textColor="#ffffff"        /></LinearLayout>

删除部分.png

一.2、在activity_main里面把放进SlideDelete这个控件,然后include进上面两个布局

activity_main

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.amqr.slidedelete.MainActivity">    <com.amqr.slidedelete.view.SlideDelete        android:layout_width="match_parent"        android:layout_height="60dp">        <!--文本部分-->        <include layout="@layout/slide_content"/>        <!--删除部分-->        <include layout="@layout/slide_delete"/>    </com.amqr.slidedelete.view.SlideDelete></RelativeLayout>

一.3、在自定义控件SlideDelete里面把利用onFinishInflate、onMeasure和onLayout让控件显示出来

public class SlideDelete extends ViewGroup{    private View mContent; // 内容部分    private View mDelete;  // 删除部分    public SlideDelete(Context context) {        super(context);    }    public SlideDelete(Context context, AttributeSet attrs) {        super(context, attrs);    }    public SlideDelete(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onFinishInflate() {        super.onFinishInflate();        mContent = getChildAt(0);        mDelete = getChildAt(1);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);        // 这跟mContent的父亲的大小有关,父亲是宽填充父窗体,高度是和孩子一样是60dp        mContent.measure(widthMeasureSpec,heightMeasureSpec); // 测量内容部分的大小        LayoutParams layoutParams = mDelete.getLayoutParams();        int deleteWidth = MeasureSpec.makeMeasureSpec(layoutParams.width,MeasureSpec.EXACTLY);        int deleteHeight = MeasureSpec.makeMeasureSpec(layoutParams.height,MeasureSpec.EXACTLY);        // 这个参数就需要指定为精确大小        mDelete.measure(deleteWidth,deleteHeight); // 测量删除部分的大小        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int mContentWidth = mContent.getMeasuredWidth();        int mContentHeight = mContent.getMeasuredHeight();        mContent.layout(0,0,mContentWidth,mContentHeight); // 摆放内容部分的位置        int mDeleteWidth = mDelete.getMeasuredWidth();        mDelete.layout(mContentWidth,0,                mContentWidth + mDeleteWidth, mContentHeight); // 摆放删除部分的位置    }}

至此,像下面这样右侧其实 删除部分 已经绘制显示在手机的右侧了,只是屏幕上现在暂时还看不见






在本文中,我们用ViewDragHelper来做动画效果。

ViewDragHelper简介

ViewDragHelper是v4包种提供的用来的做滑动的

ViewDragHelper是在android4.3的时候发布的,所以必须是android4.3以上的v4包才能支持ViewDragHelper

我们这里的Demo要使用ViewDragHelper,所以我们在当面as的项目的gradle里面引入v4包。

compile 'com.android.support:support-v4:23.1.1'

ViewDragHelper怎么用?

简单说,分两步

第一步,
利用ViewDragHelper.create(ViewGroup forParent, Callback cb)创建一个ViewDragHelper的实例。

第二步,弄一个类继承自ViewDragHelper.Callback,作为第一步中create方法的参数,复写一下这么几个方法并且一些逻辑操作

tryCaptureView()
clampViewPositionHorizontal()
clampViewPositionVertical()
onViewPositionChanged()
onViewReleased()

实现动画效果。

第二步中的复写的那几个方法很重要。这几个方法到底分别的有什么用呢?可以大概这么理解:
我们知道TouchEvent大概可以分为三个状态,Down(按下)、Move(移动)和Up(抬起)。

那么在这三个不同的状态里面,与之关联的就是上面的几个方法:

  • Touch的down事件:
    回调tryCaptureView()

  • Touch的move事件
    回调
    clampViewPositionHorizontal()
    clampViewPositionVertical()
    onViewPositionChanged()

  • Touch的up事件
    回调:onViewReleased()

来个代码看一下
继承自ViewDragHelper.Callback的类复写的几个方法。

class MyDrawHelper extends ViewDragHelper.Callback {    /**     * Touch的down事件会回调这个方法 tryCaptureView     *     * @Child:指定要动的孩子  (哪个孩子需要动起来)     * @pointerId: 点的标记     * @return : ViewDragHelper是否继续分析处理 child的相关touch事件     */    @Override    public boolean tryCaptureView(View child, int pointerId) {        System.out.println("调用tryCaptureView");        System.out.println("contentView : " + (mContent == child));        return mContent == child || mDelete == child;    }    // Touch的move事件会回调这面这几个方法    // clampViewPositionHorizontal    // clampViewPositionVertical    // onViewPositionChanged    /**     *     * 捕获了水平方向移动的位移数据     * @param child 移动的孩子View     * @param left 父容器的左上角到孩子View的距离     * @param dx 增量值,其实就是移动的孩子View的左上角距离控件(父亲)的距离,包含正负     * @return 如何动     *     * 调用完此方法,在android2.3以上就会动起来了,2.3以及以下是海动不了的     * 2.3不兼容怎么办?没事,我们复写onViewPositionChanged就是为了解决这个问题的     */    @Override    public int clampViewPositionHorizontal(View child, int left, int dx) {        Log.d("Slide","增量值:   "+left);        return left;    }    @Override    public int clampViewPositionVertical(View child, int top, int dy) {        return super.clampViewPositionVertical(child, top, dy);    }    /**     * 当View的位置改变时的回调     * @param changedView  哪个View的位置改变了     * @param left  changedView的left     * @param top  changedView的top     * @param dx x方向的上的增量值     * @param dy y方向上的增量值     */    @Override    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {        invalidate();        super.onViewPositionChanged(changedView, left, top, dx, dy);    }    /**     * 相当于Touch的up的事件会回调onViewReleased这个方法     *     * @param releasedChild     * @param xvel x方向的速率     * @param yvel y方向的速率     */    @Override    public void onViewReleased(View releasedChild, float xvel, float yvel) {        super.onViewReleased(releasedChild, xvel, yvel);    }}

通过上面的详细的备注,我们这些方法的用途都有了相当的了解了。需要注意的是,我们的clampViewPositionHorizontal和clampViewPositionHorizontal所产生的动画效果在2.3以上才会有效果,如果要达到兼容,我们就需要借助onViewPositionChanged方法。

好啦,现在可以开始做动画了。

二.1、当一个孩子动起来另外一个孩子也可以跟随着动起来

        /**         * 当View的位置改变时的回调  这个方法的价值是结合clampViewPositionHorizontal或者clampViewPositionVertical         * @param changedView  哪个View的位置改变了         * @param left  changedView的left         * @param top  changedView的top         * @param dx x方向的上的增量值         * @param dy y方向上的增量值         */        @Override        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {            //super.onViewPositionChanged(changedView, left, top, dx, dy);            invalidate();            if(changedView == mContent){ // 如果移动的是mContent                //我们移动mContent的实惠要相应的联动改变mDelete的位置                // 怎么改变mDelete的位置,当然是mDelete的layput方法啦                int tempDeleteLeft = mContentWidth+left;                int tempDeleteRight = mContentWidth+left + mDeleteWidth;                mDelete.layout(tempDeleteLeft,0,tempDeleteRight,mDeleteHeight);            }else{ // touch的是mDelete                int tempContentLeft = left - mContentWidth;                int tempContentRight = left;                mContent.layout(tempContentLeft,0,tempContentRight,mContentHeight);            }        }

跟着一起动.gif

孩子是一起跟着动起来了,出现越界的问题

二.2、解决越界问题

这个越界的问题为什么会产生,是因为现在在clampViewPositionHorizontal方法里面我们简单粗暴地返回了left。这样肯定是不行的。所以我们需要在这个方法上做一些处理

/** * * 捕获了水平方向移动的位移数据 * @param child 移动的孩子View * @param left 父容器的左上角到孩子View的距离 * @param dx 增量值,其实就是移动的孩子View的左上角距离控件(父亲)的距离,包含正负 * @return 如何动 * * 调用完此方法,在android2.3以上就会动起来了,2.3以及以下是海动不了的 * 2.3不兼容怎么办?没事,我们复写onViewPositionChanged就是为了解决这个问题的 */@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {    Log.d("Slide","增量值:   "+left);    if(child == mContent){ // 解决内容部分左右拖动的越界问题        if(left>0){            return 0;        }else if(-left>mDeleteWidth){            return -mDeleteWidth;        }    }    if(child == mDelete){ // 解决删除部分左右拖动的越界问题        if(left<mContentWidth - mDeleteWidth){            return mContentWidth - mDeleteWidth;        }else if(left > mContentWidth){            return mContentWidth;        }    }    return left;}

二、3、释放时位置的归正

/** * 相当于Touch的up的事件会回调onViewReleased这个方法 * * @param releasedChild * @param xvel  x方向的速率 * @param yvel  y方向的速率 */@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) {    //super.onViewReleased(releasedChild, xvel, yvel);    // 方法的参数里面没有left,那么我们就采用 getLeft()这个方法    int mConLeft = mContent.getLeft();    // 这里没必要分来两个孩子判断    if(-mConLeft>mDeleteWidth/2){        mContent.layout(-mDeleteWidth,0,mContentWidth-mDeleteWidth,mContentHeight);        mDelete.layout(mContentWidth-mDeleteWidth,0,mContentWidth,mDeleteHeight);    }else{        mContent.layout(0,0,mContentWidth,mContentHeight);        mDelete.layout(mContentWidth,0,mContentWidth+mDeleteWidth,mDeleteHeight);    }    super.onViewReleased(releasedChild, xvel, yvel);}

解决越界.gif

现在效果是实现了,但是松开手指的一瞬间位置归正得有点突兀,我们需要做一些过渡动画,才显得自然。

二、4、位置归正的过渡动画

ViewDragHelper里面给我们提供了一个方法,smoothSlideViewTo(View child, int finalLeft, int finalTo), smooth是平滑的意思,这个方法就是帮助我们做平滑滑动的。

smoothSlideViewTo(View child, int finalLeft, int finalTop)

public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {    mCapturedView = child;    mActivePointerId = INVALID_POINTER;    boolean continueSliding = forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);    if (!continueSliding && mDragState == STATE_IDLE && mCapturedView != null) {        // If we're in an IDLE state to begin with and aren't moving anywhere, we        // end up having a non-null capturedView with an IDLE dragState        mCapturedView = null;    }    return continueSliding;}

三个参数

child,那个孩子动
finalLeft + finalTop: 孩子运动到最后的左上角的点

通过孩子最后左上角的点就可以确定最后的应该到达的位置

上代码

    @Override    public void onViewReleased(View releasedChild, float xvel, float yvel) {        //super.onViewReleased(releasedChild, xvel, yvel);        // 方法的参数里面没有left,那么我们就采用 getLeft()这个方法        int mConLeft = mContent.getLeft();        // 这里没必要分来两个孩子判断        if(-mConLeft>mDeleteWidth/2){            //mContent.layout(-mDeleteWidth,0,mContentWidth-mDeleteWidth,mContentHeight);            //mDelete.layout(mContentWidth-mDeleteWidth,0,mContentWidth,mDeleteHeight);            //采用ViewDragHelper的 smoothSlideViewTo 方法让移动变得顺滑自然,不会太生硬            //smoothSlideViewTo只是模拟了数据,但是不会真正的动起来,动起来需要调用 invalidate            // 而 invalidate 通过调用draw()等方法之后最后还是还是会调用 computeScroll 这个方法            // 所以,使用 smoothSlideViewTo 做过渡动画需要结合  invalidate方法 和 computeScroll方法            // smoothSlideViewTo的动画执行时间没有暴露的参数可以设置,但是这个时间是google给我们经过大量计算给出合理时间        viewDragHelper.smoothSlideViewTo(mContent,-mDeleteWidth,0);            viewDragHelper.smoothSlideViewTo(mDelete,mContentWidth-mDeleteWidth,0);        }else{            //mContent.layout(0,0,mContentWidth,mContentHeight);            //mDelete.layout(mContentWidth, 0, mContentWidth + mDeleteWidth, mDeleteHeight);            viewDragHelper.smoothSlideViewTo(mContent, 0, 0);            viewDragHelper.smoothSlideViewTo(mDelete,mContentWidth, 0);        }        invalidate();        super.onViewReleased(releasedChild, xvel, yvel);    }}@Overridepublic void computeScroll() {    //super.computeScroll();    // 把捕获的View适当的时间移动,其实也可以理解为 smoothSlideViewTo 的模拟过程还没完成    if(viewDragHelper.continueSettling(true)){        invalidate();    }    // 其实这个动画过渡的过程大概在怎么走呢?    // 1、smoothSlideViewTo方法进行模拟数据,模拟后就就调用invalidate();    // 2、invalidate()最终调用computeScroll,computeScroll做一次细微动画,    //    computeScroll判断模拟数据是否彻底完成,还没完成会再次调用invalidate    // 3、递归调用,知道数据noni完成。}  }

item的自定义完成

到此为止,我们单个的侧滑删除的话的效果就实现了

三、在ListView里面嵌入我们的自定义控件

新建一个Activity,假设名为MyActivity,并且把这个Activity设置为启动页。

MyActivity

public class MyActivity extends Activity{    private ListView mLv;    private ArrayList<String> mData;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_my);        mLv = (ListView) findViewById(R.id.mLv);        mData=new ArrayList<>();        for(int i=0;i<200;i++){            mData.add("文本"+i);        }        mLv.setAdapter(new MyAdapter());    }    class MyAdapter extends BaseAdapter{        @Override        public int getCount() {            return mData.size();        }        @Override        public Object getItem(int position) {            return mData.get(position);        }        @Override        public long getItemId(int position) {            return position;        }        @Override        public View getView(int position, View convertView, ViewGroup parent) {            ViewHolder viewHolder;            if(convertView == null){                viewHolder = new ViewHolder();                convertView = View.inflate(MyActivity.this,R.layout.item,null);                viewHolder.mSlideDelete = (SlideDelete) convertView.findViewById(R.id.mSlideDelete);                viewHolder.mLlContent = (LinearLayout) convertView.findViewById(R.id.mLlContent);                viewHolder.mLlDelete = (LinearLayout) convertView.findViewById(R.id.mLlDelete);                convertView.setTag(viewHolder);            }else{                viewHolder = (ViewHolder) convertView.getTag();            }            viewHolder.mSlideDelete.setOnSlideDeleteListener(new SlideDelete.OnSlideDeleteListener() {                @Override                public void onOpen(SlideDelete slideDelete) {                }                @Override                public void onClose(SlideDelete slideDelete) {                }            });            return convertView;        }    }    class ViewHolder{        SlideDelete mSlideDelete;        LinearLayout mLlContent;        LinearLayout mLlDelete;    }}

关联的activity_my

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.amqr.slidedelete.MainActivity">    <ListView        android:id="@+id/mLv"        android:layout_width="match_parent"        android:layout_height="match_parent"></ListView></RelativeLayout>

adapter里面的item

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <com.amqr.slidedelete.view.SlideDelete        android:id="@+id/mSlideDelete"        android:layout_width="match_parent"        android:layout_height="60dp">        <!--文本部分-->        <include layout="@layout/slide_content"/>        <!--删除部分-->        <include layout="@layout/slide_delete"/>    </com.amqr.slidedelete.view.SlideDelete></LinearLayout>

当前效果


当前效果.gif

简单的效果是实现了,但是这样不好

实际使用中,我们通常是只能一个item处于打开状态,
即A打开,B必须处于关闭,不能A和B都处于打开的状态

怎么办呢。

只让一个item处于打开状态

整理思路:
1、我们给SlideDelete添加接口和回调,接口里面有onOpen(SlideDelete slideDelete)和onClose(SlideDelete slideDelete)两个方法

    // SlideDlete的接口    public interface OnSlideDeleteListener {        void onOpen(SlideDelete slideDelete);        void onClose(SlideDelete slideDelete);    }

2、暴露一个setOnSlideDeleteListener方法给外部调用,把SlideDelete的onViewReleased里面的打开和关闭抽取暴露出来,通过参数boolean决定是否显示delete部分。

然后在onViewReleased里面,真正调用接口的onOpen和onClose方法

private OnSlideDeleteListener onSlideDeleteListener;    public void setOnSlideDeleteListener(OnSlideDeleteListener onSlideDeleteListener){        this.onSlideDeleteListener = onSlideDeleteListener;    }
/**     * 相当于Touch的up的事件会回调onViewReleased这个方法     *     * @param releasedChild     * @param xvel  x方向的速率     * @param yvel  y方向的速率     */    @Override    public void onViewReleased(View releasedChild, float xvel, float yvel) {        //super.onViewReleased(releasedChild, xvel, yvel);        // 方法的参数里面没有left,那么我们就采用 getLeft()这个方法        int mConLeft = mContent.getLeft();        // 这里没必要分来两个孩子判断        if(-mConLeft>mDeleteWidth/2){  // mDelete展示起来            isShowDelete(true);            if(onSlideDeleteListener != null){                onSlideDeleteListener.onOpen(SlideDelete.this); // 调用接口打开的方法            }        }else{    // mDetele隐藏起来            isShowDelete(false);            if(onSlideDeleteListener != null){                onSlideDeleteListener.onClose(SlideDelete.this); // 调用接口的关闭的方法            }        }        super.onViewReleased(releasedChild, xvel, yvel);    }

3、在MyActivity的Adapter里面调用SlideDelete暴露出来的实现接口的方法。
弄一个集合记录起来已经打开的item,每次getView的执行都先关闭已经打开的item

@Overridepublic View getView(int position, View convertView, ViewGroup parent) {    ViewHolder viewHolder;    if(convertView == null){        viewHolder = new ViewHolder();        convertView = View.inflate(MyActivity.this,R.layout.item,null);        viewHolder.mSlideDelete = (SlideDelete) convertView.findViewById(R.id.mSlideDelete);        viewHolder.mTvContent = (TextView) convertView.findViewById(R.id.mTvContent);        viewHolder.mLlDelete = (LinearLayout) convertView.findViewById(R.id.mLlDelete);        convertView.setTag(viewHolder);    }else{        viewHolder = (ViewHolder) convertView.getTag();    }    viewHolder.mTvContent.setText(mData.get(position));    viewHolder.mSlideDelete.setOnSlideDeleteListener(new SlideDelete.OnSlideDeleteListener() {        @Override        public void onOpen(SlideDelete slideDelete) {            closeOtherItem();            slideDeleteArrayList.add(slideDelete);            Log.d("Slide", "slideDeleteArrayList当前数量:" + slideDeleteArrayList.size());        }        @Override        public void onClose(SlideDelete slideDelete) {            slideDeleteArrayList.remove(slideDelete);            Log.d("Slide", "slideDeleteArrayList当前数量:" + slideDeleteArrayList.size());        }    });    return convertView;}

关闭所有已经打开的item的方法

private void closeOtherItem(){    // 采用Iterator的原因是for是线程不安全的,迭代器是线程安全的    ListIterator<SlideDelete> slideDeleteListIterator = slideDeleteArrayList.listIterator();    while(slideDeleteListIterator.hasNext()){        SlideDelete slideDelete = slideDeleteListIterator.next();        slideDelete.isShowDelete(false);    }    slideDeleteArrayList.clear();}

至此完成。

附上当前完整的SlideDelete的代码

public class SlideDelete extends ViewGroup{    private View mContent; // 内容部分    private View mDelete;  // 删除部分    private ViewDragHelper viewDragHelper;    private int mContentWidth;    private int mContentHeight;    private int mDeleteWidth;    private int mDeleteHeight;    public SlideDelete(Context context) {        super(context);    }    public SlideDelete(Context context, AttributeSet attrs) {        super(context, attrs);    }    public SlideDelete(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    private OnSlideDeleteListener onSlideDeleteListener;    public void setOnSlideDeleteListener(OnSlideDeleteListener onSlideDeleteListener){        this.onSlideDeleteListener = onSlideDeleteListener;    }    @Override    protected void onFinishInflate() {        super.onFinishInflate();        mContent = getChildAt(0);        mDelete = getChildAt(1);        //public static ViewDragHelper create(ViewGroup forParent, Callback cb)        viewDragHelper = ViewDragHelper.create(this,new MyDrawHelper());    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);        // 这跟mContent的父亲的大小有关,父亲是宽填充父窗体,高度是和孩子一样是60dp        mContent.measure(widthMeasureSpec,heightMeasureSpec); // 测量内容部分的大小        LayoutParams layoutParams = mDelete.getLayoutParams();        int deleteWidth = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);        int deleteHeight = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);        // 这个参数就需要指定为精确大小        mDelete.measure(deleteWidth, deleteHeight); // 测量删除部分的大小        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        mContentWidth = mContent.getMeasuredWidth();        mContentHeight = mContent.getMeasuredHeight();        mContent.layout(0, 0, mContentWidth, mContentHeight); // 摆放内容部分的位置        mDeleteWidth = mDelete.getMeasuredWidth();        mDeleteHeight = mDelete.getMeasuredHeight();        mDelete.layout(mContentWidth, 0,                mContentWidth + mDeleteWidth, mContentHeight); // 摆放删除部分的位置    }    class MyDrawHelper extends ViewDragHelper.Callback {        /**         * Touch的down事件会回调这个方法 tryCaptureView         *         * @Child:指定要动的孩子  (哪个孩子需要动起来)         * @pointerId: 点的标记         * @return : ViewDragHelper是否继续分析处理 child的相关touch事件         */        @Override        public boolean tryCaptureView(View child, int pointerId) {            System.out.println("调用tryCaptureView");            System.out.println("contentView : " + (mContent == child));            return mContent == child || mDelete == child;        }        // Touch的move事件会回调这面这几个方法        // clampViewPositionHorizontal        // clampViewPositionVertical        // onViewPositionChanged        /**         *         * 捕获了水平方向移动的位移数据         * @param child 移动的孩子View         * @param left 父容器的左上角到孩子View的距离         * @param dx 增量值,其实就是移动的孩子View的左上角距离控件(父亲)的距离,包含正负         * @return 如何动         *         * 调用完此方法,在android2.3以上就会动起来了,2.3以及以下是海动不了的         * 2.3不兼容怎么办?没事,我们复写onViewPositionChanged就是为了解决这个问题的         */        @Override        public int clampViewPositionHorizontal(View child, int left, int dx) {            //Log.d("Slide", "增量值:   " + left);            if(child == mContent){ // 解决内容部分左右拖动的越界问题                if(left>0){                    return 0;                }else if(-left>mDeleteWidth){                    return -mDeleteWidth;                }            }            if(child == mDelete){ // 解决删除部分左右拖动的越界问题                if(left<mContentWidth - mDeleteWidth){                    return mContentWidth - mDeleteWidth;                }else if(left > mContentWidth){                    return mContentWidth;                }            }            return left;        }        @Override        public int clampViewPositionVertical(View child, int top, int dy) {            return super.clampViewPositionVertical(child, top, dy);        }        /**         * 当View的位置改变时的回调  这个方法的价值是结合clampViewPositionHorizontal或者clampViewPositionVertical         * @param changedView  哪个View的位置改变了         * @param left  changedView的left         * @param top  changedView的top         * @param dx x方向的上的增量值         * @param dy y方向上的增量值         */        @Override        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {            //super.onViewPositionChanged(changedView, left, top, dx, dy);            invalidate();            if(changedView == mContent){ // 如果移动的是mContent                //我们移动mContent的实惠要相应的联动改变mDelete的位置                // 怎么改变mDelete的位置,当然是mDelete的layput方法啦                int tempDeleteLeft = mContentWidth+left;                int tempDeleteRight = mContentWidth+left + mDeleteWidth;                mDelete.layout(tempDeleteLeft,0,tempDeleteRight,mDeleteHeight);            }else{ // touch的是mDelete                int tempContentLeft = left - mContentWidth;                int tempContentRight = left;                mContent.layout(tempContentLeft,0,tempContentRight,mContentHeight);            }        }        /**         * 相当于Touch的up的事件会回调onViewReleased这个方法         *         * @param releasedChild         * @param xvel  x方向的速率         * @param yvel  y方向的速率         */        @Override        public void onViewReleased(View releasedChild, float xvel, float yvel) {            //super.onViewReleased(releasedChild, xvel, yvel);            // 方法的参数里面没有left,那么我们就采用 getLeft()这个方法            int mConLeft = mContent.getLeft();            // 这里没必要分来两个孩子判断            if(-mConLeft>mDeleteWidth/2){  // mDelete展示起来                isShowDelete(true);                if(onSlideDeleteListener != null){                    onSlideDeleteListener.onOpen(SlideDelete.this); // 调用接口打开的方法                }            }else{    // mDetele隐藏起来                isShowDelete(false);                if(onSlideDeleteListener != null){                    onSlideDeleteListener.onClose(SlideDelete.this); // 调用接口的关闭的方法                }            }            super.onViewReleased(releasedChild, xvel, yvel);        }    }    /**     * 是否展示delete部分     * @param isShowDelete     */    public void isShowDelete(boolean isShowDelete){        if(isShowDelete){            //mContent.layout(-mDeleteWidth,0,mContentWidth-mDeleteWidth,mContentHeight);            //mDelete.layout(mContentWidth-mDeleteWidth,0,mContentWidth,mDeleteHeight);            //采用ViewDragHelper的 smoothSlideViewTo 方法让移动变得顺滑自然,不会太生硬            //smoothSlideViewTo只是模拟了数据,但是不会真正的动起来,动起来需要调用 invalidate            // 而 invalidate 通过调用draw()等方法之后最后还是还是会调用 computeScroll 这个方法            // 所以,使用 smoothSlideViewTo 做过渡动画需要结合  invalidate方法 和 computeScroll方法            // smoothSlideViewTo的动画执行时间没有暴露的参数可以设置,但是这个时间是google给我们经过大量计算给出合理时间            viewDragHelper.smoothSlideViewTo(mContent,-mDeleteWidth,0);            viewDragHelper.smoothSlideViewTo(mDelete,mContentWidth-mDeleteWidth,0);        }else{            //mContent.layout(0,0,mContentWidth,mContentHeight);            //mDelete.layout(mContentWidth, 0, mContentWidth + mDeleteWidth, mDeleteHeight);            viewDragHelper.smoothSlideViewTo(mContent, 0, 0);            viewDragHelper.smoothSlideViewTo(mDelete, mContentWidth, 0);        }        invalidate();    }    @Override    public void computeScroll() {        //super.computeScroll();        // 把捕获的View适当的时间移动,其实也可以理解为 smoothSlideViewTo 的模拟过程还没完成        if(viewDragHelper.continueSettling(true)){            invalidate();        }        // 其实这个动画过渡的过程大概在怎么走呢?        // 1、smoothSlideViewTo方法进行模拟数据,模拟后就就调用invalidate();        // 2、invalidate()最终调用computeScroll,computeScroll做一次细微动画,        //    computeScroll判断模拟数据是否彻底完成,还没完成会再次调用invalidate        // 3、递归调用,知道数据noni完成。    }    @Override    public boolean onTouchEvent(MotionEvent event) {        //return super.onTouchEvent(event);        /**Process a touch event received by the parent view. This method will dispatch callback events        as needed before returning. The parent view's onTouchEvent implementation should call this. */        viewDragHelper.processTouchEvent(event); // 使用ViewDragHelper必须复写onTouchEvent并调用这个方法        return true; //消费这个touch    }    // SlideDlete的接口    public interface OnSlideDeleteListener {        void onOpen(SlideDelete slideDelete);        void onClose(SlideDelete slideDelete);    }}

.
.

附上当前完整的MyActivity代码

public class MyActivity extends Activity{    private ListView mLv;    private ArrayList<String> mData;    // 继续有多少个条目的delete被展示出来的集合    private List<SlideDelete> slideDeleteArrayList = new ArrayList<>();    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_my);        mLv = (ListView) findViewById(R.id.mLv);        mData=new ArrayList<>();        for(int i=0;i<200;i++){            mData.add("文本"+i);        }        mLv.setAdapter(new MyAdapter());    }    class MyAdapter extends BaseAdapter{        @Override        public int getCount() {            if(mData!=null){                return mData.size();            }           return 0;        }        @Override        public Object getItem(int position) {            if(mData!=null){                return mData.get(position);            }            return null;        }        @Override        public long getItemId(int position) {            return position;        }        @Override        public View getView(int position, View convertView, ViewGroup parent) {            ViewHolder viewHolder;            if(convertView == null){                viewHolder = new ViewHolder();                convertView = View.inflate(MyActivity.this,R.layout.item,null);                viewHolder.mSlideDelete = (SlideDelete) convertView.findViewById(R.id.mSlideDelete);                viewHolder.mTvContent = (TextView) convertView.findViewById(R.id.mTvContent);                viewHolder.mLlDelete = (LinearLayout) convertView.findViewById(R.id.mLlDelete);                convertView.setTag(viewHolder);            }else{                viewHolder = (ViewHolder) convertView.getTag();            }            viewHolder.mTvContent.setText(mData.get(position));            viewHolder.mSlideDelete.setOnSlideDeleteListener(new SlideDelete.OnSlideDeleteListener() {                @Override                public void onOpen(SlideDelete slideDelete) {                    closeOtherItem();                    slideDeleteArrayList.add(slideDelete);                    Log.d("Slide", "slideDeleteArrayList当前数量:" + slideDeleteArrayList.size());                }                @Override                public void onClose(SlideDelete slideDelete) {                    slideDeleteArrayList.remove(slideDelete);                    Log.d("Slide", "slideDeleteArrayList当前数量:" + slideDeleteArrayList.size());                }            });            return convertView;        }    }    class ViewHolder{        SlideDelete mSlideDelete;        TextView mTvContent;        LinearLayout mLlDelete;    }    private void closeOtherItem(){        // 采用Iterator的原因是for是线程不安全的,迭代器是线程安全的        ListIterator<SlideDelete> slideDeleteListIterator = slideDeleteArrayList.listIterator();        while(slideDeleteListIterator.hasNext()){            SlideDelete slideDelete = slideDeleteListIterator.next();            slideDelete.isShowDelete(false);        }        slideDeleteArrayList.clear();    }}

c8103562-42b9-4137-a1ae-b910297c66a9.gif

4、滑动屏幕就关闭打开的条目

完善一下,如果当前有item的删除部分是展开的,当这个情况下我们去滑动竖直方向滑动屏幕,那么删除部分就会被隐藏回去。

其实就是做一下ListView的滑动监听而已

mLv.setOnScrollListener(new AbsListView.OnScrollListener() {    @Override    public void onScrollStateChanged(AbsListView view, int scrollState) {        if(scrollState == SCROLL_STATE_FLING || scrollState == SCROLL_STATE_TOUCH_SCROLL){            closeOtherItem();        }    }    @Override    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {    }});

效果图.gif

5、删除按钮按下删除item

viewHolder.mLlDelete.setOnClickListener(new View.OnClickListener() {    @Override    public void onClick(View v) {        mData.remove(position);        notifyDataSetChanged();    }});

最终效果




文/双门M(简书作者)
原文链接:http://www.jianshu.com/p/5cb27a2ce03d

0 0
原创粉丝点击