事件沖突解決思路與方案
目錄介紹
1.事件機制簡單介紹
1.1 觸摸事件
1.2 分發事件
1.3 攔截事件
2.解決滑動沖突的思路及方法
2.1 第一種情況,滑動方向不同
2.2 第二種情況,滑動方法相同
2.3 第三種情況,以上兩種情況嵌套
3.案例解決方法
3.1 針對2問題的解決思路
3.2 滑動方向不同,解決沖突的外部解決法
3.3 滑動方向不同,解決沖突的內部解決法
3.4 ViewPager嵌套ViewPager內部解決法
3.5 滑動方向相同,解決沖突的外部解決法
3.6 解決ScrollView和ViewPager,RecycleView滑動沖突
好消息
- 博客筆記大匯總【16年3月到至今】,包括Java基礎及深入知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計47篇[近20萬字],轉載請注明出處,謝謝!
- 鏈接地址:https://github.com/yangchong211/YCBlogs
- 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質變!
Demo
https://github.com/yangchong211/YCEventConflict
1.事件機制簡單介紹
1.1 觸摸事件
/**
* 觸摸事件
* 如果返回結果為false表示不消費該事件,并且也不會截獲接下來的事件序列,事件會繼續傳遞
* 如果返回為true表示當前View消費該事件,阻止事件繼續傳遞
*
* 在這里要強調View的OnTouchListener。如果View設置了該監聽,那么OnTouch()將會回調。
* 如果返回為true那么該View的OnTouchEvent將不會在執行 這是因為設置的OnTouchListener執行時的優先級要比onTouchEvent高。
* 優先級:OnTouchListener > onTouchEvent > onClickListener
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("onEvent","MyLinearLayout onTouchEvent");
return super.onTouchEvent(event);
}
1.2 分發事件
/**
* 分發事件
* 根據內部攔截狀態,向其child或者自己分發事件
*
* @param ev
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("onEvent","MyLinearLayout dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
1.3攔截事件
/**
* 攔截事件
* 默認實現是返回false,也就是默認不攔截任何事件
*
* 判斷自己是否需要截取事件
* 如果該方法返回為true,那么View將消費該事件,即會調用onTouchEvent()方法
* 如果返回false,那么通過調用子View的dispatchTouchEvent()將事件交由子View來處理
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("onEvent","MyLinearLayout onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
2.解決滑動沖突的思路及方法
2.1 第一種情況,滑動方向不同
2.2 第二種情況,滑動方法相同
2.3 第三種情況,以上兩種情況嵌套
3.案例解決方法
3.1 針對2問題的解決思路
看了上面三種情況,我們知道他們的共同特點是 父View 和 子View 都想爭著響應我們的觸摸事件,但遺憾的是我們的觸摸事件 同一時刻 只能被某一個View或者ViewGroup攔截消費,所以就產生了滑動沖突?那既然同一時刻只能由某一個View或者ViewGroup消費攔截,那我們就只需要 決定在某個時刻由這個View或者ViewGroup攔截事件,另外的 某個時刻由 另外一個View或者ViewGroup攔截事件不就OK了嗎?綜上,正如 在 《Android開發藝術》 一書提出的,總共 有兩種解決方案
3.2 滑動方向不同,解決沖突的外部解決法【以ScrollView與ViewPager為例 】
舉例子:以ScrollView與ViewPager為例
從 父View 著手,重寫 onInterceptTouchEvent 方法,在 父View 需要攔截的時候攔截,不要的時候返回false,代碼大概如下
public class MyScrollView extends ScrollView {
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(21)
public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
private float mDownPosX = 0;
private float mDownPosY = 0;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mDownPosX = x;
mDownPosY = y;
break;
case MotionEvent.ACTION_MOVE:
final float deltaX = Math.abs(x - mDownPosX);
final float deltaY = Math.abs(y - mDownPosY);
// 這里是夠攔截的判斷依據是左右滑動,讀者可根據自己的邏輯進行是否攔截
if (deltaX > deltaY) {
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
}
3.3 滑動方向不同,解決沖突的內部解決法【以ScrollView與ViewPager為例 】
從子View著手,父View 先不要攔截任何事件,所有的 事件傳遞給 子View,如果 子View 需要此事件就消費掉,不需要此事件的話就交給 父View 處理。
實現思路 如下,重寫 子View 的 dispatchTouchEvent 方法,在 Action_down 動作中通過方法 requestDisallowInterceptTouchEvent(true) 先請求 父View 不要攔截事件,這樣保證 子View 能夠接受到Action_move事件,再在Action_move動作中根據 自己的邏輯是否要攔截事件,不要的話再交給 父View 處理
public class MyViewPager extends ViewPager {
private static final String TAG = "yc";
int lastX = -1;
int lastY = -1;
public MyViewPager(Context context) {
super(context);
}
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getRawX();
int y = (int) ev.getRawY();
int dealtX = 0;
int dealtY = 0;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
dealtX = 0;
dealtY = 0;
// 保證子View能夠接收到Action_move事件
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
dealtX += Math.abs(x - lastX);
dealtY += Math.abs(y - lastY);
Log.i(TAG, "dealtX:=" + dealtX);
Log.i(TAG, "dealtY:=" + dealtY);
// 這里是夠攔截的判斷依據是左右滑動,讀者可根據自己的邏輯進行是否攔截
if (dealtX >= dealtY) {
getParent().requestDisallowInterceptTouchEvent(true);
} else {
getParent().requestDisallowInterceptTouchEvent(false);
}
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(ev);
}
}
3.4 ViewPager嵌套ViewPager內部解決法
從 子View ViewPager著手,重寫 子View 的 dispatchTouchEvent方法,在 子View 需要攔截的時候進行攔截,否則交給 父View 處理,代碼如下
public class ChildViewPager extends ViewPager {
private static final String TAG = "yc";
public ChildViewPager(Context context) {
super(context);
}
public ChildViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int curPosition;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
curPosition = this.getCurrentItem();
int count = this.getAdapter().getCount();
Log.i(TAG, "curPosition:=" +curPosition);
// 當當前頁面在最后一頁和第0頁的時候,由父親攔截觸摸事件
if (curPosition == count - 1|| curPosition==0) {
getParent().requestDisallowInterceptTouchEvent(false);
} else {//其他情況,由孩子攔截觸摸事件
getParent().requestDisallowInterceptTouchEvent(true);
}
}
return super.dispatchTouchEvent(ev);
}
}
3.5 滑動方向相同,解決沖突的外部解決法【解決ScrollView和RecycleView滑動沖突】
public class RecyclerScrollview extends ScrollView {
private int downY;
private int mTouchSlop;
public RecyclerScrollview(Context context) {
super(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public RecyclerScrollview(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public RecyclerScrollview(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
int action = e.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downY = (int) e.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int moveY = (int) e.getRawY();
if (Math.abs(moveY - downY) > mTouchSlop) {
return true;
}
}
return super.onInterceptTouchEvent(e);
}
}
**注意:RecycleView一定要被嵌套里面**
<!-- descendantFocusability該屬性是當一個為view獲取焦點時,定義viewGroup和其子控件兩者之間的關系。
beforeDescendants:viewgroup會優先其子類控件而獲取到焦點
afterDescendants:viewgroup只有當其子類控件不需要獲取焦點時才獲取焦點
blocksDescendants:viewgroup會覆蓋子類控件而直接獲得焦點-->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
**3.6 解決ScrollView和ViewPager,RecycleView滑動沖突**
對于ScrollView
public class BottomScrollView extends ScrollView {
private OnScrollToBottomListener mOnScrollToBottomListener;
public BottomScrollView(Context context) {
super(context);
}
public BottomScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BottomScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public BottomScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt){
super.onScrollChanged(l,t,oldl,oldt);
// 滑動的距離加上本身的高度與子View的高度對比
if(t + getHeight() >= getChildAt(0).getMeasuredHeight()){
// ScrollView滑動到底部
if(mOnScrollToBottomListener != null) {
mOnScrollToBottomListener.onScrollToBottom();
}
} else {
if(mOnScrollToBottomListener != null) {
mOnScrollToBottomListener.onNotScrollToBottom();
}
}
}
public void setScrollToBottomListener(OnScrollToBottomListener listener) {
this.mOnScrollToBottomListener = listener;
}
public interface OnScrollToBottomListener {
void onScrollToBottom();
void onNotScrollToBottom();
}
}
**// ViewPager滑動沖突解決**
mViewPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
if(action == MotionEvent.ACTION_DOWN) {
// 記錄點擊到ViewPager時候,手指的X坐標
mLastX = event.getX();
}
if(action == MotionEvent.ACTION_MOVE) {
// 超過閾值,禁止SwipeRefreshLayout下拉刷新,禁止ScrollView截斷點擊事件
if(Math.abs(event.getX() - mLastX) > THRESHOLD_X_VIEW_PAGER) {
mRefreshLayout.setEnabled(false);
mScrollView.requestDisallowInterceptTouchEvent(true);
}
}
// 用戶抬起手指,恢復父布局狀態
if(action == MotionEvent.ACTION_UP) {
mRefreshLayout.setEnabled(true);
mScrollView.requestDisallowInterceptTouchEvent(false);
}
return false;
}
});
**// ListView滑動沖突解決**
mListView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
if(action == MotionEvent.ACTION_DOWN) {
mLastY = event.getY();
}
if(action == MotionEvent.ACTION_MOVE) {
int top = mListView.getChildAt(0).getTop();
float nowY = event.getY();
if(!isSvToBottom) {
// 允許scrollview攔截點擊事件, scrollView滑動
mScrollView.requestDisallowInterceptTouchEvent(false);
} else if(top == 0 && nowY - mLastY > THRESHOLD_Y_LIST_VIEW) {
// 允許scrollview攔截點擊事件, scrollView滑動
mScrollView.requestDisallowInterceptTouchEvent(false);
} else {
// 不允許scrollview攔截點擊事件, listView滑動
mScrollView.requestDisallowInterceptTouchEvent(true);
}
}
return false;
}
});
后續:
平時喜歡寫寫文章,筆記。別人建議我把筆記,以前寫的東西整理,然后寫成博客,所以我會陸續整理文章,只發自己寫的東西,敬請期待:
知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
領英:https://www.linkedin.com/in/chong-yang-049216146/
簡書:http://www.lxweimin.com/u/b7b2c6ed9284
csdn:http://my.csdn.net/m0_37700275
網易博客:http://yangchong211.blog.163.com/
新浪博客:http://blog.sina.com.cn/786041010yc
github:https://github.com/yangchong211
喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
脈脈:yc930211
開源中國:https://my.oschina.net/zbj1618/blog
郵箱:yangchong211@163.com