一、外层与内层的滑动方向不一致
比如,外层ViewGroup只能横向滑动,内层View只能纵向滑动(类似ViewPager,每个页面里面是ListView)。
解决方案是根据当前滑动方向是横向还是纵向来判断事件到底该交给谁来处理。
至于如何知晓滑动方向,我们可以得到滑动过程中两个点的坐标,计算手指的移动距离。
根据手指移动距离可以计算出横向移动距离dx与纵向移动距离
dy,比较哪个大:如果dx>dy,那么此次滑动就算作横向滑动;相反,则认为此次滑动是纵向滑动。
二、外层与内层的滑动方向一致
外层ViewGroup是横向滑动的,内层View同样也是横向滑动的(类似ViewPager包裹横向滑动的ListView)。
对于处理内外层滑动方向一致的滑动冲突问题,只有一种解决方
法:根据业务需求,通过下面的拦截与禁止拦截的方法,决定在什么情况下滑动哪个View。
具体的解决方案有如下几种。
1、外部拦截法
所谓外部拦截法,是指点击事件都先经过父控件的拦截处理,如果父控件需要此事件就拦截,如果不需要就不拦截,让消息传递给子控件,这样就可以解决滑动冲突问题。
此方法需要重写父控件的onInterceptTouchEvent函数,在内部
进行相应的消息拦截。
模版代码如下:
private fun fatherNeedClick():Boolean{return true}override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {var intercepted = falsewhen(ev?.action){MotionEvent.ACTION_DOWN ->{intercepted = false}MotionEvent.ACTION_MOVE -> {if (fatherNeedClick()){// 父控件需要当前事件,那就拦截intercepted = true}else{intercepted = false}}MotionEvent.ACTION_UP ->{intercepted = false}}return intercepted}
在使用外部拦截法时,需要提前知道不拦截消息的区域,这样才能做好消息处理,所以也只有在子控件的位置和大小是固定的并且能获取到的情况下,外部拦截法才是有用的。
2、内部拦截法
内部拦截法是指父控件不拦截任何消息,所有消息都传递给子控
件,如果子控件需要此消息就直接消费掉,否则就交给父控件来处理。
这种方法是利用requestDisallowInterceptTouchEvent来实现的。
模版代码如下:
private fun selfNeedClick(): Boolean{return true}override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {when(ev?.action){MotionEvent.ACTION_MOVE -> {if(selfNeedClick()){// 如果自己需要此类事件,就告诉父控件不能拦截parent.requestDisallowInterceptTouchEvent(true)}else{// 否则允许父控件拦截parent.requestDisallowInterceptTouchEvent(false)}}}// 其他事件情况交给父控件处理return super.dispatchTouchEvent(ev)}
3、实际案例
需要实现的效果:顶部是一个TextView,而这个TextView的内容超出了显示区域,它的内容是可以滑动的。当手指在TextView区域内上下滑时,TextView内部的内容也随着滑动,而当手指在其他区域上下滑时,整个ScrollView都在滑动。
(1)内部拦截法
自定义TextView,重写dispatchTouchEvent方法
class CustomTextView @JvmOverloads constructor(context: Context,attributeSet: AttributeSet? = null,defStyleAttr: Int = 0
) : AppCompatTextView(context, attributeSet, defStyleAttr) {override fun dispatchTouchEvent(event: MotionEvent?): Boolean {when(event?.action){MotionEvent.ACTION_MOVE ->{// 在ACTION_MOVE消息到来时,TextView需要自己处理消息,所以禁止父控件拦截parent.requestDisallowInterceptTouchEvent(true)}else -> {}}return super.dispatchTouchEvent(event)}
}
在xml中使用
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/customTextActivity"xmlns:tools="http://schemas.android.com/tools"android:orientation="vertical"tools:context=".widget.CustomTextActivity"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><com.jpc.chapter15.widget.CustomTextViewandroid:id="@+id/tv"android:layout_width="match_parent"android:layout_height="100dp"android:background="@android:color/holo_blue_bright"android:maxHeight="100dp"android:singleLine="false"android:gravity="center"android:scrollbars="vertical"android:text="1234567890defewdwfneinfiwnfefneindwfneinfiwnfefneinfiwnfeidwfneinfidncidncdnjfiwnfeidwfneinfidncidncdnjnfeidwfneinfiwnfefneindwfneinfiwnfefneinfiwnfeidwfneinfidncidncdnjfiwnfeidwfneinfidncidncdnjcndidwfneinfidncidncdnjcnddefcdncidncdnjcnddefcdfdffvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvf"android:textSize="20sp"/><TextViewandroid:layout_width="match_parent"android:layout_height="1000dp"android:background="@android:color/holo_green_light"android:gravity="center"android:text="@string/app_name"android:textSize="20sp"/><TextViewandroid:layout_width="match_parent"android:layout_height="1000dp"android:background="@android:color/holo_green_light"android:gravity="center"android:text="@string/app_name"android:textSize="20sp"/></LinearLayout>
</ScrollView>
最后还需要使TextView可滚动
findViewById<TextView>(R.id.tv).movementMethod = ScrollingMovementMethod.getInstance()
(2)外部拦截法
自定义ScrollView,重写onInterceptTouchEvent方法
class CustomScrollView @JvmOverloads constructor(context: Context,attributeSet: AttributeSet? = null,defStyleAttr: Int = 0
) : ScrollView(context, attributeSet, defStyleAttr) {private var mDownPointY = 0fprivate var mConflictHeight = 0init {// 得到顶部TextView固定高度所对应的px值mConflictHeight = context.resources.getDimensionPixelSize(R.dimen.conflict_height)}override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {var intercepted = falsewhen(ev?.action){MotionEvent.ACTION_DOWN -> {intercepted = falsemDownPointY = ev.y}MotionEvent.ACTION_MOVE ->{// 当ACTION_MOVE消息到来时,判断当前手指位置是不是在顶部TextView的内部,如果在内部,则不拦截消息if (mDownPointY < mConflictHeight){intercepted = false}else{intercepted = true}}MotionEvent.ACTION_UP -> {intercepted = false}}return intercepted}
}
将ScrollView替换成自定义的ScrollView
<?xml version="1.0" encoding="utf-8"?>
<com.jpc.chapter15.widget.CustomScrollView xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/customScrollActivity"xmlns:tools="http://schemas.android.com/tools"android:orientation="vertical"tools:context=".CustomScrollActivity"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><TextViewandroid:id="@+id/tv"android:layout_width="match_parent"android:layout_height="100dp"android:background="@android:color/holo_blue_bright"android:maxHeight="100dp"android:singleLine="false"android:gravity="center"android:layout_gravity="center"android:scrollbars="vertical"android:text="1234567890defewdwfneinfiwnfefneindwfneinfiwnfefneinfiwnfeidwfneinfidncidncdnjfiwnfeidwfneinfidncidncdnjnfeidwfneinfiwnfefneindwfneinfiwnfefneinfiwnfeidwfneinfidncidncdnjfiwnfeidwfneinfidncidncdnjcndidwfneinfidncidncdnjcnddefcdncidncdnjcnddefcdfdffvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvfvf"android:textSize="20sp"/><TextViewandroid:layout_width="match_parent"android:layout_height="1000dp"android:background="@android:color/holo_green_light"android:gravity="center"android:text="@string/app_name"android:textSize="20sp"/><TextViewandroid:layout_width="match_parent"android:layout_height="1000dp"android:background="@android:color/holo_green_light"android:gravity="center"android:text="@string/app_name"android:textSize="20sp"/></LinearLayout>
</com.jpc.chapter15.widget.CustomScrollView>
最后还需要使TextView可滚动
findViewById<TextView>(R.id.tv).movementMethod = ScrollingMovementMethod.getInstance()