android滑动冲突解决方案
来源:互联网 发布:公司网络优化方案 编辑:程序博客网 时间:2024/05/19 11:47
转载请注明出处:http://blog.csdn.net/u012732170/article/details/54897422
2017年大家新年好,这还是我第一次在csdn上写博客,主要是自己最近正好在看这方面的知识,一作为存根之用,二也是希望对大家有所帮助,如讲解有不妥之处,欢迎大家指出。废话不多说了,进入今天的主题----Android滑动冲突。
在讲滑动冲突前,有一点是必须要提的,就是View的事件分发机制。View的事件分发机制可以看任玉刚大神写的《android开发艺术探索》,里面关于这方面讲解的十分透彻。下面我简单讲一下这方面的知识。
所谓事件分发,其实就是对View中MotionEvent事件的分发,当一个MotionEvent事件产生时,系统需要把这个事件传递给一个具体的View,这个传递的过程就是分发过程。点击事件的分发过程由三个方法来共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。其中dispatchTouchEvent用于传递事件;onInterceptTouchEvent用来拦截事件(只有ViewGroup有,View不存在此方法),如果返回true,即代表拦截事件,在同一事件序列中此方法不会被再次调用;onTouchEvent用来处理事件,返回true代表消耗此次事件,返回false表示不消耗,并且在同一事件序列中将不再接收到事件。
事件分发的规则:首先我们手指触碰屏幕产生的点击事件先传递给根ViewGroup,此时根ViewGroup的dispatchTouchEvent方法将执行,如果此ViewGroup想拦截该事件,那么此ViewGroup的onInterceptTouchEvent将返回true(ViewGroup的onInterceptTouchEvent默认返回都是false,不拦截),该点击事件交由ViewGroup处理,然后此ViewGroup的onTouchEvent方法将被执行,如果根ViewGroup不拦截事件,那么此事件将传递给ViewGroup的子元素,并且子元素的dispatchTouchEvent方法会被调用,如果子元素也是个ViewGroup,那么子元素也可以决定是否拦截该事件,如果是一个View,那么接下来View的onTouchEvent方法将被执行,如果返回false,即不消耗事件,那么事件将返回给根ViewGroup的onTouchEvent执行,如果返回true,那么事件到此就交由该View完成了。(其实也很好理解,boss将任务交给手下,手下能完成最好,完不成还得boss出马来解决)
理解了事件分发机制,那么对于滑动冲突也很好理解了。滑动冲突分为三种情况:
(1)不同方向冲突,比如HorizontalScrollView内嵌ListView
(2)同方向冲突,比如纵向ScrollView嵌套ListView,两个滑动方向相同
(3)包含1、2中情况的冲突。
解决方案其实就是基于事件分发机制的。
(1)对于第一种情况,一个是横向滑动,一个是纵向滑动,那么我们就让它在横向滑动时将事件交由HorizontalScrollView全权处理,在纵向滑动时将事件交由ListView全权处理,也就是说,当横向滑动时,HorizontalScrollView将拦截事件,此时产生的点击事件就由HorizontalScrollView的onTouchEvent来完成;当纵向滑动时,HorizontalScrollView不拦截事件,此时产生的点击事件就由ListView的onTouchEvent来完成。
(2)对于第二种情况,解决方案也是类似,视具体的业务需求来解决,比如ListView上头还有一个View,当滑动距离在那个View的高度之间时,ScrollView拦截事件,由ScrollView处理上下滑动的事件,当滑动距离到达ListView时,就不拦截事件,让ListView继续处理上下滑动事件,当然,这只是其中的一个例子。
(3)对于第三种情况也是类似的,就是要考虑是否拦截的判定条件变得更为复杂,我下面要讲的例子就涉及了这方面的内容。
既然知道了滑动冲突的解决思路,那么该如何解决滑动冲突呢,有2种方法:(1)外部拦截法,顾名思义,就是从父元素着手来拦截,具体实现就是在父元素的onInterceptTouchEvent方法中根据解决思路来拦截事件(2)内部拦截法,类似的,就是从子元素着手,主动告诉父元素是否要拦截,具体实现就是在子元素的dispatchTouchEvent方法中使用
getParent().requestDisallowInterceptTouchEvent(false);false的意思就是告诉父元素此时需要拦截事件了,true就是不让父元素拦截,至于getParent()方法,视你的布局而定,如果只有一个ViewGroup和一个View(或者是Viewgroup),那么用一个getParent()就可以了,如果有两个ViewGroup,那么对于最底层的那个View(或者ViewGroup)就要使用两次getParent(),即
getParent().getParent().requestDisallowInterceptTouchEvent(false);
下面上今天的实例,需求:
(1)根布局为自定义的ScrollView,模仿ViewPager的功能(至于为什么不用ViewPager,主要是ViewPager中已经处理了和Listview上下冲突的问题)
(2)ScrollView中有3个fragment
(3)第一个fragment包含一个Listview,第二个fragment包含一个自定义ScrollView(也是模仿ViewPager的功能)
这样整个布局既包含了不同方向滑动冲突,也包含了同方向滑动冲突。
先看主布局文件,自定义ScrollView中包含三个fragment
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.bellnet.slidingconflict.CustomHorizontalScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/fragment1" android:name="com.bellnet.slidingconflict.Fragment1" android:layout_width="match_parent" android:layout_height="match_parent"> </fragment> <fragment android:id="@+id/fragment2" android:name="com.bellnet.slidingconflict.Fragment2" android:layout_width="match_parent" android:layout_height="match_parent"> </fragment> <fragment android:id="@+id/fragment3" android:name="com.bellnet.slidingconflict.Fragment3" android:layout_width="match_parent" android:layout_height="match_parent"> </fragment> </com.bellnet.slidingconflict.CustomHorizontalScrollView></LinearLayout>
下面看这个ScrollView如何来实现,采用的是外部拦截法
/** * Created by bellnett on 17/2/5. */public class CustomHorizontalScrollView extends ViewGroup { private Scroller mSroller;//用于完成滚动的实例 private int mTouchSlop;//判定为拖动的最小移动像素数 private int leftBorder;//界面可滚动的左边界 private int rightBorder;//界面可滚动的右边界 private float mFirstX = 0;//第一次触碰屏幕的x坐标 private float mFirstY = 0;//第一次触碰屏幕的y坐标 private float mLastX = 0;//滑动屏幕时的x坐标 private float mLastXIntercept = 0;//用于onInterceptTouchEvent中滑动屏幕的x坐标 private float mLastYIntercept = 0;//用于onInterceptTouchEvent中滑动屏幕的y坐标 public CustomHorizontalScrollView(Context context, AttributeSet attrs) { super(context, attrs); /* 创建scroller的实例 */ mSroller = new Scroller(context); /* 获取判定滑动的最小移动像素数 */ ViewConfiguration viewConfiguration = ViewConfiguration.get(context); mTouchSlop = viewConfiguration.getScaledTouchSlop(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View view = getChildAt(i); measureChild(view, widthMeasureSpec, heightMeasureSpec); } } @Override protected void onLayout(boolean b, int i, int i1, int i2, int i3) { if (b) { int childCount = getChildCount(); for (int j = 0; j < childCount; j++) { View view = getChildAt(j); view.layout(j * view.getMeasuredWidth(), 0, (j + 1) * view.getMeasuredWidth(), view .getMeasuredHeight()); } leftBorder = getChildAt(0).getLeft(); rightBorder = getChildAt(childCount - 1).getRight(); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { System.out.println("ev------->" + ev.getAction()); Boolean intercept = false; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mFirstX = ev.getRawX(); mFirstY = ev.getRawY(); mLastX = ev.getRawX(); break; case MotionEvent.ACTION_MOVE: mLastXIntercept = ev.getRawX(); mLastYIntercept = ev.getRawY(); float delX = Math.abs(mLastXIntercept - mFirstX); float dexY = Math.abs(mLastYIntercept - mFirstY); mFirstX = mLastXIntercept; if (getScrollX() + leftBorder < getWidth()) {//此时为第一个fragment的页面 if (delX > dexY) { intercept = true;//x滑动距离大于y时,此时拦截子控件的事件 } else { intercept = false; } } else if (getScrollX() + leftBorder > getWidth() && getScrollX() + leftBorder < 2 * getWidth()) {//此时为第二个fragment的页面 intercept = false; } else {//此时是第三个fragment的页面 if (delX > dexY) { intercept = true;//x滑动距离大于y时,此时拦截子控件的事件 } else { intercept = false; } } /* if (delX < mTouchSlop) {//如果滑动的最小距离小于toushSlop,那么不认为滑动,不拦截子控件的事件 intercept = false; } else { intercept = true; } */ break; } return intercept; } @Override public boolean onTouchEvent(MotionEvent event) { System.out.println("event------->" + event.getAction()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: return true; case MotionEvent.ACTION_MOVE: mLastX = event.getRawX(); int delX = (int) (mFirstX - mLastX); if (getScrollX() + delX < leftBorder) { scrollTo(leftBorder, 0); return true; } else if (getScrollX() + getWidth() + delX > rightBorder) { scrollTo(rightBorder - getWidth(), 0); return true; } scrollBy(delX, 0); mFirstX = mLastX; break; case MotionEvent.ACTION_UP: int index = (getScrollX() + getWidth() / 2) / getWidth(); int dx = index * getWidth() - getScrollX(); mSroller.startScroll(getScrollX(), 0, dx, 0); invalidate(); break; } return super.onTouchEvent(event); } @Override public void computeScroll() { if (mSroller.computeScrollOffset()) { scrollTo(mSroller.getCurrX(), mSroller.getCurrY()); invalidate(); } }}
主要就是在onInterceptTouchEvent进行判断,当滑动的目标为第一个fragment时,此时fragment中只包含一个ListView,那么根据x、y滑动距离的比较来判定是上下滑动还是左右滑动;当滑动的目标为第二个fragment时,由于第二个fragment也包含一个类似ViewPager的ScrollView,因此就让子元素自己处理事件,父元素不拦截,至于第三个fragment,就只有一个TextView,因此也可以根据x、y滑动的比较来判定拦截条件。至于如何实现类似ViewPager的ScrollView,我就不详解了,自行百度或参照上面代码所写。
下面上子元素的ScrollView的实现,采用的是内部拦截法,主要是外部拦截法我不知道怎么来判定这个子元素的宽度,由于用了fragment,每次获取到的宽度就是手机的屏幕宽,因此从内部着手来判定宽度,方便告诉父元素何时拦截
/** * Created by bellnett on 17/2/6. */public class CustomBroadcastScrollView extends ViewGroup { private float mFirstX = 0;//第一次触碰屏幕的x坐标 private float mLastX = 0;//滑动屏幕时的x坐标 private float mDispatchX = 0;//在dispatchTouchEvent中滑动屏幕时的x坐标 private int leftBorder;//界面可滚动的左边界 private int rightBorder;//界面可滚动的右边界 private Scroller mScroller;//用于完成滚动的实例 public CustomBroadcastScrollView(Context context, AttributeSet attrs) { super(context, attrs); mScroller = new Scroller(context); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { System.out.println("ev2------->" + ev.getAction()); getParent().getParent().requestDisallowInterceptTouchEvent(true); switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: mFirstX = ev.getX(); break; case MotionEvent.ACTION_MOVE: mDispatchX = ev.getX(); int delX = (int) (mFirstX - mDispatchX); if(getScrollX() + delX < leftBorder){//此时为滑动的第一张图片 getParent().getParent().requestDisallowInterceptTouchEvent(false); }else if(getScrollX() + getWidth() + delX > rightBorder){//此时已经滑动到最后一张图片 getParent().getParent().requestDisallowInterceptTouchEvent(false); } break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { System.out.println("event2------->" + event.getAction()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: return true; case MotionEvent.ACTION_MOVE: mLastX = event.getX(); int delX = (int) (mFirstX - mLastX); if(getScrollX() + delX < leftBorder){ scrollTo(leftBorder,0); return true; }else if(getScrollX() + getWidth() + delX > rightBorder){ scrollTo(rightBorder - getWidth(),0); return true; } scrollBy(delX,0); mFirstX = mLastX; break; case MotionEvent.ACTION_UP: int index = (getScrollX() + getWidth() / 2) / getWidth(); int dx = index * getWidth() - getScrollX(); mScroller.startScroll(getScrollX(),0,dx,0); invalidate(); break; } return super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View view = getChildAt(i); measureChild(view, widthMeasureSpec, heightMeasureSpec); } } @Override protected void onLayout(boolean b, int i, int i1, int i2, int i3) { if (b) { int childCount = getChildCount(); for (int j = 0; j < childCount; j++) { View view = getChildAt(j); view.layout(j * view.getMeasuredWidth(), 0, (j + 1) * view.getMeasuredWidth (), view.getMeasuredHeight()); } leftBorder = getChildAt(0).getLeft(); rightBorder = getChildAt(childCount - 1).getRight(); } } @Override public void computeScroll() { if(mScroller.computeScrollOffset()){ scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); invalidate(); } }}
其子元素就是在dispatchTouchEvent中处理拦截事件,当滑动为第一张图片时,此时如果手指往右滑动,那么就告诉父元素拦截事件,父元素将从第二个fragment滑动到第一个fragment;当滑动为最后一张图片时,此时如果手指往左滑动,那么久告诉父元素拦截事件,父元素将从第二个fragment滑动到第三个fragment,这就是两种边界情况时的判定,其余情况就告诉父元素不要拦截事件,让子元素自己处理滑动事件。(这个视具体的业务需求来定,我这里举的例子是根据第一张和最后一张图片来判定)
第一个fragment布局
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="这是第一个fragment" android:textSize="20sp"/> <ListView android:layout_width="match_parent" android:layout_height="0dp" android:id="@+id/oneListView" android:dividerHeight="2dp" android:layout_weight="1"> </ListView></LinearLayout>
第二个fragment布局
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="这是第二个fragment" android:textSize="20sp"/> <com.bellnet.slidingconflict.CustomBroadcastScrollView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/a"/> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/b"/> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/c"/> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/d"/> </com.bellnet.slidingconflict.CustomBroadcastScrollView></LinearLayout>
第三个就不粘了,只有一个TextView
第一个fragment实现
/** * Created by bellnett on 17/2/5. */public class Fragment1 extends Fragment { private ListView listView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_one, container, false); listView = (ListView) view.findViewById(R.id.oneListView); return view; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); String[] a = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20","21","22","23","24","25","26","27"}; ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout .simple_list_item_1, a); listView.setAdapter(adapter); }}
第二个fragment实现
/** * Created by bellnett on 17/2/5. */public class Fragment2 extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_two, container, false); return view; }}
同样的,第三个fragment的实现和第二个fragment完全一样,只需要改个布局文件就可以了。到此所有的代码都粘贴出来了,如有不妥或者更好的处理方式,欢迎大家指出,下面看看具体的效果。由于上传图片有大小限制,我拆成了两张图。
- Android-滑动冲突解决方案
- Android滑动冲突解决方案
- android滑动冲突解决方案
- Android滑动冲突解决方案
- Android双向滑动冲突解决方案
- android滑动冲突的解决方案
- android滑动冲突的解决方案
- Android --- ListView 与 子项中滑动冲突解决方案
- android双向滑动冲突处理及解决方案
- Android中滑动冲突的解决方案
- android View滑动冲突的终极解决方案
- 滑动冲突的解决方案
- Android 开发中scrollview嵌套webview滑动冲突的解决方案
- Android scrollview嵌套webview滑动冲突的解决方案
- Android开发——View滑动冲突解决方案
- Android View的事件分发机制和滑动冲突解决方案
- Android Viewpager与WebView轮播滑动冲突的解决方案
- Android 开发中scrollview嵌套webview滑动冲突的解决方案
- AR相关资源整理
- svn使用
- 无线ap与无线路由器的区别
- html5常用语义化标签
- 网卡工作原理
- android滑动冲突解决方案
- 分治法学习记录
- 转载:python encode和decode函数说明
- 网络编程定时器三:使用最小堆
- c++基础(二)
- 20170206学习日记02:输入输出流,定义接口调用接口,各式类,实现接口各种方式
- maven--继承和聚合
- C++ Volatile
- 月亮与六便士 - 笔记