浅谈Android View滑动冲突的解决方法

引言

这一篇文章我们就通过介绍滑动冲突的规则和一个实例来更加深入的学习View的事件分发机制。

1、外部滑动方向和内部滑动方向不一致

考虑这样一种场景,开发中我们经常使用ViewPager和Fragment配合使用所组成的页面滑动效果,很多主流的应用都会使用这样的效果。在这种效果中,可以使用左右滑动来切换界面,而每一个界面里面往往又都是ListView这样的控件。本来这种情况是存在滑动冲突的,只是ViewPager内部处理了这种滑动冲突。如果我们不使用ViewPager而是使用ScrollView,那么滑动冲突就需要我们自己来处理,否者造成的后果就是内外两层只有一层能滑动。

情况1的解决思路

对于第一种情况的解决思路是这样的:当用户左右滑动时,需要让外层的View拦截点击事件。当用户上下滑动时,需要让内部的View拦截点击事件(外层的View不拦截点击事件),这时候我们就可以根据它们的特性来解决滑动冲突。在这里我们可以根据滑动时水平滑动还是垂直滑动来判断谁来拦截点击事件。下面先介绍一种通用的解决滑动冲突的方法。

外部拦截法

外部拦截法是指:点击事件都经过父容器的拦截处理,如果父容器需要处理此事件就进行拦截,否者不拦截交给子View进行处理。这种方法比较符合点击事件的分发机制。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可。这种方法的伪代码如下:

@Override public boolean onInterceptTouchEvent(MotionEvent ev) { int x=(int)ev.getX(); int y=(int)ev.getY(); boolean intercept=false; switch (ev.getAction()){ //按下事件不要拦截,否则后续事件都会给ViewGroup处理 case MotionEvent.ACTION_DOWN: intercept=false; break; case MotionEvent.ACTION_MOVE: //如果是横向移动就进行拦截,否则不拦截 int deltaX=x-mLastX; int deltaY=y-mLastY; if(父容器需要当前点击事件){ intercept=true; }else { intercept=false; } break; case MotionEvent.ACTION_UP: intercept=false; break; } mLastX = x; mLastY = y; return intercept; }

上面代码是外部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改父容器需要当前点击事件的条件即可,其他均不需要修改。我们在描述下:在onInterceptTouchEvent方法中,首先是ACTION_DOWN事件,父容器必须返回false,即不拦截ACTION_DOWN事件,这是因为一旦父容器拦截ACTION_DOWN,那么后续的ACTION_MOVE和ACTION_UP都会直接交给父容器处理,这时候事件就没法传递给子元素了;其次是ACTION_MOVE事件,这个事件可以根据需要来决定是否需要拦截。

下面来看一个具体的实例,这个实现模拟ViewPager的效果,我们定义一个全新的控件,名称叫HorizontalScrollView。具体代码如下:

1、我们先看Activity中的代码:

public class MainActivity extends Activity{ private HorizontalScrollView mListContainer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } private void initView() { LayoutInflater inflater = getLayoutInflater(); mListContainer = (HorizontalScrollView) findViewById(R.id.container); final int screenWidth = MyUtils.getScreenMetrics(this).widthPixels; for (int i = 0; i < 3; i++) { ViewGroup layout = (ViewGroup) inflater.inflate( R.layout.content_layout, mListContainer, false); layout.getLayoutParams().width = screenWidth; TextView textView = (TextView) layout.findViewById(R.id.title); textView.setText("page " + (i + 1)); layout.setBackgroundColor(Color.rgb(255 / (i + 1), 255 / (i + 1), 0)); createList(layout); mListContainer.addView(layout); } } private void createList(ViewGroup layout) { ListView listView = (ListView) layout.findViewById(R.id.list); ArrayList<String> datas = new ArrayList<>(); for (int i = 0; i < 50; i++) { datas.add("name " + i); } ArrayAdapter<String> adapter = new ArrayAdapter<>(this, R.layout.content_list_item, R.id.name, datas); listView.setAdapter(adapter); } }

在这个代码中,我们创建了3个ListView然后将其添加到我们自定义控件的。这里HorizontalScrollView是父容器,ListView是子View。下面我们就使用外部拦截法来实现HorizontalScrollView,代码如下:

/** * 横向布局控件 * 模拟经典滑动冲突 * 我们此处使用ScrollView来模拟ViewPager,那么必须手动处理滑动冲突,否则内外两层只能有一层滑动,那就是滑动冲突。另外内部左右滑动,外部上下滑动也同样属于该类 */ public class HorizontalScrollView extends ViewGroup { //记录上次滑动的坐标 private int mLastX = 0; private int mLastY = 0; private WindowManager wm; //子View的个数 private int mChildCount; private int mScreenWidth; //自定义控件横向宽度 private int mMeasureWidth; //滑动加载下一个界面的阈值 private int mCrital; //滑动辅助类 private Scroller mScroller; //当前展示的子View的索引 private int showViewIndex; public HorizontalScrollView(Context context){ this(context,null); } public HorizontalScrollView(Context context, AttributeSet attributeSet){ super(context,attributeSet); init(context); } /** * 初始化 * @param context */ public void init(Context context) { //读取屏幕相关的长宽 wm = ((Activity)context).getWindowManager(); mScreenWidth = wm.getDefaultDisplay().getWidth(); mCrital=mScreenWidth/4; mScroller=new Scroller(context); showViewIndex=1; } /** * 重新事件拦截机制 * 我们分析了view的事件分发,我们知道点击事件的分发顺序是 通过父布局分发,如果父布局没有拦截,即onInterceptTouchEvent返回false, * 才会传递给子View。所以我们就可以利用onInterceptTouchEvent()这个方法来进行事件的拦截。来看一下代码 * 此处使用外部拦截法 * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int x=(int)ev.getX(); int y=(int)ev.getY(); boolean intercept=false; switch (ev.getAction()){ //按下事件不要拦截,否则后续事件都会给ViewGroup处理 case MotionEvent.ACTION_DOWN: intercept=false; if(!mScroller.isFinished()){ mScroller.abortAnimation(); intercept=true; } break; case MotionEvent.ACTION_MOVE: //如果是横向移动就进行拦截,否则不拦截 int deltaX=x-mLastX; int deltaY=y-mLastY; if(Math.abs(deltaX)>Math.abs(deltaY)){ intercept=true; }else { intercept=false; } break; case MotionEvent.ACTION_UP: intercept=false; break; } mLastX = x; mLastY = y; return intercept; } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if(!mScroller.isFinished()){ mScroller.abortAnimation(); } break; case MotionEvent.ACTION_MOVE: int deltaX = x - mLastX; /** * scrollX是指ViewGroup的左侧边框和当前内容左侧边框之间的距离 */ int scrollX=getScrollX(); if(scrollX-deltaX>0 && (scrollX-deltaX)<=(mMeasureWidth-mScreenWidth)) { scrollBy(-deltaX, 0); } break; case MotionEvent.ACTION_UP: scrollX=getScrollX(); int dx; //计算滑动的差值,如果超过1/4就滑动到下一页 int subScrollX=scrollX-((showViewIndex-1)*mScreenWidth); if(Math.abs(subScrollX)>=mCrital){ boolean next=scrollX>(showViewIndex-1)*mScreenWidth; if(showViewIndex<3 && next) { showViewIndex++; }else { showViewIndex--; } } dx=(showViewIndex - 1) * mScreenWidth - scrollX; smoothScrollByDx(dx); break; } mLastX = x; mLastY = y; return true; } /** * 缓慢滚动到指定位置 * @param dx */ private void smoothScrollByDx(int dx) { //在1000毫秒内滑动dx距离,效果就是慢慢滑动 mScroller.startScroll(getScrollX(), 0, dx, 0, 1000); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } }

从上面代码中,我们看到我们只是很简单的采用横向滑动距离和垂直滑动距离进行比较来判断滑动方向。在滑动过程中,当水平方向的距离大时就判断为水平滑动,否者就是垂直滑动。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

时间: 2024-11-08 19:44:24

浅谈Android View滑动冲突的解决方法的相关文章

Android嵌套滑动冲突的解决方法

android在嵌套滑动的时候会产生滑动冲突.之前我也碰到,但是以前的笔记本丢失了,所以只能重新再写一章. 一.会产生滑动冲突的情况 那么什么时候会产生滑动冲突呢?比如你有个activity,activity的上半部分是一个布局,下半部分是一个可滑动控件(RecyclerView.ListView等),或者下半部分是个viewpager,里面的fragment布局是一个可滑动控件,这样的页面就会产生滑动冲突. 二.以前的做法 虽然我以前的笔记丢失了,但是当时的解决问题的思路我依然记得. (1)重

浅谈Android View绘制三大流程探索及常见问题

View绘制的三大流程,指的是measure(测量).layout(布局).draw(绘制) measure负责确定View的测量宽/高,也就是该View需要占用屏幕的大小,确定完View需要占用的屏幕大小后,就会通过layout确定View的最终宽/高和四个顶点在手机界面上的位置,等通过measure和layout过程确定了View的宽高和要显示的位置后,就会执行draw绘制View的内容到手机屏幕上. 在详细介绍这三大流程之前,需要简单了解一下ViewRootImpl,View绘制的三大步骤

android中view手势滑动冲突的解决方法_Android

Android手势事件的冲突跟点击事件的分发过程息息相关,由三个重要的方法来共同完成,分别是:dispatchTouchEvent.onInterceptTouchEvent和onTouchEvent. public boolean dispatchTouchEvent(MotionEvent ev) 这个方法用来进行事件的分发.如果事件传递到view,那么这个方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是

Android中DrawerLayout+ViewPager滑动冲突的解决方法

DrawerLayout 是 Android 官方的侧滑菜单控件,而 ViewPager 相信大家都很熟悉了.今天这里就讲一下当在 DrawerLayout 中嵌套 ViewPager 时,要如何解决滑动冲突的问题,效果如下: 首先,让我们先来解决 DrawerLayout 和 ViewPager 的侧滑事件冲突.当 DrawerLayout 中嵌套 ViewPager 时,侧滑默认是执行 DrawerLayout 的侧滑事件,因为 Android 的事件分发是从 外层 ViewGroup 向里

ScrollView嵌套ListView滑动冲突的解决方法_Android

ScrollView和ListView这两个控件想必大家都不会陌生,但是这两者嵌套使用的时候就会出现麻烦.比如,我们如果想在ListView下面添加其他的布局或者控件,然后想让它们作为一个整体都可以滑动的话,最常想到的就是用一个ScrollView把它们包裹起来.想法似乎很美好,但是现实就有点残酷了.我们可以写一个小例子体验一下. 首先创建一个Activity,在它的布局文件上放置一个ListView: <?xml version="1.0" encoding="utf

ScrollView嵌套ListView滑动冲突的解决方法

ScrollView和ListView这两个控件想必大家都不会陌生,但是这两者嵌套使用的时候就会出现麻烦.比如,我们如果想在ListView下面添加其他的布局或者控件,然后想让它们作为一个整体都可以滑动的话,最常想到的就是用一个ScrollView把它们包裹起来.想法似乎很美好,但是现实就有点残酷了.我们可以写一个小例子体验一下. 首先创建一个Activity,在它的布局文件上放置一个ListView: <?xml version="1.0" encoding="utf

浅谈关键词排名消失后的解决方法

对于站长们来说,关键词排名消失也不是一件不经常发生的事情,很多人在不知不觉间就让自己的网站主关键词从搜索引擎上面消失了,从此而对搜索引擎充满了敌意,而不去主动研究主关键词排名为什么会消失!下面就从我自身的经历跟大家说说我是怎么挽救一个主关键词消失的网站的! 本来我做的是有关丰胸精油排行榜这个主关键词的导购站,在刚刚建设后充满了运营的热情,每天都会通过百度来搜索网站主关键词的排名,经过我这个期间不断的努力,终于这个关键词能够上升到搜索引擎的第二页了,看到了主关键词排名不断的上升,我自然是非常高兴的

浅谈Android实践之ScrollView中滑动冲突处理解决方案_Android

1. 前言  在Android开发中,如果是一些简单的布局,都很容易搞定,但是一旦涉及到复杂的页面,特别是为了兼容小屏手机而使用了ScrollView以后,就会出现很多点击事件的冲突,最经典的就是ScrollView中嵌套了ListView.我想大部分刚开始接触Android的同学们都踩到过这个坑,这一篇文章就从最近做的一个项目讲起,然后在过程中提供一些解决冲突的思路. 2. 项目起始 项目有一个页面,涉及到了ViewPager,MapView,ListView,也就是说在一个页面中,会有这三个

浅谈Android开发中项目的文件结构及规范化部署建议_java

一.几句话 使用Gradle及其推荐的项目框架 把密码等敏感数据放入gradle.properties 不要自己写Http客户端,使用Volley或OkHttp库 使用Jackson库来解析JSON数据 避免Guava并出于Dalvik 65K methods limit不要使用过多的库 使用Fragment来绘制UI界面 Activity主要用来管理Fragment 布局文件XML也是代码,好好组织它们 在布局文件里,使用styles以避免重复的属性 使用多个style文件而不是一个巨大的st