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 = 0
var lastY: Int = 0
constructor(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 = 0
var lastYIntercept: Int = 0
constructor(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? = null
override 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 事件刚好是拦挡事件。