基于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:之前参考的文章找不到了。。希望大家如果看到和我类似的文章留个链接。我贴上来。(无论是命名还是做法都是差不多一样的)