自定義View系列教程08--滑動衝突的產生及其處理
PS:如果覺得文章太長,那就直接看視訊吧
在之前的幾篇文章中,我們已經分析了View對於Touch的處理以及ViewGroup對於Touch事件的分發。
但在開發中時常遇到一個棘手的問題:Touch事件的滑動衝突。比如ListView巢狀ScrollView,ViewPager巢狀ScrollView,ListView巢狀ScrollView時常常發生。
這些滑動衝突的產生,一般而言都具有以下特點:
- 子View和父View都有滑動的需求
- 滑動事件不能準確地傳遞給合適的View
其實,Google官方不鼓勵這種滑動巢狀的設計;但是在實際專案中卻會碰到客戶提出類似的要求。既然不太合理的東西已經存在了,開發人員再去抱怨設計或者客戶都沒有實際作用了,只有想辦法完成對應的功能。
那麼,有哪些方法可以解決滑動衝突呢?
子View禁止父View攔截Touch事件
在分析ViewGroup的dispatchTouchEvent()原始碼時,我們知道:Touch事件是由父View分發的。如果一個Touch事件是子View需要的,但是被其父View攔截了,子View就無法處理該Touch事件了。在此情形下,子View可以呼叫requestDisallowInterceptTouchEvent( )禁止父View對Touch的攔截在父View中準確地進行事件分發和攔截
我們可以重寫父View中與Touch事件分發相關的方法,比如onInterceptTouchEvent( )。這些方法中摒棄系統預設的流程,結合自身的業務邏輯重寫該部分程式碼,從而使父View放行子View需要的Touch。
在此,通過一個示例展示解決滑動衝突的常用方法。
示例效果,請看下圖:
它的佈局檔案如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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.stay4it.testtouch3.MainActivity">
<HorizontalScrollView
android:id="@+id/horizontalScrollView"
android:layout_height="450dp"
android:layout_width="match_parent"
android:scrollbars="horizontal"
android:fillViewport="true">
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</HorizontalScrollView>
</RelativeLayout>
在該示例中,我們在HorizontalScrollView中嵌入ViewPager瀏覽圖片。由於HorizontalScrollView和ViewPager都可以響應水平滑動,當我們用手指切換圖片時發現ViewPager無法正常的滾動。這是因為Touch事件被HorizontalScrollView處理,根本沒有傳遞給ViewPager。
嗯哼,滑動衝突的原因已經清楚了,我們現在來解決這個問題。
第一種解決方法
在ViewPager中禁止HorizontalScrollView攔截Touch事件
package com.stay4it.testtouch3;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
/**
* 原創作者
* 谷哥的小弟
*
* 部落格地址
* http://blog.csdn.net/lfdfhl
*/
public class MainActivity extends AppCompatActivity {
private ViewPager mViewPager;
private ViewPagerAdapter mViewPagerAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init(){
mViewPager = (ViewPager) findViewById(R.id.viewPager);
mViewPagerAdapter=new ViewPagerAdapter(this);
mViewPager.setAdapter(mViewPagerAdapter);
mViewPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mViewPager.getParent().requestDisallowInterceptTouchEvent(true);
return false;
}
});
}
}
在此,請關注第32行程式碼
mViewPager.getParent().requestDisallowInterceptTouchEvent(true);
此處首先利用 mViewPager.getParent( )得到了ViewPager的父View即HorizontalScrollView;然後再執行requestDisallowInterceptTouchEvent( )方法從而禁止掉父View對於Touch事件的傳遞。
第二種解決方法
在自定義HorizontalScrollView中合理地處理Touch事件的攔截和分發
package com.stay4it.testtouch4;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.HorizontalScrollView;
/**
* 原創作者
* 谷哥的小弟
*
* 部落格地址
* http://blog.csdn.net/lfdfhl
*/
public class HorizontalScrollViewSubclass extends HorizontalScrollView{
private float LastX;
private float LastY;
private float distanceX;
private float distanceY;
public HorizontalScrollViewSubclass(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
distanceX = 0f;
distanceY = 0f;
LastX = ev.getX();
LastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
final float currentX = ev.getX();
final float currentY = ev.getY();
distanceX += Math.abs(currentX - LastX);
distanceY += Math.abs(currentY - LastY);
LastX = currentX;
LastY = currentY;
if(distanceX > distanceY){
return false;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return super.onTouchEvent(ev);
}
}
從這段程式碼中,我們的主要操作是在自定義HorizontalScrollView中重寫了onInterceptTouchEvent( )。
在該方法中如果判定Touch是水平的滑動:
if(distanceX > distanceY)
那麼直接返回false不攔截該Touch。這樣Touch事件就可以順利傳遞到其子View即ViewPager中,從而正常地切換圖片。
既然已經採用了自定義的HorizontalScrollView就不要忘記修改佈局檔案喔~
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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.stay4it.testtouch4.MainActivity">
<com.stay4it.testtouch4.HorizontalScrollViewSubclass
android:id="@+id/horizontalScrollView"
android:layout_height="450dp"
android:layout_width="match_parent"
android:scrollbars="horizontal"
android:fillViewport="true"
>
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.stay4it.testtouch4.HorizontalScrollViewSubclass>
</RelativeLayout>
嗯哼,在此通過一個完整的示例展示了事件滑動衝突的常用解決方式。
在實際的專案開發中所遇到的滑動衝突可能比這要複雜,但是處理衝突的基本原理和方式是相同的。
至此,自定義View系列教程就全部結束了
多謝大家的指正和鼓勵
Thank you very much
PS:如果覺得文章太長,那就直接看視訊吧