从零开始学习RecyclerView(二)

来源:互联网 发布:知乎瓷砖地板和木地板 编辑:程序博客网 时间:2024/05/16 08:50

在另一篇文章”从零开始学习RecycylerView(一)中,已经记录了RecycylerView的基本使用,如何添加数据,如何使用布局管理器展现不同的形式,一般在展示数据后,用户不仅仅满足于看到这些数据就够了,所以通常都会和用户进行交互,点击事件就是交互最基本的其中一种。

上一篇文章也说了 RecycylerView 并没有处理点击事件的监听器,所以如果要监听 RecycylerView 的点击事件,我们需要自己写监听器。下面就简单介绍几种实现方法。

PS:这篇文章的代码是建立在从零开始学习RecycylerView(一)”中代码的基础之上。

方法一:利用View.onClickListener onLongClickListener

利用了 java 回调机制,这里我们依赖于子Item ViewonClickListeneronLongClickListener

首先对 TestAdapter 代码做出如下修改:

新建两个内部接口:

public interface OnItemClickListener {    void onItemClick(View view, int position);}public interface OnItemLongClickListener {    void onItemLongClick(View view, int position);}


新建两个私有变量用于保存用户设置的监听器及其 set 方法:

    private OnItemClickListener mOnItemClickListener;    private OnItemLongClickListener mOnItemLongClickListener;    public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {        this.mOnItemClickListener = mOnItemClickListener;    }    public void setOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {        this.mOnItemLongClickListener = mOnItemLongClickListener;    }


onBindViewHolder 方法内,实现回调:

    @Override    public void onBindViewHolder(final MyViewHolder holder, int position) {        holder.tvTest.setText(stringList.get(position));//        ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();//        lp.height = (int) (100 + Math.random() * 300);//        holder.itemView.setLayoutParams(lp);        //判断是否设置了监听器        if(mOnItemClickListener != null){            //为ItemView设置监听器            holder.itemView.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    int position = holder.getLayoutPosition(); // 1                    mOnItemClickListener.onItemClick(holder.itemView,position); // 2                }            });        }        if(mOnItemLongClickListener != null){            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {                @Override                public boolean onLongClick(View v) {                    int position = holder.getLayoutPosition();                    mOnItemLongClickListener.onItemLongClick(holder.itemView,position);                    //返回true 表示消耗了事件 事件不会继续传递                    return true;                }            });        }    }


可以看到,这里实际上用到了子 Item View onClickListener onLongClickListener这两个监听器,如果当前子item view被点击了,会触发点击事件进行回调,然后在内部接口处获取当前点击位置的position值,接着在我们保存的用户设置的监听器处进行再次回调,而这一次的回调是我们自己手动添加的,需要实现上面所述的接口。
修改完 TestAdapter后,我们接着在 MainActivity 中设置监听器,采用匿名内部类的形式实现了 onItemClickListener  onItemLongClickListener 接口,这种写法与一般的设置监听器的流程相同:

TestAdapter mTestAdapter = new TestAdapter(getList());mTestAdapter.setOnItemClickListener(new TestAdapter.OnItemClickListener() {    @Override    public void onItemClick(View view, int position) {        Toast.makeText(MainActivity.this, "click " + getList().get(position), Toast.LENGTH_SHORT).show();    }});mTestAdapter.setOnItemLongClickListener(new TestAdapter.OnItemLongClickListener() {    @Override    public void onItemLongClick(View view, int position) {        Toast.makeText(MainActivity.this,"long click "+getList().get(position),Toast.LENGTH_SHORT).show();    }});rvTest.setAdapter(mTestAdapter);

如果不出现什么失误的话,应该会出现下面的结果:




方法二:利用RecyclerView.OnItemTouchListener

官方虽然没有提供现成的监听器,但是提供了一个内部接口:OnItemTouchListener,我们看看官方文档对它的描述:An OnItemTouchListener allows the application to intercept touch events in progress at the view hierarchy level of the RecyclerView before those touch events are considered for RecyclerView's own scrolling behavior。大概意思是说该接口允许我们对RecyclerView的触摸事件进行拦截,我们看看它的几个接口方法:

public void onTouchEvent(RecyclerView rv, MotionEvent e);public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);


熟悉 View 事件分发机制的同学看到这些方法的名字一定觉得很熟悉了吧,所以这个接口的实质是利用了 View 的事件分发与拦截机制。大体思路是:我们可以在 onInterceptTouchEvent 获取当前触摸位置对应的子 item view ,根据点击状态决定是否要把事件拦截,在拦截的时候同时添加一个回调方法,这样我们自己实现的监听器接口就能在这里得到回调。具体的请看如下代码:
新建一个 RecyclerViewClickListener 类:

public class RecyclerViewClickListener implements RecyclerView.OnItemTouchListener {    private int mLastDownX,mLastDownY;    //该值记录了最小滑动距离    private int touchSlop ;    private OnItemClickListener mListener;    //是否是单击事件    private boolean isSingleTapUp = false;    //是否是长按事件    private boolean isLongPressUp = false;    private boolean isMove = false;    private long mDownTime;    //内部接口,定义点击方法以及长按方法    public interface OnItemClickListener {        void onItemClick(View view, int position);        void onItemLongClick(View view, int position);    }    public RecyclerViewClickListener(Context context, OnItemClickListener listener){        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();        mListener = listener;    }    @Override    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {        int x = (int) e.getX();        int y = (int) e.getY();        switch (e.getAction()){            /**             *  如果是ACTION_DOWN事件,那么记录当前按下的位置,             *  记录当前的系统时间。             */            case MotionEvent.ACTION_DOWN:                mLastDownX = x;                mLastDownY = y;                mDownTime = System.currentTimeMillis();                isMove = false;                break;            /**             *  如果是ACTION_MOVE事件,此时根据TouchSlop判断用户在按下的时候是否滑动了,             *  如果滑动了,那么接下来将不处理点击事件             */            case MotionEvent.ACTION_MOVE:                if(Math.abs(x - mLastDownX)>touchSlop || Math.abs(y - mLastDownY)>touchSlop){                    isMove = true;                }                break;            /**             *  如果是ACTION_UP事件,那么根据isMove标志位来判断是否需要处理点击事件;             *  根据系统时间的差值来判断是哪种事件,如果按下事件超过1s,则认为是长按事件,             *  否则是单击事件。             */            case MotionEvent.ACTION_UP:                if(isMove){                    break;                }                if(System.currentTimeMillis()-mDownTime > 1000){                    isLongPressUp = true;                }else {                    isSingleTapUp = true;                }                break;        }        if(isSingleTapUp ){            //根据触摸坐标来获取childView            View childView = rv.findChildViewUnder(e.getX(),e.getY());            isSingleTapUp = false;            if(childView != null){                //回调mListener#onItemClick方法                mListener.onItemClick(childView,rv.getChildLayoutPosition(childView));                return true;            }            return false;        }        if (isLongPressUp ){            View childView = rv.findChildViewUnder(e.getX(),e.getY());            isLongPressUp = false;            if(childView != null){                mListener.onItemLongClick(childView, rv.getChildLayoutPosition(childView));                return true;            }            return false;        }        return false;    }    @Override    public void onTouchEvent(RecyclerView rv, MotionEvent e) {    }    @Override    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {    }}

接着我们在 MainActivity将刚才设置监听器的代码改一下:

rvTest.setLayoutManager(mLinearLayoutManager);TestAdapter mTestAdapter = new TestAdapter(getList());rvTest.addOnItemTouchListener(new RecyclerViewClickListener(this, new RecyclerViewClickListener.OnItemClickListener() {    @Override    public void onItemClick(View view, int position) {        Toast.makeText(MainActivity.this, "Click " + getList().get(position), Toast.LENGTH_SHORT).show();    }    @Override    public void onItemLongClick(View view, int position) {        Toast.makeText(MainActivity.this, "Long Click " + getList().get(position), Toast.LENGTH_SHORT).show();    }}));rvTest.setAdapter(mTestAdapter);

重新运行程序,结果同上。

那么方法一和方法二有何区别呢?
首先,方法一我们是直接在MyAdapter数据适配器中,为itemview设置了内置监听器,再通过这个监听器实现我们的回调方法,相当于回调了两次,同时这个方法与MyAdapter的耦合度比较高,也违反了单一职责原则,当然其简易性也是突出的优点。而方法二,我们利用了onTouchListener接口对事件进行了拦截,在拦截中处理我们的点击事件,实现了与适配器的解耦,但是复杂程度会比方法一大。总地来说,如果RecyclerView需要处理的点击事件逻辑很简单,那么可以使用方法一;如果需要处理比较复杂的点击事件,比如说,双击、长按等点击事件,则需要使用方法二去实现各种复杂的逻辑。

对方法二的优化:

在实现方法二的RecyclerViewClickListener的时候,在内部对事件的实现了单击、长按的判断,但是这个长按事件不是标准的,只有松开手指的时候才会触发长按事件,这也算是一点瑕疵,同时如果要增加别的事件,比如说双击事件,则需要增加相应的逻辑,如果需要判断的事件种类变多则会给我们的代码编写带来困难,那么有没有更加简便的方法呢?其实安卓SDK为我们提供了一个手势检测类:GestureDetector来处理各种不同的手势,那么我们完全可以利用GestureDetector来对方法二进行改进。


RecyclerViewClickListener修改一下:

public class RecyclerViewClickListener implements RecyclerView.OnItemTouchListener {    private GestureDetector mGestureDetector;    private OnItemClickListener mListener;    //内部接口,定义点击方法以及长按方法    public interface OnItemClickListener {        void onItemClick(View view, int position);        void onItemLongClick(View view, int position);    }    public RecyclerViewClickListener(Context context, final RecyclerView recyclerView,OnItemClickListener listener){        mListener = listener;        mGestureDetector = new GestureDetector(context,                new GestureDetector.SimpleOnGestureListener(){ //这里选择SimpleOnGestureListener实现类,可以根据需要选择重写的方法                    //单击事件                    @Override                    public boolean onSingleTapUp(MotionEvent e) {                        View childView = recyclerView.findChildViewUnder(e.getX(),e.getY());                        if(childView != null && mListener != null){                            mListener.onItemClick(childView,recyclerView.getChildLayoutPosition(childView));                            return true;                        }                        return false;                    }                    //长按事件                    @Override                    public void onLongPress(MotionEvent e) {                        View childView = recyclerView.findChildViewUnder(e.getX(),e.getY());                        if(childView != null && mListener != null){                            mListener.onItemLongClick(childView,recyclerView.getChildLayoutPosition(childView));                        }                    }                });    }    @Override    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {        //把事件交给GestureDetector处理        if(mGestureDetector.onTouchEvent(e)){            return true;        }else            return false;    }    @Override    public void onTouchEvent(RecyclerView rv, MotionEvent e) {    }    @Override    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {    }}

MainActivity中设置监听器的代码也修改一下:

TestAdapter mTestAdapter = new TestAdapter(getList());rvTest.addOnItemTouchListener(new RecyclerViewClickListener(this, rvTest,        new RecyclerViewClickListener.OnItemClickListener() {            @Override            public void onItemClick(View view, int position) {                Toast.makeText(MainActivity.this, "Click " + getList().get(position), Toast.LENGTH_SHORT).show();            }            @Override            public void onItemLongClick(View view, int position) {                Toast.makeText(MainActivity.this, "Long Click " + getList().get(position), Toast.LENGTH_SHORT).show();            }        }));rvTest.setAdapter(mTestAdapter);

重新运行程序,结果仍然同上。

至此,三种RecyclerViewitem的点击事件的方式已记录完毕,下一篇文章再记录如何为RecyclerViewitem设置分割线。



1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 华为p9手机gps信号弱怎么办 小米手机导航gps信号弱怎么办 安卓手机gps信号弱怎么办 苹果6导航gps信号弱怎么办 苹果6plus反应慢怎么办 手机文件打开是乱码怎么办 手机wps文件打开是乱码怎么办 腾讯视频vip账号被盗怎么办 附单据数错了 怎么办 橡胶的回弹性差怎么办 自己喷漆喷坏了怎么办 透明塑料磨花了怎么办 包包金属刮花了怎么办 鞋子刮了黑印子怎么办 黑色鞋跟磨白了怎么办 脚穿鞋子磨起泡怎么办 脚被鞋子磨红了怎么办 脚被鞋子磨黑了怎么办 白鞋皮鞋磨了皮怎么办 小脚趾磨肿了怎么办 穿鞋小拇指磨脚怎么办 高铁东西忘了怎么办 人故意去撞车死了怎么办? 新货车上户超重怎么办 车险出保单车号填错怎么办 货车拦板变形了怎么办 行车监控看不清楚车号怎么办? 1.5米的鱼缸要怎么办 被锤子砸到手了怎么办 家里地下污水管道堵塞怎么办 家里pvc灯罩变黄怎么办 欧普吸顶灯灯罩坏了怎么办 硬盘用久了变慢怎么办 地税申报工资人员弄错怎么办 买保险保单丢了怎么办 买保险的银行卡丢了怎么办 没学过JAVA入职怎么办 磨砂皮擦了鞋油怎么办 磨破皮伤口有沙子怎么办 工行信用卡被风险锁定了怎么办 超重被超限站查住以后怎么办