面試五連環(huán)
1.滑動沖突的幾種情況
2.滑動沖突解決方案和源碼分析
3.滑動沖突的幾種解決方案的使用場景?什么時候用內部攔截稳摄?
4.webview滑動沖突如何解決?x5 webview如何攔截撵枢?
5.滑動沖突實踐
1.滑動沖突的幾種情況
1).點擊時間和滑動事件(比如懸浮球)
2).外層與內層滑動方向不一致锋喜,外層ViewGroup是可以橫向滑動的,內層View是可以豎向滑動的(類似ViewPager棒搜,每個頁面里面是ListView)
3).外層與內層滑動方向一致疹蛉,外層ViewGroup是可以豎向滑動的,內層View同樣也是豎向滑動的(類似ScrollView包裹ListView)
2.滑動沖突解決方案和源碼分析
針對上面第一種場景力麸,onTouch和onTouchEvent事件可款,即可
針對上面第二種場景,由于外部與內部的滑動方向不一致克蚂,那么我們可以根據(jù)當前滑動方向闺鲸,水平還是垂直來判斷這個事件到底該交給誰來處理。至于如何獲得滑動方向埃叭,我們可以得到滑動過程中的兩個點的坐標摸恍。如豎直距離與橫向距離的大小比較;
針對第三種場景赤屋,由于外部與內部的滑動方向一致立镶,那么不能根據(jù)滑動角度壁袄、距離差或者速度差來判斷。這種情況下必需通過業(yè)務邏輯來進行判斷谜慌。比較常見ScrollView嵌套了ListView。
套路一 外部攔截法:
即父View根據(jù)需要對事件進行攔截莺奔。邏輯處理放在父View的onInterceptTouchEvent方法中欣范。我們只需要重寫父View的onInterceptTouchEvent方法,并根據(jù)邏輯需要做相應的攔截即可令哟。
publicbooleanonInterceptTouchEvent(MotionEvent event){booleanintercepted =false;intx = (int) event.getX();inty = (int) event.getY();switch(event.getAction()) {caseMotionEvent.ACTION_DOWN: {? ? ? ? ? ? ? ? intercepted =false;break;? ? ? ? ? ? }caseMotionEvent.ACTION_MOVE: {if(滿足父容器的攔截要求) {? ? ? ? ? ? ? ? ? ? intercepted =true;? ? ? ? ? ? ? ? }else{? ? ? ? ? ? ? ? ? ? intercepted =false;? ? ? ? ? ? ? ? }break;? ? ? ? ? ? }caseMotionEvent.ACTION_UP: {? ? ? ? ? ? ? ? intercepted =false;break;? ? ? ? ? ? }default:break;? ? ? ? }? ? ? ? mLastXIntercept = x;? ? ? ? mLastYIntercept = y;returnintercepted;? ? }
上面?zhèn)未a表示外部攔截法的處理思路恼琼,需要注意下面幾點(down和up一樣不攔截,move根據(jù)條件攔截)
ACTION_DOWN 一定返回false屏富,不要攔截它晴竞,否則根據(jù)View事件分發(fā)機制,后續(xù)ACTION_MOVE 與 ACTION_UP事件都將默認交給父View去處理狠半!
在ACTION_MOVE方法中進行判斷噩死,根據(jù)業(yè)務邏輯需要,如果需要父View處理則返回true神年,否則返回false已维,事件分發(fā)給子View去處理。
原則上ACTION_UP也需要返回false已日,如果返回true垛耳,并且滑動事件交給子View處理,那么子View將接收不到ACTION_UP事件飘千,子View的onClick事件也無法觸發(fā)堂鲜。而父View不一樣,如果父View在ACTION_MOVE中開始攔截事件护奈,那么后續(xù)ACTION_UP也將默認交給父View處理缔莲!
套路二 內部攔截法:
即父View不攔截任何事件,所有事件都傳遞給子View霉旗,子View根據(jù)需要決定是自己消費事件還是給父View處理酌予。這需要子View使用requestDisallowInterceptTouchEvent方法才能正常工作。下面是子View的dispatchTouchEvent方法的偽代碼:
父View需要重寫onInterceptTouchEvent方法:
為什么要重寫父類的onInterceptTouchEvent?因為默認不攔截奖慌,你需要的是攔截它
publicbooleanonInterceptTouchEvent(MotionEvent event){intaction = event.getAction();if(action == MotionEvent.ACTION_DOWN) {//down不攔截抛虫,給子類returnfalse;? ? ? ? }else{//攔截returntrue;? ? ? ? }? ? }
重寫子類的dispatchTouchEvent()方法
? public boolean dispatchTouchEvent(MotionEvent event) {intx = (int) event.getX();inty = (int) event.getY();switch(event.getAction()) {caseMotionEvent.ACTION_DOWN: {? ? ? ? ? ? ? ? parent.requestDisallowInterceptTouchEvent(true);break;? ? ? ? ? ? }caseMotionEvent.ACTION_MOVE: {intdeltaX = x - mLastX;intdeltaY = y - mLastY;if(父容器需要此類點擊事件) {? ? ? ? ? ? ? ? ? ? parent.requestDisallowInterceptTouchEvent(false);? ? ? ? ? ? ? ? }break;? ? ? ? ? ? }caseMotionEvent.ACTION_UP: {break;? ? ? ? ? ? }default:break;? ? ? ? }? ? ? ? mLastX = x;? ? ? ? mLastY = y;returnsuper.dispatchTouchEvent(event);? ? }
內部攔截法要求父View不能攔截ACTION_DOWN事件,由于ACTION_DOWN不受FLAG_DISALLOW_INTERCEPT標志位控制简僧,一旦父容器攔截ACTION_DOWN那么所有的事件都不會傳遞給子View建椰。
滑動策略的邏輯放在子View的dispatchTouchEvent方法的ACTION_MOVE中,如果父容器需要獲取點擊事件則調用 parent.requestDisallowInterceptTouchEvent(false)方法岛马,讓父容器去攔截事件棉姐。
?你好屠列,內部攔截法中父容器 onInterceptTouchEvent 方法 ACTION_MOVE、ACTION_UP返回true伞矩,子view 的dispatchTouchEvent 方法中ACTION_MOVE笛洛、ACTION_UP事件還能接受到嗎,新手乃坤,這里不是很懂苛让,求指教?
原理分析:
?? 內部攔截法也叫View分發(fā)反向制約的方法?
?? 攔截不攔截湿诊,由2個東西決定的狱杰。一個是requestDisllowIntercepter和onInterceptTouchEvent()2個決定的。
源碼如下厅须。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier !=null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
? ? }
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
? ? if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
? ? }
if (actionMasked == MotionEvent.ACTION_DOWN
? ? ? ? ||mFirstTouchTarget !=null) {
final boolean disallowIntercept = (mGroupFlags &FLAG_DISALLOW_INTERCEPT) !=0;
? ? if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
? ? ? ? ev.setAction(action); // restore action in case it was changed
? ? }else {
intercepted =false;
? ? }
原因:但是子元素可以通過requestDisallowInterceptTouchEvent來干預父元素的分發(fā)過程仿畸,但是down事件除外(因為down事件方法里,會清除所有的標志位)朗和。
3.滑動沖突的幾種解決方案的使用場景
1).onTouch和繪制不管错沽。只管事件的攔截和分發(fā),所以重要的方法是:VIewGourp的onInterceptTouchEvent和View的dispatchTouchEvent
2.down眶拉,move ,Up,都是否需要攔截甥捺?
ACTION_DOWN,都不要攔截子類
在這里,首先down事件父容器必須返回false 镀层,因為若是返回true镰禾,也就是攔截了down事件,
那么后續(xù)的move和up事件就都會傳遞給父容器唱逢,子元素就沒有機會處理事件了吴侦。其次是up事件也返回了false,一是因為up事件對父容器沒什么意義坞古,其次是因為若事件是子元素處理的备韧,卻沒有收到up事件會讓子元素的onClick事件無法觸發(fā)。
3.要在MotionEvent.ACTION_MOVE根據(jù)情況痪枫,是父類滑動還是子類滑動
4.2種方式都要重寫父View需要重寫onInterceptTouchEvent方法:
可以看出外部攔截法實現(xiàn)起來更加簡單织堂,而且也符合View的正常事件分發(fā)機制,所以推薦使用外部攔截法(重寫父View的onInterceptTouchEvent奶陈,父View決定是否攔截)來處理滑動沖突
外部攔截下面列子:
1.down 不攔截易阳,否則up收不到,點擊事件也會沒有
2.move 更加業(yè)務判定吃粒。得到子view潦俺,滑動的距離和item的位置決定
3.up? ? ?不攔截
外部攔截法代碼:
@OverridepublicbooleanonInterceptTouchEvent(MotionEventevent){boolean intercepted=false;inty=(int)event.getY();switch(event.getAction()){caseMotionEvent.ACTION_DOWN:{nowY=y;intercepted=super.onInterceptTouchEvent(event);break;}caseMotionEvent.ACTION_MOVE:{if(mListView.getFirstVisiblePosition()==0&&y>nowY){intercepted=true;break;}elseif(mListView.getLastVisiblePosition()==mListView.getCount()-1&&y<nowY){intercepted=true;break;}intercepted=false;break;}caseMotionEvent.ACTION_UP:{intercepted=false;break;}default:break;}returnintercepted;}
什么時候用內部攔截? ? 主要用內部攔截,系統(tǒng)里面的事示,horscorrlview和早像,比如recyleview.
什么時候用外部攔截?? ?主要看你哪個View是你自己的
面試有一個人問道:
一個ScrowView(父類)和一個RecycleView(子類)
他說重寫子類的onIntecepter方法肖爵,讓子類攔截卢鹦,消費掉
5.滑動沖突實踐
實戰(zhàn)案例一:
ListView下拉刷新,需要ListView自身滑動劝堪,
但是當滑動到頭部時需要ListView和Header一起滑動冀自,也就是整個父容器的滑動。如果不處理好滑動沖突幅聘,就會出現(xiàn)各種意想不到情況凡纳。
實戰(zhàn)案例二:
https://www.cnblogs.com/qhyuan1992/p/5385335.html
實戰(zhàn)案例三:
.ViewPager中嵌套ViewPager怎么處理滑動沖突窃植?
1.重寫canScroll()方法
2.自己手寫
https://blog.csdn.net/weixin_43917449/article/details/86519726
實戰(zhàn)案例四:
一個scorview和一個日期選擇器
自己通過實踐帝蒿,進行重繪寫的
private void doMove(MotionEvent event)
{
mMoveLen += (event.getY() -mLastDownY);
? ? if (mMoveLen >MARGIN_ALPHA *mMinTextSize /2)
{
// 往下滑超過離開距離
? ? ? ? moveTailToHead();
? ? ? ? mMoveLen =mMoveLen -MARGIN_ALPHA *mMinTextSize;
? ? }else if (mMoveLen < -MARGIN_ALPHA *mMinTextSize /2)
{
// 往上滑超過離開距離
? ? ? ? moveHeadToTail();
? ? ? ? mMoveLen =mMoveLen +MARGIN_ALPHA *mMinTextSize;
? ? }
mLastDownY = event.getY();
? ? invalidate();
}
參考: