浅谈Android View滑动冲突的解决方法
作者:dreamGong 发布时间:2021-12-17 06:47:09
引言
这一篇文章我们就通过介绍滑动冲突的规则和一个实例来更加深入的学习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();
}
}
}
从上面代码中,我们看到我们只是很简单的采用横向滑动距离和垂直滑动距离进行比较来判断滑动方向。在滑动过程中,当水平方向的距离大时就判断为水平滑动,否者就是垂直滑动。
来源:http://www.cnblogs.com/dreamGong/p/6380065.html#top


猜你喜欢
- Spring Boot可以和大部分流行的测试框架协同工作:通过Spring JUnit创建单元测试;生成测试数据初始化数据库用于测试;Spr
- 最近看到在Linux上折腾jmeter的人越来越多,不过即使在windows上,jmeter的脚本我还是建议用命令行来执行(降低GUI模式带
- 前言Mybatis真正强大的地方在于SQL映射语句,这也是它的魅力所在。相对于它强大的功能,SQL映射文件的配置却非常简单,我上篇文章语句讲
- 之前写了一个WPF的圆形环绕的Loading动画,现在写一个Winform的圆形环绕的Loading动画。1.新建Winform项目,添加一
- 今天看《第一行代码》上面关于拍照和从相册选取图片那一部分,发现始终出不来效果,所以搜索其他资料学习一下相关知识,写一个简单的Demo。&nb
- 代码从windows下visual studio到andriod平台迁移实现步骤:前言前言也是迁言,从windows的visual stud
- springboot2启动时执行,初始化(或定时任务)servletContext需求:springboot 启动后自动执行,初始化数据,并
- 面向对象编程(Object Oriented Programming)有三大特性:封装、继承、多态。在这里,和大家一起加深对三者的理解。封装
- 苹果的Touch Icon相对我们都比较熟悉,是苹果为了支持网络应用(或者说网页)添加到桌面需要的图标,有了这些Touch Icon的网页链
- 自从SEOTcs系统11月份24日更新了一下SEO得分算法以来,一直困扰我的一个问题出现了,java的数据job任务,在执行过程中会经常报以
- 背景:有一次在生产环境,突然出现了很多笔还款单被挂起,后来排查原因,发现是内部系统调用时出现了Hystrix调用异常。在开发过程中,因为核心
- 本文实例讲述了Android获取SD卡及手机ROM容量的方法。分享给大家供大家参考,具体如下:这里通过一个简单的小例子,来获取SD卡的容量和
- IDEA 端口占用解决方法后台开发时经常遇到端口占用问题Intellij IDEA端口占用 解决方法:方法1:打开任务管理器,关闭java
- MyBatis的注解实现复杂映射开发实现复杂关系映射之前我们可以在映射文件中通过配置来实现,使用注解开发后,我们可以使用@Results注解
- 最近项目进行适配的时候发现部分(如华为手机)存在底部虚拟按键的手机会因为虚拟按键的存在导致挡住部分界面,因为需要全屏显示,故调用虚拟按键隐藏
- 首先给大家来讲一个我们遇到的一个奇怪的问题:1.我的一个springboot项目,用mvn install打包成jar,换一台有jdk的机器
- IDEA快速创建getter和setter方法找到generate我的是Mac,右击鼠标就可以打开,相信windows也不难。选择gette
- SharedPreferences是Android中最容易理解的数据存储技术,实际上SharedPreferences处理的就是一个key-
- 一.认识IO1.IO的分类(1)BIO:同步阻塞IO(2)NIO:同步非阻塞IO(3)AIO:异步阻塞IO注意: 这里主要介绍BIO2.IO
- 欲达此目的,可以采用下列两种作法: ◆使用XmlConvert类。 ◆将一个XSLT转换套用至DataSet数据的XML表示。 程序范例 本