首页 > 代码库 > Android——滑动事件冲突解决

Android——滑动事件冲突解决

android中的事件类型分为按键事件和屏幕触摸事件,Touch事件是屏幕触摸事件的基础事件。

android系统中的每个View的子类都具有下面三个与TouchEvent处理密切相关的方法:

(1)public boolean dispatchTouchEvent(MotionEvent ev)这个方法用来分发TouchEvent

(2)public boolean onInterceptTouchEvent(MotionEvent ev)这个方法用来拦截TouchEvent

(3)public boolean onTouchEvent(MotionEvent ev)这个方法用来处理TouchEvent

 

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

如果dispatchTouchEvent返回true,则交给这个view的onTouchEvent处理;如果返回false,则交给这个view的interceptTouchEvent方法来决定是否要拦截这个事件。

如果interceptTouchEvent返回true,也就是拦截了,则交给它的onTouchEvent来处理,如果interceptTouchEvent返回false,则传递给子view,由子view的dispatchTouchEvent再开始这个事件分发。

如果事件传递到某一层的子view的onTouchEvent上了,这个方法返回了false,那么这个事件会从这个view往上传递,都是onTouchEvent来接收。而如果传递到最上面的onTouchEvent也返回false的话,这个事件就好消失,而且接收不到下一次事件。

 

让子view先处理的方法是重写父view的onInterceptTouchEvent事件并返回false

1 public boolean onInterceptTouchEvent(MotionEvent ev){
2     return false;
3 }

 

转自:http://blog.csdn.net/spt110/article/details/7919870

 

 

转自: http://blog.csdn.net/a992036795/article/details/51735501

一、前言 
Android 中解决滑动的方案有2种:外部拦截法 和内部拦截法。 
滑动冲突也存在2种场景: 横竖滑动冲突、同向滑动冲突。 
所以我就写了4个例子来学习如何解决滑动冲突的,这四个例子分别为: 外部拦截法解决横竖冲突、外部拦截法解决同向冲突、内部拦截法解决横竖冲突、内部拦截法解决同向冲突。 
先上效果图: 
技术分享

二、实战 
1、外部拦截法,解决横竖冲突 
思路是,重写父控件的onInterceptTouchEvent方法,然后根据具体的需求,来决定父控件是否拦截事件。如果拦截返回返回true,不拦截返回false。关于为什么返回true就代表拦截事件。可以参考我的上一篇博客:http://blog.csdn.net/a992036795/article/details/51698023 。 如果父控件拦截了事件,则在父控件的onTouchEvent进行相应的事件处理。 
我的这个例子,是一个横向滑动的ViewGroup里面包含了3个竖向滑动的ListView。下面我附上代码: 
HorizontalEx.java

  1 /**
  2  * Created by blueberry on 2016/6/20.
  3  *
  4  * 解决交错的滑动冲突
  5  *
  6  * 外部拦截法
  7  */
  8 public class HorizontalEx extends ViewGroup {
  9 
 10     private static final String TAG = "HorizontalEx";
 11 
 12     private boolean isFirstTouch = true;
 13     private int childIndex;
 14     private int childCount;
 15     private int lastXIntercept, lastYIntercept, lastX, lastY;
 16 
 17     private Scroller mScroller;
 18     private VelocityTracker mVelocityTracker;
 19 
 20     public HorizontalEx(Context context) {
 21         super(context);
 22         init();
 23     }
 24 
 25     public HorizontalEx(Context context, AttributeSet attrs) {
 26         super(context, attrs);
 27         init();
 28     }
 29 
 30     public HorizontalEx(Context context, AttributeSet attrs, int defStyleAttr) {
 31         super(context, attrs, defStyleAttr);
 32         init();
 33     }
 34 
 35     private void init() {
 36         mScroller = new Scroller(getContext());
 37         mVelocityTracker = VelocityTracker.obtain();
 38     }
 39 
 40     @Override
 41     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 42         int width = MeasureSpec.getSize(widthMeasureSpec);
 43         int height = MeasureSpec.getSize(heightMeasureSpec);
 44         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
 45         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
 46 
 47         childCount = getChildCount();
 48         measureChildren(widthMeasureSpec, heightMeasureSpec);
 49 
 50         if (childCount == 0) {
 51             setMeasuredDimension(0, 0);
 52         } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
 53             width = childCount * getChildAt(0).getMeasuredWidth();
 54             height = getChildAt(0).getMeasuredHeight();
 55             setMeasuredDimension(width, height);
 56         } else if (widthMode == MeasureSpec.AT_MOST) {
 57             width = childCount * getChildAt(0).getMeasuredWidth();
 58             setMeasuredDimension(width, height);
 59         } else {
 60             height = getChildAt(0).getMeasuredHeight();
 61             setMeasuredDimension(width, height);
 62         }
 63     }
 64 
 65     @Override
 66     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 67         int left = 0;
 68         for (int i = 0; i < getChildCount(); i++) {
 69             final View child = getChildAt(i);
 70             child.layout(left + l, t, r + left, b);
 71             left += child.getMeasuredWidth();
 72         }
 73     }
 74 
 75     /**
 76      * 拦截事件
 77      * @param ev
 78      * @return
 79      */
 80     @Override
 81     public boolean onInterceptTouchEvent(MotionEvent ev) {
 82         boolean intercepted = false;
 83         int x = (int) ev.getX();
 84         int y = (int) ev.getY();
 85 
 86         switch (ev.getAction()) {
 87             /*如果拦截了Down事件,则子类不会拿到这个事件序列*/
 88             case MotionEvent.ACTION_DOWN:
 89                 lastXIntercept = x;
 90                 lastYIntercept = y;
 91                 intercepted = false;
 92                 if (!mScroller.isFinished()) {
 93                     mScroller.abortAnimation();
 94                     intercepted = true;
 95                 }
 96                 break;
 97             case MotionEvent.ACTION_MOVE:
 98                 final int deltaX = x - lastXIntercept;
 99                 final int deltaY = y - lastYIntercept;
100                 /*根据条件判断是否拦截该事件*/
101                 if (Math.abs(deltaX) > Math.abs(deltaY)) {
102                     intercepted = true;
103                 } else {
104                     intercepted = false;
105                 }
106                 break;
107             case MotionEvent.ACTION_UP:
108                 intercepted = false;
109                 break;
110 
111         }
112         lastXIntercept = x;
113         lastYIntercept = y;
114         return intercepted;
115     }
116 
117 
118     @Override
119     public boolean onTouchEvent(MotionEvent event) {
120         int x = (int) event.getX();
121         int y = (int) event.getY();
122         mVelocityTracker.addMovement(event);
123         ViewConfiguration configuration = ViewConfiguration.get(getContext());
124         switch (event.getAction()) {
125             case MotionEvent.ACTION_DOWN:
126                 if (!mScroller.isFinished()) {
127                     mScroller.abortAnimation();
128                 }
129                 break;
130             case MotionEvent.ACTION_MOVE:
131                 /*因为这里父控件拿不到Down事件,所以使用一个布尔值,
132                     当事件第一次来到父控件时,对lastX,lastY赋值*/
133                 if (isFirstTouch) {
134                     lastX = x;
135                     lastY = y;
136                     isFirstTouch = false;
137                 }
138                 final int deltaX = x - lastX;
139                 scrollBy(-deltaX, 0);
140                 break;
141             case MotionEvent.ACTION_UP:
142                 int scrollX = getScrollX();
143                 final int childWidth = getChildAt(0).getWidth();
144                 mVelocityTracker.computeCurrentVelocity(1000, configuration.getScaledMaximumFlingVelocity());
145                 float xVelocity = mVelocityTracker.getXVelocity();
146                 if (Math.abs(xVelocity) > configuration.getScaledMinimumFlingVelocity()) {
147                     childIndex = xVelocity < 0 ? childIndex + 1 : childIndex - 1;
148                 } else {
149                     childIndex = (scrollX + childWidth / 2) / childWidth;
150                 }
151                 childIndex = Math.min(getChildCount() - 1, Math.max(childIndex, 0));
152                 smoothScrollBy(childIndex * childWidth - scrollX, 0);
153                 mVelocityTracker.clear();
154                 isFirstTouch = true;
155                 break;
156         }
157 
158         lastX = x;
159         lastY = y;
160         return true;
161     }
162 
163     void smoothScrollBy(int dx, int dy) {
164         mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, 500);
165         invalidate();
166     }
167 
168     @Override
169     public void computeScroll() {
170         if (mScroller.computeScrollOffset()) {
171             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
172             invalidate();
173         }
174     }
175 
176     @Override
177     protected void onDetachedFromWindow() {
178         super.onDetachedFromWindow();
179         mVelocityTracker.recycle();
180     }
181 }

调用代码:

 1 @Override
 2     public void showOutHVData(List<String> data1, List<String> data2, List<String> data3) {
 3         ListView listView1 = new ListView(getContext());
 4         ArrayAdapter<String> adapter1 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data1);
 5         listView1.setAdapter(adapter1);
 6 
 7         ListView listView2 = new ListView(getContext());
 8         ArrayAdapter<String> adapter2 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data2);
 9         listView2.setAdapter(adapter2);
10 
11         ListView listView3 = new ListView(getContext());
12         ArrayAdapter<String> adapter3 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data3);
13         listView3.setAdapter(adapter3);
14 
15         ViewGroup.LayoutParams params
16                 = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
17                 ViewGroup.LayoutParams.MATCH_PARENT);
18 
19         mHorizontalEx.addView(listView1, params);
20         mHorizontalEx.addView(listView2, params);
21         mHorizontalEx.addView(listView3, params);
22     }

其实外部拦截的主要思想都在于对onInterceptTouchEvent的重写。

 1 @Override
 2     public boolean onInterceptTouchEvent(MotionEvent ev) {
 3         boolean intercepted = false;
 4         int x = (int) ev.getX();
 5         int y = (int) ev.getY();
 6 
 7         switch (ev.getAction()) {
 8             /*如果拦截了Down事件,则子类不会拿到这个事件序列*/
 9             case MotionEvent.ACTION_DOWN:
10                 lastXIntercept = x;
11                 lastYIntercept = y;
12                 intercepted = false;
13                 if (!mScroller.isFinished()) {
14                     mScroller.abortAnimation();
15                     intercepted = true;
16                 }
17                 break;
18             case MotionEvent.ACTION_MOVE:
19                 final int deltaX = x - lastXIntercept;
20                 final int deltaY = y - lastYIntercept;
21                 /*根据条件判断是否拦截该事件*/
22                 if (Math.abs(deltaX) > Math.abs(deltaY)) {
23                     intercepted = true;
24                 } else {
25                     intercepted = false;
26                 }
27                 break;
28             case MotionEvent.ACTION_UP:
29                 intercepted = false;
30                 break;
31 
32         }
33         lastXIntercept = x;
34         lastYIntercept = y;
35         return intercepted;
36     }

这几乎是一个实现外部拦截事件的模板,这里一定不要在ACTION_DOWN 中返回 true,否则会让子VIew没有机会得到事件,因为如果在ACTION_DOWN的时候返回了 true,同一个事件序列ViewGroup的disPatchTouchEvent就不会在调用onInterceptTouchEvent方法了,如果不明白可以参考我的上一遍文章。 
还有就是 在ACTION_UP中返回false,因为如果父控件拦截了ACTION_UP,那么子View将得不到UP事件,那么将会影响子View的 Onclick方法等。但这对父控件是没有影响的,因为如果是父控件子ACITON_MOVE中 就拦截了事件,他们UP事件必定也会交给它处理,因为有那么一条定律叫做:父控件一但拦截了事件,那么同一个事件序列的所有事件都将交给他处理。这条结论在我的上一篇文章中已经分析过。 
最后就是在 ACTION_MOVE中根据需求决定是否拦截

2、内部拦截法,解决横竖冲突 
内部拦截主要依赖于父控件的 requestDisallowInterceptTouchEvent方法,关于这个方法我的上篇文章其实已经分析过。他设置父控件的一个标志(FLAG_DISALLOW_INTERCEPT) 
这个标志可以决定父控件是否拦截事件,如果设置了这个标志则不拦截,如果没设这个标志,它就会调用父控件的onInterceptTouchEvent()来询问父控件是否拦截。但这个标志对Down事件无效。 
可以参考一下源码: 
ViewGroup#dispatchTouchEvent

 1 // Handle an initial down.
 2             if (actionMasked == MotionEvent.ACTION_DOWN) {
 3                 // Throw away all previous state when starting a new touch gesture.
 4                 // The framework may have dropped the up or cancel event for the previous gesture
 5                 // due to an app switch, ANR, or some other state change.
 6                 cancelAndClearTouchTargets(ev);
 7                 //清楚标志
 8                 resetTouchState();
 9             }
10 
11             // Check for interception.
12             final boolean intercepted;
13             if (actionMasked == MotionEvent.ACTION_DOWN
14                     || mFirstTouchTarget != null) {
15                     //标志
16                 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
17                 if (!disallowIntercept) {
18                     intercepted = onInterceptTouchEvent(ev);
19                     ev.setAction(action); // restore action in case it was changed
20                 } else {
21                     intercepted = false;
22                 }
23             } else {
24                 // There are no touch targets and this action is not an initial down
25                 // so this view group continues to intercept touches.
26                 intercepted = true;
27             }

 

那么我们如果想使用 内部拦截法拦截事件。 
第一步: 
a、我们要重写父控件的onInterceptTouchEvent,在ACTION_DOWN的时候返回false,负责的话子View调用requestDisallowInterceptTouchEvent也将无能为力。 
b、还有就是其他事件的话都返回true,这样就把能否拦截事件的权利交给了子View。

第二步: 
在子View的dispatchTouchEvent中 来决定是否让父控件拦截事件。 
a. 先要在MotionEvent.ACTION_DOWN:的时候使用mHorizontalEx2.requestDisallowInterceptTouchEvent(true);,负责的话,下一个事件到来时,就交给父控件了。 
b. 然后在MotionEvent.ACTION_MOVE: 根据业务逻辑决定是否调用mHorizontalEx2.requestDisallowInterceptTouchEvent(false);来决定父控件是否拦截事件。 
上代码: 
HorizontalEx2.java

  1 /**
  2  * Created by blueberry on 2016/6/20.
  3  * <p/>
  4  * 内部拦截
  5  * 和 ListViewEx配合使用
  6  */
  7 public class HorizontalEx2 extends ViewGroup {
  8 
  9     private int lastX, lastY;
 10     private int childIndex;
 11     private Scroller mScroller;
 12     private VelocityTracker mVelocityTracker;
 13 
 14     public HorizontalEx2(Context context) {
 15         super(context);
 16         init();
 17     }
 18 
 19     public HorizontalEx2(Context context, AttributeSet attrs) {
 20         super(context, attrs);
 21         init();
 22     }
 23 
 24     public HorizontalEx2(Context context, AttributeSet attrs, int defStyleAttr) {
 25         super(context, attrs, defStyleAttr);
 26         init();
 27     }
 28 
 29     private void init() {
 30         mScroller = new Scroller(getContext());
 31         mVelocityTracker = VelocityTracker.obtain();
 32     }
 33 
 34     @Override
 35     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 36         int width = MeasureSpec.getSize(widthMeasureSpec);
 37         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
 38         int height = MeasureSpec.getSize(heightMeasureSpec);
 39         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
 40 
 41         int childCount = getChildCount();
 42         measureChildren(widthMeasureSpec, heightMeasureSpec);
 43 
 44         if (childCount == 0) {
 45             setMeasuredDimension(0, 0);
 46         } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
 47             height = getChildAt(0).getMeasuredHeight();
 48             width = childCount * getChildAt(0).getMeasuredWidth();
 49             setMeasuredDimension(width, height);
 50         } else if (widthMode == MeasureSpec.AT_MOST) {
 51             width = childCount * getChildAt(0).getMeasuredWidth();
 52             setMeasuredDimension(width, height);
 53         } else {
 54             height = getChildAt(0).getMeasuredHeight();
 55             setMeasuredDimension(width, height);
 56         }
 57     }
 58 
 59     @Override
 60     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 61         int leftOffset = 0;
 62         for (int i = 0; i < getChildCount(); i++) {
 63             View child = getChildAt(i);
 64             child.layout(l + leftOffset, t, r + leftOffset, b);
 65             leftOffset += child.getMeasuredWidth();
 66         }
 67     }
 68 
 69     /**
 70      * 不拦截Down事件,其他一律拦截
 71      * @param ev
 72      * @return
 73      */
 74     @Override
 75     public boolean onInterceptTouchEvent(MotionEvent ev) {
 76         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
 77             if (!mScroller.isFinished()) {
 78                 mScroller.abortAnimation();
 79                 return true;
 80             }
 81             return false;
 82         } else {
 83             return true;
 84         }
 85     }
 86 
 87     private boolean isFirstTouch = true;
 88 
 89     @Override
 90     public boolean onTouchEvent(MotionEvent event) {
 91         int x = (int) event.getX();
 92         int y = (int) event.getY();
 93         mVelocityTracker.addMovement(event);
 94         ViewConfiguration configuration = ViewConfiguration.get(getContext());
 95         switch (event.getAction()) {
 96             case MotionEvent.ACTION_DOWN:
 97                 if (!mScroller.isFinished()) {
 98                     mScroller.abortAnimation();
 99                 }
100                 break;
101             case MotionEvent.ACTION_MOVE:
102                 if (isFirstTouch) {
103                     isFirstTouch = false;
104                     lastY = y;
105                     lastX = x;
106                 }
107                 final int deltaX = x - lastX;
108                 scrollBy(-deltaX, 0);
109                 break;
110             case MotionEvent.ACTION_UP:
111                 isFirstTouch = true;
112                 int scrollX = getScrollX();
113                 mVelocityTracker.computeCurrentVelocity(1000, configuration.getScaledMaximumFlingVelocity());
114                 float mVelocityX = mVelocityTracker.getXVelocity();
115                 if (Math.abs(mVelocityX) > configuration.getScaledMinimumFlingVelocity()) {
116                     childIndex = mVelocityX < 0 ? childIndex + 1 : childIndex - 1;
117                 } else {
118                     childIndex = (scrollX + getChildAt(0).getWidth() / 2) / getChildAt(0).getWidth();
119                 }
120                 childIndex = Math.min(getChildCount() - 1, Math.max(0, childIndex));
121                 smoothScrollBy(childIndex*getChildAt(0).getWidth()-scrollX,0);
122                 mVelocityTracker.clear();
123                 break;
124         }
125 
126         lastX = x;
127         lastY = y;
128         return true;
129     }
130 
131     private void smoothScrollBy(int dx, int dy) {
132         mScroller.startScroll(getScrollX(), getScrollY(), dx, dy,500);
133         invalidate();
134     }
135 
136     @Override
137     public void computeScroll() {
138         if(mScroller.computeScrollOffset()){
139             scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
140             postInvalidate();
141         }
142     }
143 
144     @Override
145     protected void onDetachedFromWindow() {
146         super.onDetachedFromWindow();
147         mVelocityTracker.recycle();
148     }
149 }

 

ListViewEx.java

 1 /**
 2  * Created by blueberry on 2016/6/20.
 3  * 内部拦截事件
 4  */
 5 public class ListViewEx extends ListView {
 6 
 7     private int lastXIntercepted, lastYIntercepted;
 8 
 9     private HorizontalEx2 mHorizontalEx2;
10 
11     public ListViewEx(Context context) {
12         super(context);
13     }
14 
15     public ListViewEx(Context context, AttributeSet attrs) {
16         super(context, attrs);
17     }
18 
19     public ListViewEx(Context context, AttributeSet attrs, int defStyleAttr) {
20         super(context, attrs, defStyleAttr);
21     }
22 
23     public HorizontalEx2 getmHorizontalEx2() {
24         return mHorizontalEx2;
25     }
26 
27     public void setmHorizontalEx2(HorizontalEx2 mHorizontalEx2) {
28         this.mHorizontalEx2 = mHorizontalEx2;
29     }
30 
31     /**
32      * 使用 outter.requestDisallowInterceptTouchEvent();
33      * 来决定父控件是否对事件进行拦截
34      * @param ev
35      * @return
36      */
37     @Override
38     public boolean dispatchTouchEvent(MotionEvent ev) {
39         int x = (int) ev.getX();
40         int y = (int) ev.getY();
41         switch (ev.getAction()) {
42             case MotionEvent.ACTION_DOWN:
43                 mHorizontalEx2.requestDisallowInterceptTouchEvent(true);
44                 break;
45             case MotionEvent.ACTION_MOVE:
46                 final int deltaX = x-lastYIntercepted;
47                 final int deltaY = y-lastYIntercepted;
48                 if(Math.abs(deltaX)>Math.abs(deltaY)){
49                     mHorizontalEx2.requestDisallowInterceptTouchEvent(false);
50                 }
51                 break;
52             case MotionEvent.ACTION_UP:
53                 break;
54         }
55         lastXIntercepted = x;
56         lastYIntercepted = y;
57         return super.dispatchTouchEvent(ev);
58     }
59 }

调用代码:

 1 @Override
 2     public void showInnerHVData(List<String> data1, List<String> data2, List<String> data3) {
 3 
 4         ListViewEx listView1 = new ListViewEx(getContext());
 5         ArrayAdapter<String> adapter1 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data1);
 6         listView1.setAdapter(adapter1);
 7         listView1.setmHorizontalEx2(mHorizontalEx2);
 8 
 9         ListViewEx listView2 = new ListViewEx(getContext());
10         ArrayAdapter<String> adapter2 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data2);
11         listView2.setAdapter(adapter2);
12         listView2.setmHorizontalEx2(mHorizontalEx2);
13 
14         ListViewEx listView3 = new ListViewEx(getContext());
15         ArrayAdapter<String> adapter3 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data3);
16         listView3.setAdapter(adapter3);
17         listView3.setmHorizontalEx2(mHorizontalEx2);
18 
19         ViewGroup.LayoutParams params
20                 = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
21                 ViewGroup.LayoutParams.MATCH_PARENT);
22 
23         mHorizontalEx2.addView(listView1, params);
24         mHorizontalEx2.addView(listView2, params);
25         mHorizontalEx2.addView(listView3, params);
26     }

 

至此,2种拦截方法已经学习完毕,下面我们来学习如何解决同向滑动冲突。 
其实和上面的2个例子思路是一样的,只是用来判断是否拦截的那块逻辑不同而已。

下面的例子,是一个下拉刷新的一个控件。

3、外部拦截 解决同向滑动冲突 
RefreshLayoutBase.java

  1 package com.blueberry.sample.widget.refresh;
  2 
  3 import android.content.Context;
  4 import android.graphics.Color;
  5 import android.util.AttributeSet;
  6 import android.util.DisplayMetrics;
  7 import android.util.Log;
  8 import android.util.TypedValue;
  9 import android.view.MotionEvent;
 10 import android.view.View;
 11 import android.view.ViewConfiguration;
 12 import android.view.ViewGroup;
 13 import android.view.WindowManager;
 14 import android.widget.ProgressBar;
 15 import android.widget.Scroller;
 16 import android.widget.TextView;
 17 
 18 import com.blueberry.sample.R;
 19 
 20 /**
 21  * Created by blueberry on 2016/6/21.
 22  *
 23  *外部拦截(同向)
 24  *
 25  */
 26 public abstract class RefreshLayoutBase<T extends View> extends ViewGroup {
 27 
 28     private static final String TAG = "RefreshLayoutBase";
 29 
 30     public static final int STATUS_LOADING = 1;
 31     public static final int STATUS_RELEASE_TO_REFRESH = 2;
 32     public static final int STATUS_PULL_TO_REFRESH = 3;
 33     public static final int STATUS_IDLE = 4;
 34     public static final int STATUS_LOAD_MORE =5;
 35     private static int SCROLL_DURATION =500;
 36 
 37     protected ViewGroup mHeadView;
 38     protected ViewGroup mFootView;
 39     private T contentView;
 40     private ProgressBar headProgressBar;
 41     private TextView headTv;
 42     private ProgressBar footProgressBar;
 43     private TextView footTv;
 44 
 45     private boolean isFistTouch = true;
 46 
 47     protected int currentStatus = STATUS_IDLE;
 48     private int mScreenWidth;
 49     private int mScreenHeight;
 50     private int mLastXIntercepted;
 51     private int mLastYIntercepted;
 52     private int mLastX;
 53     private int mLastY;
 54     protected int mInitScrollY = 0;
 55     private int mTouchSlop;
 56 
 57     protected Scroller mScoller;
 58 
 59     private OnRefreshListener mOnRefreshListener;
 60 
 61     public RefreshLayoutBase(Context context) {
 62         this(context, null);
 63     }
 64 
 65     public RefreshLayoutBase(Context context, AttributeSet attrs) {
 66         this(context, attrs, 0);
 67     }
 68 
 69     public RefreshLayoutBase(Context context, AttributeSet attrs, int defStyleAttr) {
 70         super(context, attrs, defStyleAttr);
 71         getScreenSize();
 72         initView();
 73         mScoller = new Scroller(context);
 74         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
 75         setPadding(0, 0, 0, 0);
 76     }
 77 
 78     public void setContentView(T view) {
 79         addView(view, 1);
 80     }
 81 
 82     public OnRefreshListener getOnRefreshListener() {
 83         return mOnRefreshListener;
 84     }
 85 
 86     public void setOnRefreshListener(OnRefreshListener mOnRefreshListener) {
 87         this.mOnRefreshListener = mOnRefreshListener;
 88     }
 89 
 90     private void initView() {
 91         setupHeadView();
 92         setupFootView();
 93     }
 94 
 95     private void getScreenSize() {
 96         WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
 97         DisplayMetrics metrics = new DisplayMetrics();
 98         wm.getDefaultDisplay().getMetrics(metrics);
 99         mScreenWidth = metrics.widthPixels;
100         mScreenHeight = metrics.heightPixels;
101     }
102 
103     private int dp2px(int dp) {
104         WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
105         DisplayMetrics metrics = new DisplayMetrics();
106         wm.getDefaultDisplay().getMetrics(metrics);
107         return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics);
108     }
109 
110     /**
111      * 设置头布局
112      */
113     private void setupHeadView() {
114         mHeadView = (ViewGroup) View.inflate(getContext(), R.layout.fresh_head_view, null);
115         mHeadView.setBackgroundColor(Color.RED);
116         headProgressBar = (ProgressBar) mHeadView.findViewById(R.id.head_progressbar);
117         headTv = (TextView) mHeadView.findViewById(R.id.head_tv);
118         /*设置 实际高度为 1/4 ,但内容区域只有 100dp*/
119         ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, mScreenHeight / 4);
120         mHeadView.setLayoutParams(layoutParams);
121         mHeadView.setPadding(0, mScreenHeight / 4 - dp2px(100), 0, 0);
122         addView(mHeadView);
123     }
124 
125     /**
126      * 设置尾布局
127      */
128     private void setupFootView() {
129         mFootView = (ViewGroup) View.inflate(getContext(), R.layout.fresh_foot_view, null);
130         mFootView.setBackgroundColor(Color.BLUE);
131         footProgressBar = (ProgressBar) mFootView.findViewById(R.id.fresh_foot_progressbar);
132         footTv = (TextView) mFootView.findViewById(R.id.fresh_foot_tv);
133         addView(mFootView);
134     }
135 
136     @Override
137     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
138         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
139         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
140         int height = MeasureSpec.getSize(heightMeasureSpec);
141         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
142 
143         int finalHeight = 0;
144         for (int i = 0; i < getChildCount(); i++) {
145             View child = getChildAt(i);
146             measureChild(child, widthMeasureSpec, heightMeasureSpec);
147             finalHeight += child.getMeasuredHeight();
148         }
149 
150         if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
151             widthSize = getChildAt(0).getMeasuredWidth();
152             setMeasuredDimension(widthSize, finalHeight);
153         } else if (widthMode == MeasureSpec.AT_MOST) {
154             widthSize = getChildAt(0).getMeasuredWidth();
155             setMeasuredDimension(widthSize, height);
156         } else {
157             setMeasuredDimension(widthSize, finalHeight);
158         }
159 
160     }
161 
162 
163     @Override
164     protected void onLayout(boolean changed, int l, int t, int r, int b) {
165         int topOffset = 0;
166         for (int i = 0; i < getChildCount(); i++) {
167             View child = getChildAt(i);
168             child.layout(getPaddingLeft(), getPaddingTop() + topOffset, r, getPaddingTop() + child.getMeasuredHeight() + topOffset);
169             topOffset += child.getMeasuredHeight();
170         }
171         mInitScrollY = mHeadView.getMeasuredHeight() + getPaddingTop();
172         scrollTo(0, mInitScrollY);
173 
174     }
175 
176     @Override
177     public boolean onInterceptTouchEvent(MotionEvent ev) {
178         boolean intercepted = false;
179         int x = (int) ev.getX();
180         int y = (int) ev.getY();
181         switch (ev.getAction()) {
182             case MotionEvent.ACTION_DOWN:
183                 mLastXIntercepted = x;
184                 mLastYIntercepted = y;
185                 break;
186             case MotionEvent.ACTION_MOVE:
187                 final int deltaY = x - mLastYIntercepted;
188                 if (isTop() && deltaY > 0 && Math.abs(deltaY) > mTouchSlop) {
189                     /*下拉*/
190                     intercepted = true;
191                 }
192                 break;
193             case MotionEvent.ACTION_UP:
194                 break;
195         }
196         mLastXIntercepted = x;
197         mLastYIntercepted = y;
198         return intercepted;
199     }
200 
201     private void doRefresh() {
202         Log.i(TAG, "doRefresh: ");
203         if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
204             mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION);
205             currentStatus = STATUS_IDLE;
206         } else if (currentStatus == STATUS_PULL_TO_REFRESH) {
207             mScoller.startScroll(0,getScrollY(),0,0-getScrollY(),SCROLL_DURATION);
208             if (null != mOnRefreshListener) {
209                 currentStatus = STATUS_LOADING;
210                 mOnRefreshListener.refresh();
211             }
212         }
213         invalidate();
214     }
215 
216     @Override
217     public boolean onTouchEvent(MotionEvent event) {
218         int x = (int) event.getX();
219         int y = (int) event.getY();
220         switch (event.getAction()) {
221             case MotionEvent.ACTION_DOWN:
222                 if (!mScoller.isFinished()) {
223                     mScoller.abortAnimation();
224                 }
225                 mLastX = x;
226                 mLastY = y;
227                 break;
228             case MotionEvent.ACTION_MOVE:
229                 if (isFistTouch) {
230                     isFistTouch = false;
231                     mLastX = x;
232                     mLastY = y;
233                 }
234                 final int deltaY = y - mLastY;
235                 if (currentStatus != STATUS_LOADING) {
236                     changeScrollY(deltaY);
237                 }
238                 break;
239             case MotionEvent.ACTION_UP:
240                 isFistTouch = true;
241                 doRefresh();
242                 break;
243         }
244 
245         mLastX = x;
246         mLastY = y;
247         return true;
248     }
249 
250     private void changeScrollY(int deltaY) {
251         Log.i(TAG, "changeScrollY: ");
252         int curY = getScrollY();
253         if (deltaY > 0) {
254             /*下拉*/
255             if (curY - deltaY > getPaddingTop()) {
256                 scrollBy(0, -deltaY);
257             }
258         } else {
259             /*上拉*/
260             if (curY - deltaY <= mInitScrollY) {
261                 scrollBy(0, -deltaY);
262             }
263         }
264 
265         curY = getScrollY();
266         int slop = mInitScrollY / 2;
267         if (curY > 0 && curY <=slop) {
268             currentStatus = STATUS_PULL_TO_REFRESH;
269         } else if (curY > 0 && curY >= slop) {
270             currentStatus = STATUS_RELEASE_TO_REFRESH;
271         }
272     }
273 
274     @Override
275     public void computeScroll() {
276         if (mScoller.computeScrollOffset()) {
277             scrollTo(mScoller.getCurrX(), mScoller.getCurrY());
278             postInvalidate();
279         }
280     }
281 
282     /**
283      * 加载完成调用这个方法
284      */
285     public void refreshComplete() {
286         mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION);
287         currentStatus = STATUS_IDLE;
288         invalidate();
289     }
290 
291     /**
292      * 显示 Footer
293      */
294     public void showFooter() {
295         if(currentStatus==STATUS_LOAD_MORE) return ;
296         currentStatus = STATUS_LOAD_MORE ;
297         mScoller.startScroll(0, getScrollY(), 0, mFootView.getMeasuredHeight()
298                 , SCROLL_DURATION);
299         invalidate();
300 
301     }
302 
303 
304     /**
305      * loadMore完成之后调用
306      */
307     public void footerComplete() {
308         mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION);
309         invalidate();
310         currentStatus = STATUS_IDLE;
311     }
312 
313     public interface OnRefreshListener {
314         void refresh();
315     }
316 
317     abstract boolean isTop();
318 
319     abstract boolean isBottom();
320 
321 }

 

它是一个抽象类,需要编写子类继承isTop()和 isBottom()方法、 
下面给出它的一个实现类:

 1 package com.blueberry.sample.widget.refresh;
 2 
 3 import android.content.Context;
 4 import android.util.AttributeSet;
 5 import android.widget.AbsListView;
 6 import android.widget.ListView;
 7 
 8 /**
 9  * Created by blueberry on 2016/6/21.
10  *
11  * RefreshLayoutBase 的一个实现类
12  */
13 public class RefreshListView extends RefreshLayoutBase<ListView> {
14 
15     private static final String TAG = "RefreshListView";
16 
17     private ListView listView;
18     private onl oadListener loadListener;
19 
20     public RefreshListView(Context context) {
21         super(context);
22     }
23 
24     public RefreshListView(Context context, AttributeSet attrs) {
25         super(context, attrs);
26     }
27 
28     public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
29         super(context, attrs, defStyleAttr);
30     }
31 
32     public ListView getListView() {
33         return listView;
34     }
35 
36     public void setListView(final ListView listView) {
37         this.listView = listView;
38         setContentView(listView);
39 
40         this.listView.setOnScrollListener(new AbsListView.OnScrollListener() {
41             @Override
42             public void onScrollStateChanged(AbsListView view, int scrollState) {
43             }
44 
45             @Override
46             public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
47 
48                 /*这里存在一个bug: 当listView滑动到底部的时候,如果下拉也会出现footer
49                 * 这是因为,暂时还没有想到如何判断是下拉还是上拉。
50                 * 如果要解决此问题,我觉得应该重写listView 的onTouchEvent来判断手势方向
51                 * 次模块主要解决竖向滑动冲突,故现将此问题放下。
52                 * */
53                 if (currentStatus == STATUS_IDLE
54                         && getScrollY() <= mInitScrollY && isBottom()
55                         ) {
56                     showFooter();
57                     if (null != loadListener) {
58                         loadListener.onLoadMore();
59                     }
60                 }
61 
62             }
63         });
64     }
65 
66     public onl oadListener getLoadListener() {
67         return loadListener;
68     }
69 
70     public void setLoadListener(OnLoadListener loadListener) {
71         this.loadListener = loadListener;
72     }
73 
74     @Override
75     boolean isTop() {
76         return listView.getFirstVisiblePosition() == 0
77                 && getScrollY() <= mHeadView.getMeasuredHeight();
78     }
79 
80     @Override
81     boolean isBottom() {
82         return listView.getLastVisiblePosition() == listView.getAdapter().getCount() - 1;
83     }
84 
85     public interface onl oadListener {
86         void onl oadMore();
87     }
88 }

 

4、内部拦截法解决同向滑动 
同样是一个下拉刷新组件,因为实现原理都一样,所以这个写的比较随意些。主要还是如果解决滑动冲突。 
RefreshLayoutBase2.java

  1 package com.blueberry.sample.widget.refresh;
  2 
  3 import android.content.Context;
  4 import android.graphics.Color;
  5 import android.util.AttributeSet;
  6 import android.util.Log;
  7 import android.view.MotionEvent;
  8 import android.view.View;
  9 import android.view.ViewGroup;
 10 import android.widget.ArrayAdapter;
 11 import android.widget.ListView;
 12 import android.widget.Scroller;
 13 
 14 import com.blueberry.sample.R;
 15 
 16 import java.util.ArrayList;
 17 import java.util.List;
 18 
 19 /**
 20  * Created by blueberry on 2016/6/22.
 21  * 结合内部类 ListVieEx
 22  * 内部拦截法,同向
 23  */
 24 public class RefreshLayoutBase2 extends ViewGroup {
 25 
 26     private static final String TAG = "RefreshLayoutBase2";
 27 
 28     private static List<String> datas;
 29 
 30     static {
 31         datas = new ArrayList<>();
 32         for (int i = 0; i < 40; i++) {
 33             datas.add("数据—" + i);
 34         }
 35     }
 36 
 37     private ViewGroup headView;
 38     private ListViewEx lv;
 39 
 40     private int lastY;
 41     public int mInitScrollY;
 42 
 43     private Scroller mScroller;
 44 
 45     public RefreshLayoutBase2(Context context) {
 46         this(context, null);
 47     }
 48 
 49     public RefreshLayoutBase2(Context context, AttributeSet attrs) {
 50         this(context, attrs, 0);
 51 
 52     }
 53 
 54     public RefreshLayoutBase2(Context context, AttributeSet attrs, int defStyleAttr) {
 55         super(context, attrs, defStyleAttr);
 56         mScroller = new Scroller(context);
 57         setupHeadView(context);
 58         setupContentView(context);
 59 
 60     }
 61 
 62     @Override
 63     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 64         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
 65         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
 66         int height = MeasureSpec.getSize(heightMeasureSpec);
 67         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
 68 
 69         int finalHeight = 0;
 70         for (int i = 0; i < getChildCount(); i++) {
 71             View child = getChildAt(i);
 72             measureChild(child, widthMeasureSpec, heightMeasureSpec);
 73             finalHeight += child.getMeasuredHeight();
 74         }
 75 
 76         if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
 77             widthSize = getChildAt(0).getMeasuredWidth();
 78             setMeasuredDimension(widthSize, finalHeight);
 79         } else if (widthMode == MeasureSpec.AT_MOST) {
 80             widthSize = getChildAt(0).getMeasuredWidth();
 81             setMeasuredDimension(widthSize, height);
 82         } else {
 83             setMeasuredDimension(widthSize, finalHeight);
 84         }
 85 
 86     }
 87 
 88     @Override
 89     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 90         int topOffset = 0;
 91         for (int i = 0; i < getChildCount(); i++) {
 92             View child = getChildAt(i);
 93             child.layout(getPaddingLeft(), getPaddingTop() + topOffset, r, getPaddingTop() + child.getMeasuredHeight() + topOffset);
 94             topOffset += child.getMeasuredHeight();
 95         }
 96         mInitScrollY = headView.getMeasuredHeight() + getPaddingTop();
 97         scrollTo(0, mInitScrollY);
 98 
 99     }
100 
101     /**
102      * 不拦截Down 其他一律拦截
103      * @param ev
104      * @return
105      */
106     @Override
107     public boolean onInterceptTouchEvent(MotionEvent ev) {
108         if (ev.getAction() == MotionEvent.ACTION_DOWN) return false;
109         return true;
110     }
111 
112     @Override
113     public boolean onTouchEvent(MotionEvent event) {
114         int y = (int) event.getY();
115         switch (event.getAction()) {
116             case MotionEvent.ACTION_DOWN:
117                 break;
118             case MotionEvent.ACTION_MOVE:
119                 final int deltaY = y-lastY;
120                 Log.i(TAG, "onTouchEvent: deltaY: "+deltaY);
121                 if (deltaY >= 0 && lv.isTop() && getScrollY() - deltaY >=getPaddingTop()) {
122                         scrollBy(0, -deltaY);
123                 }
124                 break;
125             case MotionEvent.ACTION_UP:
126                 this.postDelayed(new Runnable() {
127                     @Override
128                     public void run() {
129                         mScroller.startScroll(0,getScrollY(),0,mInitScrollY-getScrollY());
130                         invalidate();
131                     }
132                 },2000);
133                 break;
134         }
135 
136         lastY = y ;
137         return true;
138     }
139 
140     private void setupHeadView(Context context) {
141         headView = (ViewGroup) View.inflate(context, R.layout.fresh_head_view, null);
142         headView.setBackgroundColor(Color.RED);
143         ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300);
144         addView(headView, params);
145     }
146 
147     public void setupContentView(Context context) {
148         lv = new ListViewEx(context, this);
149         lv.setBackgroundColor(Color.BLUE);
150         ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, datas);
151         lv.setAdapter(adapter);
152         addView(lv, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
153     }
154 
155     @Override
156     public void computeScroll() {
157         if(mScroller.computeScrollOffset()){
158             scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
159             postInvalidate();
160         }
161     }
162 
163     public static class ListViewEx extends ListView {
164 
165         private RefreshLayoutBase2 outter;
166 
167         public ListViewEx(Context context, RefreshLayoutBase2 outter) {
168             super(context);
169             this.outter = outter;
170         }
171 
172         public ListViewEx(Context context, AttributeSet attrs) {
173             super(context, attrs);
174         }
175 
176         public ListViewEx(Context context, AttributeSet attrs, int defStyleAttr) {
177             super(context, attrs, defStyleAttr);
178         }
179 
180         /**
181          * 使用 outter.requestDisallowInterceptTouchEvent();
182          * 来决定父控件是否对事件进行拦截
183          * @param ev
184          * @return
185          */
186         @Override
187         public boolean dispatchTouchEvent(MotionEvent ev) {
188             switch (ev.getAction()) {
189                 case MotionEvent.ACTION_DOWN:
190                     outter.requestDisallowInterceptTouchEvent(true);
191                     break;
192                 case MotionEvent.ACTION_MOVE:
193 
194                     if ( isTop() && outter.getScrollY() <= outter.mInitScrollY) {
195                         outter.requestDisallowInterceptTouchEvent(false);
196                     }
197                     break;
198 
199             }
200             return super.dispatchTouchEvent(ev);
201         }
202 
203         public boolean isTop() {
204             return getFirstVisiblePosition() ==0;
205         }
206     }
207 }

 



Android——滑动事件冲突解决