[入门向]关于RecyclerView的事件拦截机制

代码 代码 1132 人阅读 | 0 人回复

<
声明:本文章已独家受权郭霖公家号
目次
1、发明成绩
2、缘故原由阐发
1、变乱分收的机造
2、缘故原由推测
3、RecyclerView变乱阻拦机造
switch (action)部分
返回值
ACTION_DOWN
ACTION_MOVE
ACTION_UP
小结
switch (action) 之前
OnItemTouchListener接心
阻拦历程
小结
4、处理成绩
计划一
计划两
5、总结

1、发明成绩

近来正在操纵RecyclerView做开辟的时分,碰到了一面成绩:
给RecyclerView的子项增加变乱监听的时分,发明ACITON_DOWN能获得处置,ACITON_UP战ACTION_MOVE却得没有四处理。

2、缘故原由阐发

正在刚开端开辟需供的时分我借没有太了解变乱分收的机造。以是我先来进修了一下变乱分收,那里对变乱分收做一个简朴的总结。
1、变乱分收的机造

变乱分收是由三个办法共同完成的:


  • dispatchTouchEvent() 分收变乱
  • onInterceptTouchEvent() 阻拦变乱
  • onTouchEvent() 处置变乱
并且变乱分收的挨次是:
Activity -> ViewGroup -> View
借助一张图去共同了解:
155202hptypu2fuzfup7up.jpg



(图源:Android变乱分收机造详解:史上最片面、最易懂 - 海角天涯路 - 专客园 (cnblogs.com))
经由过程图片我们能够看到,ViewGroup是比力特别的。onInterceptTouchEven()是他独占办法,他能够将变乱阻拦下去挑选没有分收给下一层的View而是本人处置。

2、缘故原由推测

正在了解了变乱分收的机造事后,我便推测会没有会是由于RecyclerView将变乱阻拦了下去。由于RecyclerView必定有他本人的变乱监听,当ACTION_MOVE的时分该当会触收转动,减载数据然后显现到屏幕上。
那假如实的是被RecyclerView给阻拦了,那我又发生了新的疑问:


  • 按照变乱分收的机造,再ACITON_DOWN的时分该当便决议了targetViewitemView,为何正在ACITON_MOVE的时分会目的View又酿成了RecyclerView
  • RecyclerView是怎样做到只阻拦ACTION_MOVE战ACTION_UP而没有阻拦ACTION_DOWN的呢?
  • 那假如念要完成子项本人处置ACTION_MOVE战ACTION_UP要怎样处置呢?
为了考证我的料想处理那些疑问,我决议来RecyclerView的源码里一探求竟。

3、RecyclerView变乱阻拦机造

既然我们要阐发的是阻拦机造,那末当然该当来onTouchEvent()那个办法里来看。
那里先阐明一下,以下揭出去的源码并非局部。我不断觉得阐发源码不克不及一止一止的扣,否则思绪会很紊乱。正在那篇文章里,我只把对处理成绩有效的部分揭了出去,也能让各人更好了解。假如有小同伴有看没有懂的处所,能够再共同一切源码去了解。
  1. @Override
  2.    public boolean onInterceptTouchEvent(MotionEvent e) {
  3.        ...
  4.        mInterceptingOnItemTouchListener = null;
  5.        if (findInterceptingOnItemTouchListener(e)) {
  6.            cancelScroll();
  7.            return true;
  8.        }
  9.        
  10.        ...
  11.            
  12.        final int action = e.getActionMasked();
  13.        
  14.        ...
  15.        switch (action) {
  16.            case MotionEvent.ACTION_DOWN: {
  17.                ...
  18.            } break
  19.                
  20.            ...
  21.                
  22.            case MotionEvent.ACTION_MOVE: {
  23.                ...
  24.            } break;
  25.            ...
  26.            case MotionEvent.ACTION_UP: {
  27.                ..
  28.            } break;
  29.            ...
  30.        }
  31.        return mScrollState == SCROLL_STATE_DRGING;
  32.    }
复造代码
总的来讲那个函数我把他分为两个部分,switch(aciton)之前switch(aciton)部分。我们按倒序阐发一下。
switch (action)部分

返回值

那部分呢我们需求先看一下最初的返回值,由于返回值决议了能否阻拦。
  1. return mScrollState == SCROLL_STATE_DRGING;
复造代码
注释一下mScrollState那个变量。那个变量是用去记载滑动形态的,有上面三个值:
  1. //截至转动
  2. public static final int SCROLL_STATE_IDLE = 0;
  3. //正正在被内部拖拽,通常是用户正正在用脚指转动
  4. public static final int SCROLL_STATE_DRAGGING = 1;
  5. //主动转动开端
  6. public static final int SCROLL_STATE_SETTLING = 2;
复造代码
第一个战第两个皆好了解,那里注释一下第三个形态。
全部RecyclerView里只要正在fing()办法里会把mScrollState的值设置为SCROLL_STATE_SETTLING。而fling()那个函数呢,实在便是指当您脚指正在屏幕上快速滑动时,会触收主动滑动。便像上面如许:
                                           
155203js8471l1zf7tjuft.gif


那个功用实在各人一样平常利用中也常常会用到。各人明白那个形态的含义便可。
那那个返回值的意义便是判定最初RecyclerView能否是脚斧正正在拖着转动的形态。假如是正正在转动,那末便会阻拦本次变乱;反之则没有阻拦。

ACTION_DOWN

进进到ACTION_DOWN操纵,前脸部门战前面部分皆是设置一些形态(触面的地位,规划转动标的目的是横曲的仍是垂曲的)。最主要的是中心的判定。
  1. case MotionEvent.ACTION_DOWN:
  2.     ...
  3.    if (mScrollState == SCROLL_STATE_SETTLING) {
  4.    getParent().requestDisallowInterceptTouchEvent(true);
  5.    setScrollState(SCROLL_STATE_DRAGGING);
  6.    stopNestedScroll(TYPE_NON_TOUCH);
  7.    }
  8.    ...
  9.    break;
复造代码
假如今朝的形态是正在主动转动的形态下,内里便会将mScrollState设置为SCROLL_STATE_DRAGGING。
那里实在很好念大白。当您的列表正在主动快速转动的过程当中,脚指再按上来,是需求他立即停下去的。那末理所该当那里需求把变乱阻拦下去RecyclerView本人处置。便像上面如许便会阻拦:
                                             
155203dgrggtisdpp52g52.gif



没有阻拦的话,便只能等他本人停下去,那那个主动转动便是不成控的了。
那假如没有是这类状况,便没有会阻拦。那末子项就能够承受到ACTION_DOWN变乱啦。
ACTION_MOVE

  1. case MotionEvent.ACTION_MOVE: {
  2.    ...          
  3.     final int x = (int) (e.getX(index) + 0.5f);
  4.    final int y = (int) (e.getY(index) + 0.5f);
  5.    if (mScrollState != SCROLL_STATE_DRAGGING) {
  6.         final int dx = x - mInitialTouchX;
  7.        final int dy = y - mInitialTouchY;
  8.        boolean startScroll = false;
  9.        if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
  10.              mLastTouchX = x;
  11.              startScroll = true;
  12.        }
  13.        if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
  14.              mLastTouchY = y;
  15.              startScroll = true;
  16.        }
  17.        if (startScroll) {
  18.              setScrollState(SCROLL_STATE_DRAGGING);
  19.        }
  20.    }
  21. } break;
复造代码
  1. [/code] ACTION_MOVE内里便很简朴啦。
  2. [list=1]
  3. [*] 假如当前[b]mScrollState[/b]的形态是正正在转动,那末便没有做任那边理了。那个时分暗示脚斧正正在拖着列表转动,天然是要阻拦下去的。
  4. [*] 假如当前[b]mScrollState[/b]的形态没有是转动,那便会停止一个判定了。判定您脚指的挪动的间隔能否正在响应标的目的上超越了一个阈值。假如超越了那个阈值,阐明您念要开端滑动了,那末那个时分又会挪用setScrollState(SCROLL_STATE_DRAGGING)去将mScrollState的值设置为转动,将变乱阻拦下去。
  5. [/list]
  6. [size=4]ACTION_UP[/size]
  7. ACTION_UP里并出有对[b]mScrollState[/b]停止修正战赋值。以是那个时分也便会按照能否正正在滑动去判定能否阻拦变乱了。
  8. [size=4]小结[/size]
  9. [b]RecyclerView[/b]的确会阻拦变乱,会对最根本的三个变乱按照状况阻拦:
  10. [list]
  11. [*] ACTION_DOWN:当列表正在主动转动的形态下会阻拦,用于处置截至转动。
  12. [*] ACTION_MOVE:当脚指挪动的间隔正在对应标的目的上超越了阈值,便会阻拦失落变乱,用于列表转动。
  13. [*] ACTION_UP :按照当前线表能否处于转动形态挑选能否阻拦。
  14. [/list]那部分的内乱容实在便曾经能证明我们的料想了。
  15. [size=5]switch (action) 之前[/size]
  16. 看到那里您能够会猎奇,前里没有是曾经能证明料想了吗?别慢,正在阐发源码的时分我借发明一个工具,短短的几止代码,展示出[b]RecyclerView[/b]的灵活性。那也便是为何我要把那部分放到前面来讲。
  17. [code]    mInterceptingOnItemTouchListener = null;
  18.    if (findInterceptingOnItemTouchListener(e)) {
  19.        cancelScroll();
  20.        return true;
  21.    }
复造代码
我们先从mInterceptingOnItemTouchListener的规范OnItemTouchListener接心开端提及吧。
OnItemTouchListener接心

熟习ListView的同窗皆明白,ListView能够经由过程setOnItemClickListener()去给一个ItemView增加变乱的监听器,而RecyclerView并出有如许的办法。
  那末您能够便有疑问了,为何RecyclerView正在各圆里的设想皆要劣于ListView,恰恰正在面击变乱上却出有处置的十分好呢?实在没有是如许的,ListView正在面击变乱上处置得其实不兽性化,setOnItemClickListener()办法注册的是子项的面击变乱,但假如我念面击的是子项里详细的某一个按钮呢?固然ListView也能做到,可是完成起去便相比照较费事了。为此,RecyclerView痛快间接摒弃了子项面击变乱的监听器,让一切的面击变乱皆由详细的View来注册,便再出有那个搅扰了。
(戴自郭霖《第一止代码》)
郭神正在书中给出的办法,也是正在Adapter的onCreateViewHolder()办法里来给每个子项绑定变乱监听,如许做的确更灵活,但同时由于每一个子项皆绑定了变乱监听,正在内乱存上也会有必然的耗损。实在RecyclerView内乱部也有一个接心,可以完成对全部RecyclerView的监听。
  1.     public interface OnItemTouchListener {
  2.    
  3.        boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e);
  4.        void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e);
  5.        void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);
  6.    }
复造代码
前两个经由过程办法名我们能够随便的猜出他们的目标,没有便是ViewGroup里变乱分收的两个函数吗。
我们重面道一下第三个函数。第三个函数正在ViewGroup里也有完成,他的感化是设置ViewGroup能否开启变乱阻拦。也注释道,经由过程那个函数我们能够正在子View里设置女ViewGroup封闭阻拦,如许就可以让子View自止处置变乱了。
那关于全部接心的感化,那里我放一下民圆的正文。
  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&#39;s own scrolling behavior.
  This can be useful for applications that wish to implement various forms of gestural manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept a touch interaction already in progress even if the RecyclerView is already handling that gesture stream itself for the purposes of scrolling.
翻译一下,大要意义便是那个监听器许可正在RecyclerView考虑本人的转动变乱之前,正在ViewGroup层里阻拦变乱。
道人话便是RecyclerView正在处置变乱的时分,得先看那个监听器要没有要阻拦那个工作,假如监听器要阻拦,那末RecyclerView便出资历本人处置了。
完成了对RecyclerView全部视图的监听,许可我们自界说对一些特定脚势的处置。
用那个接心有甚么长处呢?

  • 节流内乱存。正在运转时期只要一个监听器,没有像之前RecyclerView的每一个子项皆要设置一个监听器。
  • 关于全部里板来讲愈加灵活。假如道我们需求对全部里板有一些自界说的脚势操纵,那末便只能经由过程完成那个接心,来子项里完成曾经没有太能够了。

阻拦历程

由于本文篇幅缘故原由,便没有展现怎样来完成了。我们那里经由过程源码阐发一下他是怎样做到让RecyclerView出资历处置本人的转动的。
  1.    private final ArrayList<OnItemTouchListener> mOnItemTouchListeners =
  2.            new ArrayList<>();
  3.    private OnItemTouchListener mInterceptingOnItemTouchListener;
复造代码
起首是有两个齐局变量,一个用去寄存一切的自界说完成的OnItemTouchListener。我们能够经由过程挪用addOnItemTouchListener()去增加监听器。那里也阐明了一个RecycerView里能够自界说多个监听器。另外一个是用去记载阻拦变乱的监听器。能够那里有面懵,看到上面就可以大白了。
  1. public void addOnItemTouchListener(@NonNull OnItemTouchListener listener) {
  2.    mOnItemTouchListeners.add(listener);
  3. }
复造代码
然后我们回到RecyclerView的onInterceptTouchEvent()那五止代码
  1.     mInterceptingOnItemTouchListener = null;
  2.    if (findInterceptingOnItemTouchListener(e)) {
  3.        cancelScroll();
  4.        return true;
  5.    }
复造代码
先将mInterceptingOnItemTouchListener置为null,是为了避免上一次赋值的mInterceptingOnItemTouchListener出有被烧毁,招致堕落。

然后我们到findInterceptingOnItemTouchListener()办法里来看看。
  1.     private boolean findInterceptingOnItemTouchListener(MotionEvent e) {
  2.        int action = e.getAction();
  3.        final int listenerCount = mOnItemTouchListeners.size();
  4.        for (int i = 0; i < listenerCount; i++) {
  5.            final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
  6.            if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
  7.                mInterceptingOnItemTouchListener = listener;
  8.                return true;
  9.            }
  10.        }
  11.        return false;
  12.    }
复造代码
那里的逻辑十分简朴,来遍历监听器数组,假如发明此中一个监听器阻拦了此类变乱并且变乱没有是ACTION_CANCEL ,那末便给mInterceptingOnItemTouchListener赋值。那里阐明了mInterceptingOnItemTouchListener的用途,记载了阻拦变乱的监听器。
然后假如找到了那么一个监听器,返回true,那末RecyclerView便会打消转动,帮监听器间接阻拦下本次变乱,确保没有会往下分收。从那里就可以看出那个监听器的劣先级了。

而对变乱的处置的进口呢,是正在RecyclerView的onTouchEvnet()内里。
  1.     @Override
  2.    public boolean onTouchEvent(MotionEvent e) {
  3.        ...
  4.        if (dispatchToOnItemTouchListeners(e)) {
  5.            cancelScroll();
  6.            return true;
  7.        }
  8.        ...
  9.    }
复造代码
那里挪用的是dispatchToOnItemTouchListeners()那个办法
  1.     private boolean dispatchToOnItemTouchListeners(MotionEvent e) {
  2.        if (mInterceptingOnItemTouchListener == null) {
  3.            if (e.getAction() == MotionEvent.ACTION_DOWN) {
  4.                return false;
  5.            }
  6.            return findInterceptingOnItemTouchListener(e);
  7.        } else {
  8.            mInterceptingOnItemTouchListener.onTouchEvent(this, e);
  9.            final int action = e.getAction();
  10.            if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
  11.                mInterceptingOnItemTouchListener = null;
  12.            }
  13.            return true;
  14.        }
  15.    }
复造代码
正在那个办法里,假如mInterceptingOnItemTouchListener没有为空,那末便正在那里挪用它的onTouchEvent()去向理。返回了true,ReyclerView天然便没有会本人处置了。
小结

那个便是自界说的onItemTouchListener的阻拦历程了。

  • 我们自界说完成的onItemTouchListener,需求经由过程addOnItemTouchListener()增加到RecyclerView里。
  • RecyclerView正在判定阻拦变乱时,会劣先判定有无自界说的onItemTouchListener要阻拦此次变乱,假如有,则会帮他阻拦下去。
  • RecyclerView正在处置变乱时,也会劣先判定判定有无自界说的onItemTouchListener要处置该次变乱。假如有,那便交给它处置,本人没有再处置。

4、处理成绩

正在阐发完好个阻拦机造后,我们就能够有两套处理计划了。详细计划能够按照需供自止挑选。
计划一

这类计划保举用于针对子项的某一详细组件的事项,好比RecyclerView的子项是一个RelativeLayout,变乱只针对此中的一个Button。


  • 假如子View没有需求本人处置ACTION_MOVE,只需求正在ACTION_UP里做一些扫尾操纵,那末能够把扫尾操纵增加一份到ACTION_CANCEL里。
  • 假如子View需求本人处置ACTION_MOVE战ACTION_UP,那末就能够经由过程requestDisallowInterceptTouchEvent(boolean disallowIntercept)去设置没有让RecyclerView对变乱停止阻拦。不外这类办法没有倡议增加到ACTION_DOWN里,会招致列表没法滑动。
计划两

这类计划用于针对子项大概全部RecyclerView的变乱。
经由过程完成onItemTouchListener接心去处置本人需求的变乱,经由过程脚指按下的地位获得到详细的子项。

5、总结

做一个团体的总结。

  • ReyclerViewItemView的变乱,出格是ACTION_MOVE战ACTION_UP简单被RecyclerView阻拦,可是会收收一个ACTION_CANCEL给子View用去处置一些扫尾事情。
  • 假如ItemView没有期望被RecyclerView给阻拦,能够经由过程parent.requestDisallowInterceptTouchEvent(true)去设置,如许便没有会被阻拦。
  • RecyclerView供给了一个内乱部接心onItemTouchListener用于对全部RecyclerView停止监听,能够完成更灵活的功用,劣先级下于RecyclerView本人的变乱处置。

最初,十分感激您能够看到那里。我是一个行将年夜四的练习死,Android的常识系统借出有成为一个结实的体系,正在那之前我以至皆没有明白甚么是变乱分收。本篇内乱容满是本人进修相干常识后读源码的了解,不免会有不对。假如发明了毛病期望各人体谅并为我指堕落误。也十分期望那篇文章能给您带去一面协助。

感激您的浏览

免责声明:假如进犯了您的权益,请联络站少,我们会实时删除侵权内乱容,感谢协作!
1、本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明,如果原文没有版权声明,按照目前互联网开放的原则,我们将在不通知作者的情况下,转载文章;如果原文明确注明“禁止转载”,我们一定不会转载。如果我们转载的文章不符合作者的版权声明或者作者不想让我们转载您的文章的话,请您发送邮箱:Cdnjson@163.com提供相关证明,我们将积极配合您!
2、本网站转载文章仅为传播更多信息之目的,凡在本网站出现的信息,均仅供参考。本网站将尽力确保所提供信息的准确性及可靠性,但不保证信息的正确性和完整性,且不对因信息的不正确或遗漏导致的任何损失或损害承担责任。
3、任何透过本网站网页而链接及得到的资讯、产品及服务,本网站概不负责,亦不负任何法律责任。
4、本网站所刊发、转载的文章,其版权均归原作者所有,如其他媒体、网站或个人从本网下载使用,请在转载有关文章时务必尊重该文章的著作权,保留本网注明的“稿件来源”,并自负版权等法律责任。
回复 关闭延时

使用道具 举报

 
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则