ViewPager,ScrollView 嵌套ViewPager滑动冲突解决

来源:互联网 发布:红蜘蛛软件一年多少钱 编辑:程序博客网 时间:2024/05/21 09:42

http://blog.csdn.net/gdutxiaoxu/article/details/52939127

目录(?)[+]

  1. ViewPagerScrollView 嵌套ViewPager滑动冲突解决
    1. 本篇文章已授权微信公众号 guolin_blog 郭霖独家发布
  2. 先看一下效果图
    1. ScrollView里面嵌套ViewPager
    2. ViewPager里面嵌套ViewPager
  3. View的 事件分发机制
    1. 下面引用图解 Android 事件分发机制这一篇博客的内容
    2. 总结
  4. 解决事件滑动冲突的思路及方法
    1. 常见的三种情况
    2. 解决思路
    3. 外部解决法
    4. 内部解决法
  5. ScrollView 里面嵌套ViewPager导致的滑动冲突
    1. 外部解决法
    2. 内部解决法
  6. 注意事项坑
    1. 在Activity中的相关解决方法
    2. 在Fragment中的相关解决方法
      1. 个人疑点
    3. ViewPager里面嵌套ViewPager导致的滑动冲突
      1. 内部解决法
      2. 外部解决法
    4. 讨论
    5. 总结
    6. 题外话

ViewPager,ScrollView 嵌套ViewPager滑动冲突解决

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

这篇博客主要讲解一下几个问题
- 粗略地介绍一下View的事件分发机制
- 解决事件滑动冲突的思路及方法
- ScrollView 里面嵌套ViewPager导致的滑动冲突
- ViewPager里面嵌套ViewPager 导致的滑动冲突
- 轮播图的几种实现方式

文章首发地址CSDN:http://blog.csdn.NET/gdutxiaoxu/article/details/52939127

先看一下效果图

ScrollView里面嵌套ViewPager

ViewPager里面嵌套ViewPager


View的 事件分发机制

这篇博客大打算详细讲解View的事件分发机制,因为网上已经出现了一系列的好 文章,我自己的水平也有限,目前肯定写得不咋的。

先啰嗦一下,View 的事件分发机制主要涉及到一下三个 方法

  • dispatchTouchEvent ,这个方法主要是用来分发事件的
  • onInterceptTouchEvent,这个方法主要是用来拦截事件的(需要注意的是ViewGroup才有这个方法,View没有onInterceptTouchEvent这个方法
  • onTouchEvent这个方法主要是用来处理事件的
  • requestDisallowInterceptTouchEvent(true),这个方法能够影响父View是否拦截事件,true表示 不拦截事件,false表示拦截事件

下面引用图解 Android 事件分发机制这一篇博客的内容

  • 仔细看的话,图分为3层,从上往下依次是Activity、ViewGroup、View
  • 事件从左上角那个白色箭头开始,由Activity的dispatchTouchEvent做分发
  • 箭头的上面字代表方法返回值,(return true、return false、return super.xxxxx(),super 的意思是调用父类实现。
  • dispatchTouchEvent和 onTouchEvent的框里有个【true—->消费】的字,表示的意思是如果方法返回true,那么代表事件就此消费,不会继续往别的地方传了,事件终止。
  • 目前所有的图的事件是针对ACTION_DOWN的,对于ACTION_MOVE和ACTION_UP我们最后做分析。
  • 之前图中的Activity 的dispatchTouchEvent 有误(图已修复),只有return super.dispatchTouchEvent(ev) 才是往下走,返回true 或者 false 事件就被消费了(终止传递)。

总结

当TouchEvent发生时,首先Activity将TouchEvent传递给最顶层的View,TouchEvent最先到达最顶层 view 的 dispatchTouchEvent ,然后由 dispatchTouchEvent 方法进行分发,

  • 如果dispatchTouchEvent返回true 消费事件,事件终结。
  • 如果dispatchTouchEvent返回 false ,则回传给父View的onTouchEvent事件处理;

    onTouchEvent事件返回true,事件终结,返回false,交给父View的OnTouchEvent方法处理

  • 如果dispatchTouchEvent返回super的话,默认会调用自己的onInterceptTouchEvent方法

    默认的情况下interceptTouchEvent回调用super方法,super方法默认返回false,所以会交给子View的onDispatchTouchEvent方法处理

    如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,

    如果 interceptTouchEvent 返回 false ,那么就传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。

关于更多详细分析,请查看原博客图解 Android 事件分发机制,真心推荐,写得很好。


解决事件滑动冲突的思路及方法

常见的三种情况

第一种情况,滑动方向不同

第二种情况,滑动方向相同

第三种情况,上述两种情况的嵌套

解决思路

看了上面三种情况,我们知道他们的共同特点是父View 和子View都想争着响应我们的触摸事件,但遗憾的是我们的触摸事件 同一时刻只能被某一个View或者ViewGroup拦截消费,所以就产生了滑动冲突?那既然同一时刻只能由某一个View或者ViewGroup消费拦截,那我们就只需要 决定在某个时刻由这个View或者ViewGroup拦截事件,另外的 某个时刻由 另外一个View或者ViewGroup拦截事件不就OK了吗?综上,正如 在《Android开发艺术》 一书提出的,总共 有两种解决方案

以下解决思路来自于 《Android开发艺术》 书籍

下面的两种方法针对第一种情况(滑动方向不同),父View是上下滑动,子View是左右滑动的情况。

外部解决法

从父View着手,重写onInterceptTouchEvent方法,在父View需要拦截的时候拦截,不要的时候返回false,为代码大概 如下

<code class="hljs java has-numbering"><span class="hljs-annotation">@Override</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onInterceptTouchEvent</span>(MotionEvent ev) {    <span class="hljs-keyword">final</span> <span class="hljs-keyword">float</span> x = ev.getX();    <span class="hljs-keyword">final</span> <span class="hljs-keyword">float</span> y = ev.getY();    <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> action = ev.getAction();    <span class="hljs-keyword">switch</span> (action) {        <span class="hljs-keyword">case</span> MotionEvent.ACTION_DOWN:            mDownPosX = x;            mDownPosY = y;            <span class="hljs-keyword">break</span>;        <span class="hljs-keyword">case</span> MotionEvent.ACTION_MOVE:            <span class="hljs-keyword">final</span> <span class="hljs-keyword">float</span> deltaX = Math.abs(x - mDownPosX);            <span class="hljs-keyword">final</span> <span class="hljs-keyword">float</span> deltaY = Math.abs(y - mDownPosY);            <span class="hljs-comment">// 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截</span>            <span class="hljs-keyword">if</span> (deltaX > deltaY) {                <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;            }    }    <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.onInterceptTouchEvent(ev);}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li></ul>

内部解决法

从子View着手,父View先不要拦截任何事件,所有的 事件传递给 子View,如果子View需要此事件就消费掉,不需要此事件的话就交给 父View处理。

实现思路 如下,重写子 View的dispatchTouchEvent方法,在Action_down 动作中通过方法 requestDisallowInterceptTouchEvent(true) 先请求 父 View不要拦截事件,这样保证 子View能够 接受到Action_move事件,再在Action_move动作中根据 自己的逻辑是否要拦截事件,不要的 话再交给 父View处理

<code class="language-java hljs  has-numbering"><span class="hljs-annotation">@Override</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">dispatchTouchEvent</span>(MotionEvent ev) {    <span class="hljs-keyword">int</span> x = (<span class="hljs-keyword">int</span>) ev.getRawX();    <span class="hljs-keyword">int</span> y = (<span class="hljs-keyword">int</span>) ev.getRawY();    <span class="hljs-keyword">int</span> dealtX = <span class="hljs-number">0</span>;    <span class="hljs-keyword">int</span> dealtY = <span class="hljs-number">0</span>;    <span class="hljs-keyword">switch</span> (ev.getAction()) {        <span class="hljs-keyword">case</span> MotionEvent.ACTION_DOWN:            dealtX = <span class="hljs-number">0</span>;            dealtY = <span class="hljs-number">0</span>;            <span class="hljs-comment">// 保证子View能够接收到Action_move事件</span>            getParent().requestDisallowInterceptTouchEvent(<span class="hljs-keyword">true</span>);            <span class="hljs-keyword">break</span>;        <span class="hljs-keyword">case</span> MotionEvent.ACTION_MOVE:            dealtX += Math.abs(x - lastX);            dealtY += Math.abs(y - lastY);            Log.i(TAG, <span class="hljs-string">"dealtX:="</span> + dealtX);            Log.i(TAG, <span class="hljs-string">"dealtY:="</span> + dealtY);            <span class="hljs-comment">// 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截</span>            <span class="hljs-keyword">if</span> (dealtX >= dealtY) {                getParent().requestDisallowInterceptTouchEvent(<span class="hljs-keyword">true</span>);            } <span class="hljs-keyword">else</span> {                getParent().requestDisallowInterceptTouchEvent(<span class="hljs-keyword">false</span>);            }            lastX = x;            lastY = y;            <span class="hljs-keyword">break</span>;        <span class="hljs-keyword">case</span> MotionEvent.ACTION_CANCEL:            <span class="hljs-keyword">break</span>;        <span class="hljs-keyword">case</span> MotionEvent.ACTION_UP:            <span class="hljs-keyword">break</span>;    }    <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.dispatchTouchEvent(ev);}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li></ul>

ScrollView 里面嵌套ViewPager导致的滑动冲突

外部解决法

如上面所述,从 父View ScrollView着手,重写 OnInterceptTouchEvent方法,在上下滑动的时候拦截事件,在左右滑动的时候不拦截事件,返回 false,这样确保子View 的dispatchTouchEvent方法会被调用,代码 如下

<code class="hljs java has-numbering"><span class="hljs-javadoc">/** * @ explain:这个ScrlloView不拦截水平滑动事件, * 是用来解决 ScrollView里面嵌套ViewPager使用的 * @ author:xujun on 2016/10/25 15:28 * @ email:gdutxiaoxu@163.com */</span><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">VerticalScrollView</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ScrollView</span> {</span>    <span class="hljs-keyword">public</span> <span class="hljs-title">VerticalScrollView</span>(Context context) {        <span class="hljs-keyword">super</span>(context);    }    <span class="hljs-keyword">public</span> <span class="hljs-title">VerticalScrollView</span>(Context context, AttributeSet attrs) {        <span class="hljs-keyword">super</span>(context, attrs);    }    <span class="hljs-keyword">public</span> <span class="hljs-title">VerticalScrollView</span>(Context context, AttributeSet attrs, <span class="hljs-keyword">int</span> defStyleAttr) {        <span class="hljs-keyword">super</span>(context, attrs, defStyleAttr);    }    <span class="hljs-annotation">@TargetApi</span>(<span class="hljs-number">21</span>)    <span class="hljs-keyword">public</span> <span class="hljs-title">VerticalScrollView</span>(Context context, AttributeSet attrs, <span class="hljs-keyword">int</span> defStyleAttr, <span class="hljs-keyword">int</span>            defStyleRes) {        <span class="hljs-keyword">super</span>(context, attrs, defStyleAttr, defStyleRes);    }    <span class="hljs-keyword">private</span> <span class="hljs-keyword">float</span> mDownPosX = <span class="hljs-number">0</span>;    <span class="hljs-keyword">private</span> <span class="hljs-keyword">float</span> mDownPosY = <span class="hljs-number">0</span>;    <span class="hljs-annotation">@Override</span>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onInterceptTouchEvent</span>(MotionEvent ev) {        <span class="hljs-keyword">final</span> <span class="hljs-keyword">float</span> x = ev.getX();        <span class="hljs-keyword">final</span> <span class="hljs-keyword">float</span> y = ev.getY();        <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> action = ev.getAction();        <span class="hljs-keyword">switch</span> (action) {            <span class="hljs-keyword">case</span> MotionEvent.ACTION_DOWN:                mDownPosX = x;                mDownPosY = y;                <span class="hljs-keyword">break</span>;            <span class="hljs-keyword">case</span> MotionEvent.ACTION_MOVE:                <span class="hljs-keyword">final</span> <span class="hljs-keyword">float</span> deltaX = Math.abs(x - mDownPosX);                <span class="hljs-keyword">final</span> <span class="hljs-keyword">float</span> deltaY = Math.abs(y - mDownPosY);                <span class="hljs-comment">// 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截</span>                <span class="hljs-keyword">if</span> (deltaX > deltaY) {                    <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;                }        }        <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.onInterceptTouchEvent(ev);    }}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li></ul>

内部解决法

如上面上述,通过requestDisallowInterceptTouchEvent(true)方法来影响父View是否拦截事件,我们通过重写ViewPager的 dispatchTouchEvent()方法,在左右滑动的时候请求父View ScrollView不要拦截事件,其他的时候由子View 拦截事件

<code class="language-java hljs  has-numbering"><span class="hljs-javadoc">/** * @ explain:这个 ViewPager是用来解决ScrollView里面嵌套ViewPager的 内部解决法的 * @ author:xujun on 2016/10/25 16:38 * @ email:gdutxiaoxu@163.com */</span><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyViewPager</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ViewPager</span> {</span>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String TAG = <span class="hljs-string">"xujun"</span>;    <span class="hljs-keyword">int</span> lastX = -<span class="hljs-number">1</span>;    <span class="hljs-keyword">int</span> lastY = -<span class="hljs-number">1</span>;    <span class="hljs-keyword">public</span> <span class="hljs-title">MyViewPager</span>(Context context) {        <span class="hljs-keyword">super</span>(context);    }    <span class="hljs-keyword">public</span> <span class="hljs-title">MyViewPager</span>(Context context, AttributeSet attrs) {        <span class="hljs-keyword">super</span>(context, attrs);    }    <span class="hljs-annotation">@Override</span>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">dispatchTouchEvent</span>(MotionEvent ev) {        <span class="hljs-keyword">int</span> x = (<span class="hljs-keyword">int</span>) ev.getRawX();        <span class="hljs-keyword">int</span> y = (<span class="hljs-keyword">int</span>) ev.getRawY();        <span class="hljs-keyword">int</span> dealtX = <span class="hljs-number">0</span>;        <span class="hljs-keyword">int</span> dealtY = <span class="hljs-number">0</span>;        <span class="hljs-keyword">switch</span> (ev.getAction()) {            <span class="hljs-keyword">case</span> MotionEvent.ACTION_DOWN:                dealtX = <span class="hljs-number">0</span>;                dealtY = <span class="hljs-number">0</span>;                <span class="hljs-comment">// 保证子View能够接收到Action_move事件</span>                getParent().requestDisallowInterceptTouchEvent(<span class="hljs-keyword">true</span>);                <span class="hljs-keyword">break</span>;            <span class="hljs-keyword">case</span> MotionEvent.ACTION_MOVE:                dealtX += Math.abs(x - lastX);                dealtY += Math.abs(y - lastY);                Log.i(TAG, <span class="hljs-string">"dealtX:="</span> + dealtX);                Log.i(TAG, <span class="hljs-string">"dealtY:="</span> + dealtY);                <span class="hljs-comment">// 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截</span>                <span class="hljs-keyword">if</span> (dealtX >= dealtY) {                    getParent().requestDisallowInterceptTouchEvent(<span class="hljs-keyword">true</span>);                } <span class="hljs-keyword">else</span> {                    getParent().requestDisallowInterceptTouchEvent(<span class="hljs-keyword">false</span>);                }                lastX = x;                lastY = y;                <span class="hljs-keyword">break</span>;            <span class="hljs-keyword">case</span> MotionEvent.ACTION_CANCEL:                <span class="hljs-keyword">break</span>;            <span class="hljs-keyword">case</span> MotionEvent.ACTION_UP:                <span class="hljs-keyword">break</span>;        }        <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.dispatchTouchEvent(ev);    }}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li></ul>

注意事项(坑)

当我们ScrollView的最上层的Layout里面多多个孩子的时候,当下面一个孩子是RecyclerView或者ListView的时候,往往会自动滑动到ListView或者RecyclerView 的第一个item,导致进入界面的时候会导致RecyclerView 上面的 View被滑动到界面之外,看不见,这时候的用户体验是比较差的

即结构如下面的时候

在Activity中的相关解决方法

于是我查找了相关的资料,在Activity中完美解决,主要要一下两种方法

第一种方法,重写Activity的onWindowFocusChanged()方法,在里面调用mNoHorizontalScrollView.scrollTo(0,0);方法,滑动到顶部,因为onWindowFocusChanged是在所有View绘制完毕的时候才会回调的,不熟悉的话建议先回去看一下Activity的生命周期的相关介绍

<code class="hljs java has-numbering"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">scroll</span>() {    mNoHorizontalScrollView.scrollTo(<span class="hljs-number">0</span>,<span class="hljs-number">0</span>);}<span class="hljs-annotation">@Override</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onWindowFocusChanged</span>(<span class="hljs-keyword">boolean</span> hasFocus) {    <span class="hljs-keyword">super</span>.onWindowFocusChanged(hasFocus);    <span class="hljs-keyword">if</span>(hasFocus  && first){        first=<span class="hljs-keyword">false</span>;        scroll();    }}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul>

第二种解决方法,调用RecyclerView上面的View的一下方法,让其获取焦点

<code class="hljs avrasm has-numbering">view<span class="hljs-preprocessor">.setFocusable</span>(true)<span class="hljs-comment">;  </span>view<span class="hljs-preprocessor">.setFocusableInTouchMode</span>(true)<span class="hljs-comment">;  </span>view<span class="hljs-preprocessor">.requestFocus</span>()<span class="hljs-comment">;</span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul>

这段代码在初始化的时候就让该界面的顶部的某一个控件获得焦点,滚动条自然就显示到顶部了。

在Fragment中的相关解决方法

同样是调用第二种方法,调用RecyclerView上面的View的一下方法,让其获取焦点

<code class="hljs avrasm has-numbering">view<span class="hljs-preprocessor">.setFocusable</span>(true)<span class="hljs-comment">;  </span>view<span class="hljs-preprocessor">.setFocusableInTouchMode</span>(true)<span class="hljs-comment">;  </span>view<span class="hljs-preprocessor">.requestFocus</span>()<span class="hljs-comment">;</span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets_01.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul>

这段代码在初始化的时候就让该界面的顶部的某一个控件获得焦点,滚动条自然就显示到顶部了。但是该方法存在缺点,就是当我们上面的view如果滑动到一半的时候,切换到下一个Fragment,在切换回来的时候,RecyclerView的第一个item会自动滑动到顶部。目前我还没有找到相对比较好的解决这个问题的方法,大家知道相关解决方法的话也欢迎联系我,可以加我微信或者在留言区评论,谢谢

个人疑点

借鉴于解决Activity的方法,目前我还没有找到一个方法是在Fragemnt界面完全绘制完毕以后回调的方法,如果大家知道怎样处理的 话,欢迎大家提出来


ViewPager里面嵌套ViewPager导致的滑动冲突

内部解决法

从子View ViewPager着手,重写 子View的 dispatchTouchEvent方法,在子 View需要拦截的时候进行拦截,否则交给父View处理,代码如下

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ChildViewPager</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ViewPager</span> {</span>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String TAG = <span class="hljs-string">"xujun"</span>;    <span class="hljs-keyword">public</span> <span class="hljs-title">ChildViewPager</span>(Context context) {        <span class="hljs-keyword">super</span>(context);    }    <span class="hljs-keyword">public</span> <span class="hljs-title">ChildViewPager</span>(Context context, AttributeSet attrs) {        <span class="hljs-keyword">super</span>(context, attrs);    }    <span class="hljs-annotation">@Override</span>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">dispatchTouchEvent</span>(MotionEvent ev) {        <span class="hljs-keyword">int</span> curPosition;        <span class="hljs-keyword">switch</span> (ev.getAction()) {            <span class="hljs-keyword">case</span> MotionEvent.ACTION_DOWN:                getParent().requestDisallowInterceptTouchEvent(<span class="hljs-keyword">true</span>);                <span class="hljs-keyword">break</span>;            <span class="hljs-keyword">case</span> MotionEvent.ACTION_MOVE:                curPosition = <span class="hljs-keyword">this</span>.getCurrentItem();                <span class="hljs-keyword">int</span> count = <span class="hljs-keyword">this</span>.getAdapter().getCount();                Log.i(TAG, <span class="hljs-string">"curPosition:="</span> +curPosition);                <span class="hljs-comment">// 当当前页面在最后一页和第0页的时候,由父亲拦截触摸事件</span>                <span class="hljs-keyword">if</span> (curPosition == count - <span class="hljs-number">1</span>|| curPosition==<span class="hljs-number">0</span>) {                    getParent().requestDisallowInterceptTouchEvent(<span class="hljs-keyword">false</span>);                } <span class="hljs-keyword">else</span> {<span class="hljs-comment">//其他情况,由孩子拦截触摸事件</span>                    getParent().requestDisallowInterceptTouchEvent(<span class="hljs-keyword">true</span>);                }        }        <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.dispatchTouchEvent(ev);    }}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li></ul>

外部解决法

这个如果要采用外部解决法来解决的话想,相对很麻烦,我提一下自己的个人思路,我们可以先测量子View在哪个区域,然后我们在根据我们按下的点是否在区域以内,如果是的话,在根据子View时候需要拦截进行处理


讨论

对于这种效果,上面是轮播图的,下面是RecyclerView或者ListView的,一般有一下几种实现方式
- 使用我们上述提高的ScrollView里面嵌套ViewPager和RecyclerView,这种实现方式需要自己解决View滑动事件的冲突,同时还有我在上述提高的在Fragment中存在的问题
- 使用listView的addHeaderView来实现,或者是通过多种不同的item来实现
- 使用RecyclerView添加headerView来实现,或者复用多种不同的item来实现。关于RecyclerView如何添加headerView可以参考鸿洋大神的这一篇博客Android 优雅的为RecyclerView添加HeaderView和FooterView
- 使用SupportLibrary中的CoordinatorLayout等控件

其布局文件如下,Activity代码见项目中的SixActivity

<code class="language-java hljs  has-numbering"><?xml version=<span class="hljs-string">"1.0"</span> encoding=<span class="hljs-string">"utf-8"</span>?><android.support.design.widget.CoordinatorLayout    xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>    xmlns:app=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>    android:layout_width=<span class="hljs-string">"match_parent"</span>    android:layout_height=<span class="hljs-string">"match_parent"</span>    android:background=<span class="hljs-string">"@android:color/background_light"</span>    android:fitsSystemWindows=<span class="hljs-string">"true"</span>>    <android.support.design.widget.AppBarLayout        android:layout_width=<span class="hljs-string">"match_parent"</span>        android:layout_height=<span class="hljs-string">"300dp"</span>        android:fitsSystemWindows=<span class="hljs-string">"true"</span>        android:theme=<span class="hljs-string">"@style/ThemeOverlay.AppCompat.Dark.ActionBar"</span>    >        <android.support.design.widget.CollapsingToolbarLayout            android:layout_width=<span class="hljs-string">"match_parent"</span>            android:layout_height=<span class="hljs-string">"match_parent"</span>            app:layout_scrollFlags=<span class="hljs-string">"scroll|snap"</span>>            <android.support.v4.view.ViewPager                android:id=<span class="hljs-string">"@+id/viewPager"</span>                android:layout_width=<span class="hljs-string">"match_parent"</span>                android:layout_height=<span class="hljs-string">"match_parent"</span>            >            </android.support.v4.view.ViewPager>            <TextView                android:id=<span class="hljs-string">"@+id/tv_page"</span>                android:layout_width=<span class="hljs-string">"match_parent"</span>                android:layout_height=<span class="hljs-string">"wrap_content"</span>                android:layout_gravity=<span class="hljs-string">"bottom"</span>                android:gravity=<span class="hljs-string">"right"</span>                android:text=<span class="hljs-string">"1/10"</span>                android:textColor=<span class="hljs-string">"#000"</span>/>        </android.support.design.widget.CollapsingToolbarLayout>    </android.support.design.widget.AppBarLayout>    <android.support.v7.widget.RecyclerView        android:id=<span class="hljs-string">"@+id/recyclerView"</span>        android:layout_width=<span class="hljs-string">"match_parent"</span>        android:layout_height=<span class="hljs-string">"match_parent"</span>        app:layout_behavior=<span class="hljs-string">"@string/appbar_scrolling_view_behavior"</span>>    </android.support.v7.widget.RecyclerView></android.support.design.widget.CoordinatorLayout></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li></ul>

关于CoordinatorLayout的更多用法,可以参考我的这一篇博客使用CoordinatorLayout打造各种炫酷的效果


总结

  • 当我们滑动方向不同的时候,采用外部解决法和内部解决法,复杂度差不多。
  • 当我们滑动的方向相同的话,建议采用内部解决法来解决,因为采用外部解决法复杂度比较高。而且有时候我们是采用别人的开源控件,这时候去修改别人的源码可能会发生一些意想不到的bug。

题外话

  • 在这篇博客的最后提高的实现轮播图+list列表的几种实现形式,刚开始是不想写的,后面因为ScrollView里面嵌套ViewPager和RecyclerView在fragment中RecyclerView抢占焦点,在某些情况下用户体验不好,才写出来的,跟这篇博客要讲解的View滑动事件冲突没有多大关系,只是给读者提供多种思路而已
  • 至于CoordinatorLayout,是google IO 2015中提出来的,功能很强大,可以说是专门为了解决嵌套导滑动而产生的,极大地方便了开发者,对于初学者,可以暂时不必掌握它,先把其他的基础学好就好
  • 同时卖一下广告,欢迎大家到我的github上面star或者fork,谢谢

参考文章:图解 Android 事件分发机制

文章首发地址CSDN:http://blog.csdn.Net/gdutxiaoxu/article/details/52939127

源码下载地址:https://github.com/gdutxiaoxu/TouchDemo.Git

0 0