基于recyclerview的itemview左滑删除置顶功能(一)

前言:

每天把玩QQ也想作一个类似QQ的功能出来,于是二话不说就动起来。但是又鉴于自己对安卓view的不了解,要做到这种自定义view还是有困难的,所以鉴前人之经验,参考了一篇文章。在理解分析了里面的代码后,会对其不太清楚的地方进行解释和改进。

效果:

如何拉出隐藏的view?

布局

首先我们先把布局弄好,一个recyclerview,自然不用说,然后是每个itemview的布局:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="20dp">
    <TextView
        android:id="@+id/tv_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>
<LinearLayout
    android:id="@+id/tvdeleteLayout"
    android:orientation="horizontal"
    android:layout_width="80dp"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv_delete"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@android:color/holo_red_light"
        android:gravity="center"
        android:text="删除"
        android:textColor="@android:color/white" />
</LinearLayout>
<LinearLayout
    android:orientation="horizontal"
    android:id="@+id/tvaddLayout"
    android:layout_width="80dp"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv_add"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="增加"
        android:background="@android:color/holo_orange_light"
        android:gravity="center"
        android:textColor="@android:color/white"
        />
</LinearLayout>
</LinearLayout>

后面滑出来的你要几个就写几个LinerLayout含textview的。


接下来是逻辑代码,这个类叫ItemSlideHelper2并且实现RecyclerView.OnItemTouchListener接口。实现这个接口就要覆写onInterceptTouchEvent以及onTouchEvent,这个覆写是关键。因为我们要实现这个滑动功能就要实现对点击事件的拦截和消费(后面会讲到) ps:这是个人根据前人文章改进的。


原理及接口

首先我们要理解,我们用手指滑动的是recyclerview里的itemview,而不是我们自己定义的view。因此,我们要把隐藏起来的自定义view通过滑动itemview,以达到显示的效果。因此我们要从recyclerview中的适配器(Adapter)获得itemview对象,以及我们需要滑动的距离(自定义view的总长度)在ItemSlideHelper2类中定义接口

 interface Callback{
        View getItemView(float x, float y); //获得itemview
        int getMyViewTotalWidth(View itemView); //获得自定义view总长度
        }

在适配器中实现接口并返回需要的数据:

@Override
    public View getItemView(float x, float y) {
        return mRecyclerView.findChildViewUnder(x , y);
    }
@Override
public int getMyViewTotalWidth(View itemview) {
    RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(itemview);
    ViewGroup viewGroup = (ViewGroup) vh.itemView;
    View mview =  viewGroup.getChildAt(1);
    View mview2 = viewGroup.getChildAt(2);
    mcustomViews = new CustomViews();
    mcustomViews.addView(mview);
   mcustomViews.addView(mview2);
    return mcustomViews.getWidth();
}

ps:点击的x,y可在ItemSlideHelper2实现的方法onInterceptTouchEvent中传入。可先判断事件类型,为down时,传入点击坐标。而获取长度的可使用viewGroup的getChildAt();来获取自定义view。 注:在调用自己定义的接口时,先调用getItemView初始化itemSlideHelper2里的全局变量itemView,然后再调用getMyViewTotalWidth(View itemview),不然可能会空指针报错。因此最好是集中把两个接口的调用写一起:

 private void InitclickItem(int x, int y) {
            itemView = mCallback.getItemView(x , y);
            mviewWidth = mCallback.getMyViewTotalWidth(itemView);
    }

如何滑动

在此之前,我们先看看覆写的两个方法的用途: onInterceptTouchEvent:返回ture则拦截点击等事件,并交给onTouchEvent处理。返回false不交给onTouchEvent处理,继续点击事件。 什么意思呢?我们都知道recycler的Itemview我们可以加监听,就像手机QQ的每一聊天信息,点进去就可以聊天。那这时候就产生冲突了,如何要在能拖动Itemview的情况下而不影响点击呢?又或者可以这么说,不让ItemVIew的点击监听覆盖(看上去想覆盖,实际是处理了,导致无法传递时间)了我们的拖动事件呢?答案就是使用onInterceptTouchEvent和onTouchEvent对事件进行处理过滤。 我们拖动ItemView希望不被ItemView的点击时间拦截,我们可以在onInterceptTouchEvent中恒定返回true,这样事件就可以把事件传递到OnTouchEvent中进行处理,从而滑动ItemView。现在我们先恒定返回true。(当然这样ItemView的点击时间就没用了。这个后面再改) 首先我们先到调用自定义的两个接口,初始化我们需要的itemview(View对象)和 自定义view的总长度(int) 这里把两个接口的调用统一放到了上文中的 InitclickItem函数中,这样可以防止空指针错误。 初始化在onInterceptTouchEvent中的down事件进行。

int x = (int) e.getX(); //获取通用坐标,无论是点击,还是拖动
        int y = (int) e.getY();
int action = e.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN:
                InitclickItem(x , y); //初始化调用mcallback后的值
                break;
        }

当然记得要返回true,不然就没效果了。接下来在OnTouchEvent对ItemView进行拖动。

 @Override
        public void onTouchEvent(RecyclerView rv, MotionEvent e) {
            int x = (int) e.getX();
            int y = (int) e.getY();
            switch (e.getAction()){
                case MotionEvent.ACTION_DOWN:
                    break;
                case MotionEvent.ACTION_MOVE:
                    int deltaX = firstX - x;
                    ScrollwithMouse(deltaX);//随着水平移动距离而滑动
                    break;
            }
        }

这里的firstX是指第一次按下的坐标,可以在这里定义,也可以定义个全局变量在onInterceptTouchEvent事件中进行赋值。我是在onInterceptTouchEven赋值的,因为在后文中也用用到。 接下来是滑动功能函数:

 //根据鼠标位移滑动view;同时也改变view的scrollX
    private void ScrollwithMouse(int deltaX) {
        if (itemView == null){ //没有初始化跳出
            return;
        }
        int scrollX = itemView.getScrollX(); //获得itemView水平方向的偏移量
        int scrollY= itemView.getScrollY();//获得itemView竖直方向的偏移量
        if (scrollX + deltaX < 0){
            itemView.scrollTo(0 , scrollY);//防止用户右滑而不是左滑。
            return;
        }
        scrollX += deltaX;
        if (Math.abs(scrollX) < mviewWidth){
            itemView.scrollTo(scrollX,scrollY);//滑到哪停到哪
        }else {
            itemView.scrollTo(mviewWidth , scrollY);//到了尽头,不能滑了。
        }
    }

总结

原理:滑动itemView,事先定义好的view因为过长而看上去被隐藏。 使用onInterceptTouchEvent和OnTouchEvent对事件(滑动)拦截消费。 通过View的函数scrollTo(偏移量X,偏移量y)滑动Itemview PS:之前参考的文章找不到了。。希望大家如果看到和我类似的文章留个链接。我贴上来。(无论是命名还是做法都是差不多一样的)