事件沖突解決思路與方案
目錄介紹
1.事件機(jī)制簡單介紹
1.1 觸摸事件
1.2 分發(fā)事件
1.3 攔截事件
2.解決滑動沖突的思路及方法
2.1 第一種情況肩袍,滑動方向不同
2.2 第二種情況安拟,滑動方法相同
2.3 第三種情況录别,以上兩種情況嵌套
3.案例解決方法
3.1 針對2問題的解決思路
3.2 滑動方向不同界赔,解決沖突的外部解決法
3.3 滑動方向不同伴逸,解決沖突的內(nèi)部解決法
3.4 ViewPager嵌套ViewPager內(nèi)部解決法
3.5 滑動方向相同侨歉,解決沖突的外部解決法
3.6 解決ScrollView和ViewPager,RecycleView滑動沖突
好消息
- 博客筆記大匯總【16年3月到至今】攻锰,包括Java基礎(chǔ)及深入知識點,Android技術(shù)博客熟吏,Python學(xué)習(xí)筆記等等距糖,還包括平時開發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題牵寺,長期更新維護(hù)并且修正悍引,持續(xù)完善……開源的文件是markdown格式的!同時也開源了生活博客帽氓,從12年起趣斤,積累共計47篇[近20萬字],轉(zhuǎn)載請注明出處黎休,謝謝浓领!
- 鏈接地址:https://github.com/yangchong211/YCBlogs
- 如果覺得好,可以star一下势腮,謝謝联贩!當(dāng)然也歡迎提出建議,萬事起于忽微捎拯,量變引起質(zhì)變撑蒜!
Demo
https://github.com/yangchong211/YCEventConflict
1.事件機(jī)制簡單介紹
1.1 觸摸事件
/**
* 觸摸事件
* 如果返回結(jié)果為false表示不消費(fèi)該事件,并且也不會截獲接下來的事件序列,事件會繼續(xù)傳遞
* 如果返回為true表示當(dāng)前View消費(fèi)該事件座菠,阻止事件繼續(xù)傳遞
*
* 在這里要強(qiáng)調(diào)View的OnTouchListener狸眼。如果View設(shè)置了該監(jiān)聽,那么OnTouch()將會回調(diào)浴滴。
* 如果返回為true那么該View的OnTouchEvent將不會在執(zhí)行 這是因為設(shè)置的OnTouchListener執(zhí)行時的優(yōu)先級要比onTouchEvent高拓萌。
* 優(yōu)先級:OnTouchListener > onTouchEvent > onClickListener
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("onEvent","MyLinearLayout onTouchEvent");
return super.onTouchEvent(event);
}
1.2 分發(fā)事件
/**
* 分發(fā)事件
* 根據(jù)內(nèi)部攔截狀態(tài),向其child或者自己分發(fā)事件
*
* @param ev
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("onEvent","MyLinearLayout dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
1.3攔截事件
/**
* 攔截事件
* 默認(rèn)實現(xiàn)是返回false升略,也就是默認(rèn)不攔截任何事件
*
* 判斷自己是否需要截取事件
* 如果該方法返回為true微王,那么View將消費(fèi)該事件,即會調(diào)用onTouchEvent()方法
* 如果返回false,那么通過調(diào)用子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 都想爭著響應(yīng)我們的觸摸事件罩旋,但遺憾的是我們的觸摸事件 同一時刻 只能被某一個View或者ViewGroup攔截消費(fèi),所以就產(chǎn)生了滑動沖突眶诈?那既然同一時刻只能由某一個View或者ViewGroup消費(fèi)攔截涨醋,那我們就只需要 決定在某個時刻由這個View或者ViewGroup攔截事件,另外的 某個時刻由 另外一個View或者ViewGroup攔截事件不就OK了嗎逝撬?綜上浴骂,正如 在 《Android開發(fā)藝術(shù)》 一書提出的,總共 有兩種解決方案
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);
// 這里是夠攔截的判斷依據(jù)是左右滑動,讀者可根據(jù)自己的邏輯進(jìn)行是否攔截
if (deltaX > deltaY) {
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
}
3.3 滑動方向不同谣光,解決沖突的內(nèi)部解決法【以ScrollView與ViewPager為例 】
從子View著手檩淋,父View 先不要攔截任何事件芬为,所有的 事件傳遞給 子View萄金,如果 子View 需要此事件就消費(fèi)掉,不需要此事件的話就交給 父View 處理媚朦。
實現(xiàn)思路 如下氧敢,重寫 子View 的 dispatchTouchEvent 方法,在 Action_down 動作中通過方法 requestDisallowInterceptTouchEvent(true) 先請求 父View 不要攔截事件询张,這樣保證 子View 能夠接受到Action_move事件孙乖,再在Action_move動作中根據(jù) 自己的邏輯是否要攔截事件,不要的話再交給 父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);
// 這里是夠攔截的判斷依據(jù)是左右滑動,讀者可根據(jù)自己的邏輯進(jìn)行是否攔截
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內(nèi)部解決法
從 子View ViewPager著手唯袄,重寫 子View 的 dispatchTouchEvent方法弯屈,在 子View 需要攔截的時候進(jìn)行攔截,否則交給 父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);
// 當(dāng)當(dāng)前頁面在最后一頁和第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該屬性是當(dāng)一個為view獲取焦點時宴偿,定義viewGroup和其子控件兩者之間的關(guān)系。
beforeDescendants:viewgroup會優(yōu)先其子類控件而獲取到焦點
afterDescendants:viewgroup只有當(dāng)其子類控件不需要獲取焦點時才獲取焦點
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坐標(biāo)
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);
}
}
// 用戶抬起手指舷胜,恢復(fù)父布局狀態(tài)
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;
}
});
后續(xù):
平時喜歡寫寫文章,筆記逞带。別人建議我把筆記欺矫,以前寫的東西整理,然后寫成博客展氓,所以我會陸續(xù)整理文章穆趴,只發(fā)自己寫的東西,敬請期待:
知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
領(lǐng)英:https://www.linkedin.com/in/chong-yang-049216146/
簡書:http://www.reibang.com/u/b7b2c6ed9284
csdn:http://my.csdn.net/m0_37700275
網(wǎng)易博客: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