图-1 布局图
加入了ScrollView和SwipeRefreshLayout之后引入了新的问题,就是各个控件之间的事件冲突,嵌套在ScrollView中的ViewPager、MapView、ListView都需要能够正确的处理点击事件,特别是ListView,需求要求它在ScrollView中可以滑动,两种滑动混淆在一起,不是特别好处理。
问题提出来了,下面直接看解决思路。
3. 解决滑动冲突的思路
在ViewGroup中有个方法叫requestDisallowInterceptTouchEvent(boolean disallowIntercept),这个方法可以用来控制该ViewGroup是否截断点击事件。我们解决滑动冲突的时候,其实就是在某个时机去调用这个方法,让父布局不截断点击事件,将点击事件传递到子View,让相关的子View去处理。
下面就是关于在项目中处理各种点击事件冲突的一些例子和思考。处理的方法只是提供一种思路,可能并不是最优的方法,肯定存在其他思路的解决方案。
以下处理滑动冲突的方案都是在子View的OnTouchListener里面进行处理,并没有去复写控件的点击事件处理过程,在使用中还是比较方便的。
3.1 MapView地图页面滑动冲突
MapView与ScrollView的冲突主要在于,当用户点击到MapView地图并且滑动的时候,希望由地图Map去处理点击事件,并包括后续的滑动事件、双手指缩放地图等等。
在ScrollView中,是会默认截断点击事件的,导致用户点击到地图后,地图基本是没有反应,更别谈双手指缩放地图了。
用户手指点击到地图,并且滑动的时候,很难确定用户是要ScrollView上下滑动还是操控地图内容滑动,所以我简单的认为,只要用户手指点击到地图,就是要对地图进行操作;当用户手指抬起,就认为用户不需要操作地图了。
解决思路也很简单,就是在用户点击到地图或者滑动地图时候,让ScrollView不截断点击事件,并传递给子View处理,也就是地图去处理点击事件;当用户手指抬起的时候,将ScrollView的状态恢复至之前的状态,也就是ScrollView可以截断点击事件。
我使用的是百度地图,直接上代码,更容易理解。
mMapView.getChildAt(0).setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {if(event.getAction() == MotionEvent.ACTION_UP){//允许ScrollView截断点击事件,ScrollView可滑动mScrollView.requestDisallowInterceptTouchEvent(false);}else{//不允许ScrollView截断点击事件,点击事件由子View处理mScrollView.requestDisallowInterceptTouchEvent(true);}return false;}});3.2 ViewPager滑动冲突解决
mViewPager.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {int action = event.getAction();if(action == MotionEvent.ACTION_DOWN) {// 记录点击到ViewPager时候,手指的X坐标mLastX = event.getX();}if(action == MotionEvent.ACTION_MOVE) {// 超过阈值if(Math.abs(event.getX() - mLastX) > 60f) {mRefreshLayout.setEnabled(false);mScrollView.requestDisallowInterceptTouchEvent(true);}}if(action == MotionEvent.ACTION_UP) {// 用户抬起手指,恢复父布局状态mScrollView.requestDisallowInterceptTouchEvent(false);mRefreshLayout.setEnabled(true);}return false;}});用户点击到ViewPager时候,记录下点击位置的X坐标,当用户滑动过程中,如果在X轴上面的滑动超过阈值(我写的是60f,这个可以在实际使用中自行设置最佳的阈值),就禁止ScrollView截断点击事件,同时设置不可下拉刷新。当用户手指离开屏幕,将ScrollView和SwipeRefreshLayout的状态恢复。
@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt){super.onScrollChanged(l,t,oldl,oldt);// 滑动的距离加上本身的高度与子View的高度对比if(t + getHeight() >= getChildAt(0).getMeasuredHeight()){// ScrollView滑动到底部if(mOnScrollToBottomListener != null) {mOnScrollToBottomListener.onScrollToBottom();}} else {if(mOnScrollToBottomListener != null) {mOnScrollToBottomListener.onNotScrollToBottom();}}}public void setScrollToBottomListener(OnScrollToBottomListener listener) {this.mOnScrollToBottomListener = listener;}public interface OnScrollToBottomListener {void onScrollToBottom();void onNotScrollToBottom();}有了思路,而且ScrollView滑动到底部的标识也可以拿到,下面就可以直接来解决滑动冲突了,直接看代码。
mScrollView.setScrollToBottomListener(new BottomScrollView.OnScrollToBottomListener() {@Overridepublic void onScrollToBottom() {isSvToBottom = true;}@Overridepublic void onNotScrollToBottom() {isSvToBottom = false;}});mListView.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {int action = event.getAction();if(action == MotionEvent.ACTION_DOWN) {mLastY = event.getY();}if(action == MotionEvent.ACTION_MOVE) {int top = mListView.getChildAt(0).getTop();float nowY = event.getY();if(!isSvToBottom) {// 允许scrollview拦截点击事件, scrollView滑动mScrollView.requestDisallowInterceptTouchEvent(false);} else if(top == 0 && nowY - mLastY > THRESHOLD_Y_LIST_VIEW) {// 允许scrollview拦截点击事件, scrollView滑动mScrollView.requestDisallowInterceptTouchEvent(false);} else {// 不允许scrollview拦截点击事件, listView滑动mScrollView.requestDisallowInterceptTouchEvent(true);}}return false;}});相对于其他的控件来说,ListView和ScrollView之间的滑动冲突更难解决,但其实在实际使用中并不推荐ScrollView里面嵌套ListView,一旦业务复杂,很容易出现各种UI和业务逻辑冲突的错误。
图-2 运行效果
5. 总结
本篇文章只是提供一种解决方法的思路,在具体的场景下,交互往往是贴合具体业务需求的。但是不管怎么样,找出点击事件截断和处理的时机是最重要的,围绕这个关键点,总能找出相应的解决方法。
附上Demo工程地址:Demo
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。