ps:本文系转载文章,浏览原文可获取源码,文章开端有原文链接
ps:本文的 demo 是基于 kotlin 语言来写的
在 Android 开发中,如果界面内外两层同时能够滑动,那么就会产生滑动抵触;那 View 产生滑动抵触的都有那几种状况呢?产生滑动抵触的无非是以下3种状况:
1)内部滑动方向和外部滑动方向不统一,比方最外层 View 能够左右滑动,内层 View 能够高低滑动
2)内部滑动方向和外部滑动方向统一
3)以上两种状况的嵌套
1)和 2)这种状况咱们很常见,先说 1)吧,假如咱们自定义了一个 ViewPager 并容许它的子元素能够滑动,当它和 ListView 一起应用的时候就会产生 1)这种状况就会呈现滑动卡顿甚至滑动不了;2)呢,当咱们用 ScrollView 和 RecyclerView 搭配应用并遗记解决滑动抵触时,2)这种状况就会呈现滑动卡顿甚至滑动不了;其实产生滑动抵触的,无非是零碎非法分辨用户想要滑动的是内部 View 还是外部 View。
在状况 1)中,咱们的解决方案是这样的,挪动的过程中,获取到 X 轴和 Y 轴上位移的绝对值,通过比照 X 轴和 Y 轴上的位移,当 X 轴的位移绝对值大于等于 Y轴的位移绝对值时,就拦挡外部 View 的触摸事件,内部 View 就会生产事件;当 X 轴的位移绝对值小于 Y轴的位移绝对值时,就容许外部 View 的进行触摸事件,那么此时内部 View 就不会生产触摸事件。
在状况 2)中,咱们无奈依据滑动的角度、间隔差以及速度差来做判断,然而咱们能够在业务的需要上做出判断,比方需要规定:当外部 View 先开始滑动并生产事件,滑动到一半后就拦挡外部 View 触摸事件并由内部 View 生产,有了解决规定同样能够进行下一步解决。
在状况 3)中,它的滑动规定和状况 2)一样简单,它也无奈间接依据滑动的角度、间隔差以及速度差来做判断,然而也是能够从业务的需要上找到解决方案的,和 2)一样相似的解决规定。
为了更好的了解,咱们以状况 1)进行举例,状况 2)和状况 3)就不再举例了,感兴趣的读者能够对状况 2)和状况 3)进行实现。
首先咱们对 1)制作一个滑动抵触;
1、制作滑动抵触
(1)新建一个 kotlin 语言类型的类 MyListView 并继承于 ListView:
class MyListView: ListView {
var lastX: Int = 0var lastY: Int = 0constructor(context: Context): super(context) {}constructor(context: Context, @Nullable attrs: AttributeSet): super(context,attrs) {}constructor(context: Context, @Nullable attrs: AttributeSet, defStyleAttr: Int): super(context,attrs,defStyleAttr) {}
}
(2)新建一个 kotlin 语言类型的类 MyViewPager 并继承于 ViewPager :
class MyViewPager: ViewPager {
companion object { /** * 1、示意制作一个滑动抵触 * 2、示意用内部拦截法解决滑动抵触 * 3、示意用外部拦截法解决滑动抵触 */ var flag: Int = 0;}var lastXIntercept: Int = 0var lastYIntercept: Int = 0constructor(context: Context): super(context) {}constructor(context: Context,@Nullable attrs: AttributeSet): super(context,attrs) {}override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { if (flag == 1) { return forbidInterceptTouchEvent(ev); } return super.onInterceptTouchEvent(ev)}fun forbidInterceptTouchEvent(ev: MotionEvent?): Boolean { Log.d(MainActivity.TAG,"--forbidInterceptTouchEvent--") return false}
}
(3)新建一个 kotlin 语言类型的类 ViewPagerAdapter 并继承于 PagerAdapter :
class ViewPagerAdapter: PagerAdapter {
val views: List<View>?constructor(list: List<View>){ this.views = list}override fun getCount(): Int { return views!!.size}override fun instantiateItem(container: ViewGroup, position: Int): Any { val view = views!!.get(position) container.addView(view) return view}override fun isViewFromObject(view: View, obj: Any): Boolean { return view === obj}override fun destroyItem(container: ViewGroup, position: Int, obj: Any) { container.removeView(obj as View)}
}
(4)新建一个 kotlin 语言类型的 Activity,名叫 SlideCollideActivity :
class SlideCollideActivity : AppCompatActivity() {
var viewPager: ViewPager? = nulloverride fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_slide_collide) viewPager = findViewById(R.id.viewPager) var viewList = java.util.ArrayList<View>() for (i in 0..3) { val listView = MyListView(this) val dataList = java.util.ArrayList<String>() for (i in 0..29) { dataList.add("数据 $i") } val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, dataList) listView.setAdapter(adapter) viewList!!.add(listView) } viewPager!!.setAdapter(ViewPagerAdapter(viewList))}
}
(5)SlideCollideActivity 对应的布局文件 activity_slide_collide.xml 如下所示:
<?xml version="1.0" encoding="utf-8"?>
<com.xe.views.MyViewPager
android:id="@+id/viewPager"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"tools:context="com.xe.slidecollidedemo.SlideCollideActivity">
</com.xe.views.MyViewPager>
首先咱们将 MyViewPager 类中的 flag 属性置为 1,再运行程序,界面展现如下所示:
图片
当我向左滑动的时候,发现曾经滑动不了了,但打印如下日志:
09-13 18:23:46.023 15084-15084/com.xe.slidecollidedemo D/MainActivity: --forbidInterceptTouchEvent--
09-13 18:23:46.033 15084-15084/com.xe.slidecollidedemo D/MainActivity: --forbidInterceptTouchEvent--
09-13 18:23:46.056 15084-15084/com.xe.slidecollidedemo D/MainActivity: --forbidInterceptTouchEvent--
09-13 18:23:46.080 15084-15084/com.xe.slidecollidedemo D/MainActivity: --forbidInterceptTouchEvent--
咱们晓得,ViewPager 外部曾经做好了滑动抵触的解决,当咱们自定义一个 ViewPager 并重写 它的 onInterceptTouchEvent 办法让该办法的返回值为 false 时,它就天经地义的产生滑动抵触了,因为 MyListView 和 MyViewPager 都能够滑动,所以零碎无奈辨认该滑动谁。
上面咱们来解决滑动抵触,在日常的开发中,我个别用以下2种办法解决滑动抵触,那就是内部拦截法和外部拦截法。
2、内部拦截法
内部拦截法是指导击事件都先通过父容器的拦挡解决,如果父容器须要此事件就拦挡,那么父容器就会生产事件;如果不须要此事件就不拦挡,就交给子元素去生产事件,这样就能够解决滑动抵触的问题;内部拦截法须要重写父容器的 onInterceptTouchEvent 办法,这种办法比拟合乎点击事件的散发机制。
咱们在 1)滑动抵触的 demo 上略微改一下代码;
(1)在 MyListView 类中重写一下 onTouchEvent 办法:
override fun onTouchEvent(ev: MotionEvent): Boolean {
val b = super.onTouchEvent(ev) var s = "s" when (ev.action) { MotionEvent.ACTION_DOWN -> s = "--MyListView--onTouchEvent--MotionEvent.ACTION_DOWN--$b" MotionEvent.ACTION_MOVE -> s = "--MyListView--onTouchEvent--MotionEvent.ACTION_MOVE--$b" MotionEvent.ACTION_UP -> s = "--MyListView--onTouchEvent--MotionEvent.ACTION_UP--$b" } Log.d(MainActivity.TAG, s) return b
}
(2)将 MyViewPager 类的 flag 置为2,并增加 externalIntercept 办法和改一下 onInterceptTouchEvent 办法:
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
if (MyViewPager.flag == 1) { return forbidInterceptTouchEvent(ev); } else if (MyViewPager.flag == 2) { return externalIntercept(ev) } return super.onInterceptTouchEvent(ev)
}
fun externalIntercept(ev: MotionEvent?): Boolean {
var intercepted = false val x = ev!!.getX().toInt() val y = ev!!.getY().toInt() val action = ev.getAction() and MotionEvent.ACTION_MASK when (action) { MotionEvent.ACTION_DOWN -> { intercepted = false //调用 ViewPager的 onInterceptTouchEvent 办法用于初始化 mActivePointerId super.onInterceptTouchEvent(ev) } MotionEvent.ACTION_MOVE -> { val deltaX = x - lastXIntercept val deltaY = y - lastYIntercept intercepted = Math.abs(deltaX) > Math.abs(deltaY) } MotionEvent.ACTION_UP -> { intercepted = false } } lastXIntercept = x lastYIntercept = y return intercepted
}
程序再次运行,当咱们向左滑动时,发现能够滑动了,并打印如下日志:
09-13 21:05:04.172 27291-27291/com.xe.slidecollidedemo D/MainActivity: --MyViewPager--onTouchEvent--MotionEvent.ACTION_MOVE--true
09-13 21:05:04.272 27291-27291/com.xe.slidecollidedemo D/MainActivity: --MyViewPager--onTouchEvent--MotionEvent.ACTION_MOVE--true
09-13 21:05:04.279 27291-27291/com.xe.slidecollidedemo D/MainActivity: --MyViewPager--onTouchEvent--MotionEvent.ACTION_UP--true
当咱们向下滑动时,也能滑动,也并打印如下日志:
09-13 21:06:12.948 27291-27291/com.xe.slidecollidedemo D/MainActivity: --MyListView--onTouchEvent--MotionEvent.ACTION_DOWN--true
09-13 21:06:12.976 27291-27291/com.xe.slidecollidedemo D/MainActivity: --MyListView--onTouchEvent--MotionEvent.ACTION_MOVE--true
09-13 21:06:13.046 27291-27291/com.xe.slidecollidedemo D/MainActivity: --MyListView--onTouchEvent--MotionEvent.ACTION_MOVE--true
咱们重写了 MyViewPager 的 onInterceptTouchEvent 办法,并在该办法进行了滑动抵触的解决,在 MyViewPager 的 down 事件和 up 事件中并没有做滑动解决,当左右滑动间隔的绝对值大于高低间隔滑动的绝对值时,MyViewPager 就进行事件拦挡,并让本人生产;否则就不拦挡事件,并交给子元素 MyListView 生产。
3、外部拦截法
外部拦截法是指父容器不间接拦挡任何事件,所有的事件都传递给子元素,如果子元素须要此事件就间接消耗掉,否则就调用容许父元素拦挡的语句从而交由父容器进行拦挡解决,这种办法和 Android 中的事件散发机制不统一,须要配合 requestDisallowInterceptTouchEvent 办法能力失常工作,应用起来较内部拦截法稍显简单。
咱们在内部拦截法的根底上改一下;
(1)将 MyViewPager 类的 flag 属性置为 3,增加 internalIntercept 办法并批改一下 onInterceptTouchEvent 办法:
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
if (flag == 1) { return forbidInterceptTouchEvent(ev); } else if (flag == 2) { return externalIntercept(ev) } else if (flag == 3) { return internalIntercept(ev) } return super.onInterceptTouchEvent(ev)
}
fun internalIntercept(ev: MotionEvent?): Boolean {
val action = ev!!.getAction() and MotionEvent.ACTION_MASK var intercepted: Boolean = true; when (action) { MotionEvent.ACTION_DOWN -> { intercepted = false super.onInterceptTouchEvent(ev) Log.d(MainActivity.TAG,"--MyViewPager--internalIntercept--MotionEvent.ACTION_DOWN") } MotionEvent.ACTION_MOVE -> { intercepted = true Log.d(MainActivity.TAG,"--MyViewPager--internalIntercept--MotionEvent.ACTION_MOVE") } MotionEvent.ACTION_UP -> { intercepted = false Log.d(MainActivity.TAG,"--MyViewPager--internalIntercept--MotionEvent.ACTION_UP") } } return intercepted
}
(2)在 MyListView 中重写一下 dispatchTouchEvent 办法:
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
if (MyViewPager.flag == 3) { internalIntercept(ev) } return super.dispatchTouchEvent(ev)
}
咱们再次运行,向左滑动时也能进行滑动,日志并打印如下所示:
09-13 21:43:27.257 32062-32062/com.xe.slidecollidedemo D/MainActivity: --MyViewPager--internalIntercept--MotionEvent.ACTION_DOWN
09-13 21:43:27.258 32062-32062/com.xe.slidecollidedemo D/MainActivity: --internalIntercept--
09-13 21:43:27.259 32062-32062/com.xe.slidecollidedemo D/MainActivity: --MyListView--onTouchEvent--MotionEvent.ACTION_DOWN--true
09-13 21:43:27.269 32062-32062/com.xe.slidecollidedemo D/MainActivity: --internalIntercept--
09-13 21:43:27.270 32062-32062/com.xe.slidecollidedemo D/MainActivity: --MyListView--onTouchEvent--MotionEvent.ACTION_MOVE--true
09-13 21:43:27.285 32062-32062/com.xe.slidecollidedemo D/MainActivity: --MyViewPager--internalIntercept--MotionEvent.ACTION_MOVE
09-13 21:43:27.285 32062-32062/com.xe.slidecollidedemo D/MainActivity: --internalIntercept--
09-13 21:43:27.285 32062-32062/com.xe.slidecollidedemo D/MainActivity: s
09-13 21:43:27.303 32062-32062/com.xe.slidecollidedemo D/MainActivity: --MyViewPager--onTouchEvent--MotionEvent.ACTION_MOVE--true
09-13 21:43:27.386 32062-32062/com.xe.slidecollidedemo D/MainActivity: --MyViewPager--onTouchEvent--MotionEvent.ACTION_MOVE--true
09-13 21:43:27.391 32062-32062/com.xe.slidecollidedemo D/MainActivity: --MyViewPager--onTouchEvent--MotionEvent.ACTION_UP--true
当咱们向下滑动时,也能进行滑动,并打印如下日志:
09-13 21:44:54.420 32062-32062/com.xe.slidecollidedemo D/MainActivity: --MyListView--onTouchEvent--MotionEvent.ACTION_MOVE--true
09-13 21:44:54.435 32062-32062/com.xe.slidecollidedemo D/MainActivity: --internalIntercept--
09-13 21:44:54.437 32062-32062/com.xe.slidecollidedemo D/MainActivity: --MyListView--onTouchEvent--MotionEvent.ACTION_MOVE--true
09-13 21:44:54.486 32062-32062/com.xe.slidecollidedemo D/MainActivity: --internalIntercept--
09-13 21:44:54.487 32062-32062/com.xe.slidecollidedemo D/MainActivity: --MyListView--onTouchEvent--MotionEvent.ACTION_UP--true
咱们剖析一下,父元素 MyViewPager 的 down 事件和 up 事件是不拦挡事件的;当咱们只向下滑动的时候,down 事件能传递到子元素 MyListView 中,并在 MyListView 的 dispatchTouchEvent 办法中调用 internalIntercept 办法,ternalIntercept 办法在 down 事件中调用 parent.requestDisallowInterceptTouchEvent(true) 代码,目标是不要执行父元素 MyViewPager 的 onInterceptTouchEvent 办法;当咱们左右滑动时,子元素 MyListView 的 move 事件的 parent.requestDisallowInterceptTouchEvent(false) 代码就会被调用,该行代码的目标是让父元素 MyViewPager 的 onInterceptTouchEvent 办法执行,父元素 MyViewPager 的 move 事件刚好是拦挡事件。