1. 内部拦截法:

父容器不拦截事件,所有的事件全部都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器进行处理。

这种方法和Android中的事件分发机制不一样,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法稍显负责一点。

我们需要重写子元素的dispatchTouchEvent方法。

这种方法的伪代码是:

  @Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY(); switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
parent.requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (父容器需要此类点击事件) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
} mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}

上面重写的子元素的dispatchTouchEvent方法,这里同时需要重写父容器的onInterceptTouchEvent方法,为什么呢?

那是因为ACTION_DOWN事件并不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦父容器拦截ACTION_DOWN事件,那么所有的事件都无法传递到子元素之中,这样内部拦截法就无法起作用了。

父容器所做的修改如下:

 @Override
public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}

2. 下面通过一个Demo示例说明:

(1)首先我们创建一个Android工程,如下:

(2)首先我们来到主布局activity_main.xml,如下:

 <com.himi.viewconflict1.ui.RevealLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="12dp"
tools:context="${relativePackage}.${activityClass}" > <Button
android:id="@+id/button1"
style="@style/AppTheme.Button.Green"
android:onClick="onButtonClick"
android:text="滑动冲突场景1-内部拦截" /> </com.himi.viewconflict1.ui.RevealLayout>

(3)接下来来到MainActivity,如下:

 package com.himi.viewconflict;

 import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View; public class MainActivity extends Activity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} public void onButtonClick(View view) {
Intent intent = new Intent(this, DemoActivity_1.class);
startActivity(intent);
}
}

(4)上面很自然地跳转到DemoActivity_2之中,如下:

 package com.himi.viewconflict1;

 import java.util.ArrayList;

 import com.himi.viewconflict1.ui.HorizontalScrollViewEx2;
import com.himi.viewconflict1.ui.ListViewEx;
import com.himi.viewconflict1.utils.MyUtils; import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import android.widget.Toast; public class DemoActivity_2 extends Activity {
private static final String TAG = "DemoActivity_2"; private HorizontalScrollViewEx2 mListContainer; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.demo_2);
Log.d(TAG, "onCreate");
initView();
} private void initView() {
LayoutInflater inflater = getLayoutInflater();
mListContainer = (HorizontalScrollViewEx2) findViewById(R.id.container);
final int screenWidth = MyUtils.getScreenMetrics(this).widthPixels;
final int screenHeight = MyUtils.getScreenMetrics(this).heightPixels;
for (int i = 0; i < 3; i++) {
ViewGroup layout = (ViewGroup) inflater.inflate(
R.layout.content_layout2, 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) {
ListViewEx listView = (ListViewEx) layout.findViewById(R.id.list);
ArrayList<String> datas = new ArrayList<String>();
for (int i = 0; i < 50; i++) {
datas.add("name " + i);
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
R.layout.content_list_item, R.id.name, datas);
listView.setAdapter(adapter);
listView.setHorizontalScrollViewEx2(mListContainer);
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(DemoActivity_2.this, "click item "+position,
Toast.LENGTH_SHORT).show(); }
});
} @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "dispatchTouchEvent action:" + ev.getAction());
return super.dispatchTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent action:" + event.getAction());
return super.onTouchEvent(event);
}
}

上面的DemoActivity_2主布局demo_2.xml,如下:

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical" > <com.himi.viewconflict1.ui.HorizontalScrollViewEx2
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="match_parent" /> </LinearLayout>

上面使用到HorizontalScrollViewEx2是自定义控件(继承自ViewGroup),如下:

HorizontalScrollViewEx2是父容器,这里需要重写它的onInterceptTouchEvent方法,让父容器不拦截ACTION_DOWN事件。

 package com.himi.viewconflict1.ui;

 import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller; public class HorizontalScrollViewEx2 extends ViewGroup {
private static final String TAG = "HorizontalScrollViewEx2"; private int mChildrenSize;
private int mChildWidth;
private int mChildIndex;
// 分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0; // 分别记录上次滑动的坐标(onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0; private Scroller mScroller;
private VelocityTracker mVelocityTracker; public HorizontalScrollViewEx2(Context context) {
super(context);
init();
} public HorizontalScrollViewEx2(Context context, AttributeSet attrs) {
super(context, attrs);
init();
} public HorizontalScrollViewEx2(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init();
} private void init() {
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
} @Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
mLastX = x;
mLastY = y;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
return true;
}
return false;
} else {
return true;
}
} @Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent action:" + event.getAction());
mVelocityTracker.addMovement(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;
int deltaY = y - mLastY;
Log.d(TAG, "move, deltaX:" + deltaX + " deltaY:" + deltaY);
scrollBy(-deltaX, 0);
break;
}
case MotionEvent.ACTION_UP: {
int scrollX = getScrollX();
int scrollToChildIndex = scrollX / mChildWidth;
Log.d(TAG, "current index:" + scrollToChildIndex);
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) >= 50) {
mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
} else {
mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
}
mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
int dx = mChildIndex * mChildWidth - scrollX;
smoothScrollBy(dx, 0);
mVelocityTracker.clear();
Log.d(TAG, "index:" + scrollToChildIndex + " dx:" + dx);
break;
}
default:
break;
} mLastX = x;
mLastY = y;
return true;
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = 0;
int measuredHeight = 0;
final int childCount = getChildCount();
measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (childCount == 0) {
setMeasuredDimension(0, 0);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);
measuredWidth = childView.getMeasuredWidth() * childCount;
setMeasuredDimension(measuredWidth, heightSpaceSize);
} else {
final View childView = getChildAt(0);
measuredWidth = childView.getMeasuredWidth() * childCount;
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(measuredWidth, measuredHeight);
}
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.d(TAG, "width:" + getWidth());
int childLeft = 0;
final int childCount = getChildCount();
mChildrenSize = childCount; for (int i = 0; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
final int childWidth = childView.getMeasuredWidth();
mChildWidth = childWidth;
childView.layout(childLeft, 0, childLeft + childWidth,
childView.getMeasuredHeight());
childLeft += childWidth;
}
}
} private void smoothScrollBy(int dx, int dy) {
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
invalidate();
} @Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
} @Override
protected void onDetachedFromWindow() {
mVelocityTracker.recycle();
super.onDetachedFromWindow();
}
}

(5)来到主布局之中,在HorizontalScrollViewEx2之中包含一个子布局content_layout2.xml,如下:

 <?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:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:text="TextView" /> <com.himi.viewconflict1.ui.ListViewEx
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff4f7f9"
android:cacheColorHint="#00000000"
android:divider="#dddbdb"
android:dividerHeight="1.0px"
android:listSelector="@android:color/transparent" /> </LinearLayout>

上面的ListViewEx是自定义的控件(继承自ListView),在ListViewEx里面实现了内部拦截法的逻辑,如下:

 package com.himi.viewconflict1.ui;

 import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ListView; public class ListViewEx extends ListView {
private static final String TAG = "ListViewEx"; private HorizontalScrollViewEx2 mHorizontalScrollViewEx2; // 分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0; public ListViewEx(Context context) {
super(context);
} public ListViewEx(Context context, AttributeSet attrs) {
super(context, attrs);
} public ListViewEx(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
} public void setHorizontalScrollViewEx2(
HorizontalScrollViewEx2 horizontalScrollViewEx2) {
mHorizontalScrollViewEx2 = horizontalScrollViewEx2;
} @Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY(); switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "dx:" + deltaX + " dy:" + deltaY);
if (Math.abs(deltaX) > Math.abs(deltaY)) {
mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
} mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
} }

void  requestDisallowInterceptTouchEvent(boolean  disallowIntercept):

这个方法的入参一个boolean 变量,用来表示是否需要调用onInterceptTouchEvent来判断是否拦截.

该标记如果为True,就如它的字面意思一样---不允许调用onInterceptTouchEvent(),结果就是,所有的父类方法都不会进行拦截,而把事件传递给子View. 该方法属于ViewGroup ,并且是个递归方法,也就是说一旦调用后,所有父类的disallowIntercept都会设置成True。即当前View的所有父类View,都不会调用自身的onInterceptTouchEvent()进行拦截。

该标记如果为False,就如它的字面意思一样---允许调用onInterceptTouchEvent(),结果就是,父类可以拦截事件。

接下来,来到Listview的Item布局content_list_item.xml,如下:

 <?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="50dp"
android:gravity="center_vertical"
android:orientation="vertical" > <TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" /> </LinearLayout>

(6)最终项目如下:

(7)部署程序到手机上,如下:

3. 示例源码下载

自定义控件(视图)2期笔记13:View的滑动冲突之 内部拦截法的更多相关文章

  1. 自定义控件(视图)2期笔记12:View的滑动冲突之 外部拦截法

    1. 外部拦截法: 点击事件通过父容器拦截处理,如果父容器需要就拦截,不需要就不拦截. 这种方法比较符合事件分发机制.外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做 ...

  2. view的滑动冲突解决方案

    一.常见的滑动冲突场景 1.外部滑动方向和内部滑动方向不一致 2.外部滑动方向和内部滑动方向一致 3.上面两种情况的嵌套 二.滑动冲突处理的原则 场景1的处理原则是:当用户左右滑动时,需要让外部的vi ...

  3. 一个Demo带你彻底掌握View的滑动冲突

    本文已授权微信公众号:鸿洋(hongyangAndroid)在微信公众号平台原创首发. 近期在又一次学习Android自己定义View这一块的内容.遇到了平时开发中常常碰到的一个棘手问题:View的滑 ...

  4. View的滑动冲突和解决方案

    1.滑动冲突原因: 当有内外两层View同时可以滑动的时候,这个时候就会产生滑动冲突. 2.常见的冲突场景: 场景1: 场景2: 场景3: 4.解决方法种类: (1)外部拦截法: 针对场景1,我们可以 ...

  5. View的滑动冲突

    一.常见的滑动冲突 场景1:外部滑动和内部滑动不一致 场景2:外部滑动和内部滑动一致 场景3:上面两种情况的嵌套 二.滑动冲突的处理方法 场景一:根据水平滑动还是竖直滑动判断到底由谁来拦截事件. 场景 ...

  6. 自定义控件(视图)2期笔记11:View的滑动冲突之 概述

    1. 引入: 滑动冲突可以说是日常开发中比较常见的一类问题,也是比较让人头疼的一类问题,尤其是在使用第三方框架的时候,两个原本完美的控件,组合在一起之后,忽然发现整个世界都不好了. 那到底是为什么会产 ...

  7. Android View的滑动

    Android View的滑动 文章目录 Android View的滑动 一.实现移动 1.1 layout() 1.2 设置位置偏移量 1.3 改变布局参数 1.4 动画 1.5 ScrollTo以 ...

  8. Android学习笔记之View

    转载: 0.7562018.10.22 21:44:10字数 5,423阅读 189   导图 一.View事件体系 1.什么是 View 和 View的位置坐标 View是什么: View 是一种界 ...

  9. 《android开发艺术探索》读书笔记(三)--分发机制和滑动冲突

    接上篇<android开发艺术探索>读书笔记(二) No1: 通过MotionEvent对象可以得到点击事件发生的x和y坐标,getX/getY返回的是相对于当前View左上角的x和y坐标 ...

随机推荐

  1. 构建流式应用—RxJS详解[转]

    目录 常规方式实现搜索功能 RxJS · 流 Stream RxJS 实现原理简析 观察者模式 迭代器模式 RxJS 的观察者 + 迭代器模式 RxJS 基础实现 Observable Observe ...

  2. easyform表单校验插件改版源码

    改动特性: 1.支持回调,可用于ajax提交 2.提示框样式修改,原版太丑,修改成bootstrap的popover  样式 原版还存在缺陷:被校验的表单元素设置不灵活,还得加上id.name 什么的 ...

  3. 微信小程序 c#后台支付结果回调

    又为大家带来简单的c#后台支付结果回调方法,首先还是要去微信官网下载模板(WxPayAPI),将模板(WxPayAPI)添加到服务器上,然后在打开WxPayAPI项目中的example文件下的 Nat ...

  4. protobuf简单测试应用

    protobuf是google推出的一种数据交换协议,比较适合应用于底层服务交互,nodejs提供protobufjs包的实现,下面是一个简单的测试demo: 首先是.proto文件: package ...

  5. hibernate的inverse用法

    Inverse和cascade是Hibernate映射中最难掌握的两个属性.两者都在对象的关联操作中发挥作用. 1.明确inverse和cascade的作用 inverse 决定是否把对对象中集合的改 ...

  6. Q:java中的泛型数组

     对于java,其是不支持直接创建泛型数组的.当采用如下的方式去创建一个泛型数组时,其会出现错误,编译无法通过的情况. package other.jdk1_5; /** * 该类用于演示泛型数组的创 ...

  7. Spring的自动装配Bean

    spring的自动装配功能的定义:无须在Spring配置文件中描述javaBean之间的依赖关系(如配置<property>.<constructor-arg>).IOC容器会 ...

  8. Jquery中动态生成的元素没有点击事件或者只有一次点击事件

    今天用jq做动态生成的元素的click事件时,click只执行了一次,当然有些朋友可能根本没执行, 执行了一次的原因是因为可能有函数加载了一遍,一次都没执行的可能是没绑定对象或者jq版本问题, 动态生 ...

  9. 一类划分关系和指数级生成函数,多项式exp的关系

    划分关系 姑且这么叫着 设满足性质 \(A\) 的集合为 \(S_A\),每个元素有标号 如果 \(S_B\) 是由若干个 \(S_A\) 组成的一个大集合 设 \(a_i\) 表示大小为 \(i\) ...

  10. 01--CSS的引入方式及CSS选择器

    一 CSS介绍 现在的互联网前端分三层: a.HTML:超文本标记语言.从语义的角度描述页面结构. b.CSS:层叠样式表.从审美的角度负责页面样式. c.JS:JavaScript .从交互的角度描 ...