View事件分發(fā)原理和ViewPager+ListView嵌套滑動(dòng)沖突
前言:
一個(gè)touch事件序列包括:down、move、up(其中move事件會(huì)多次觸發(fā),就是說(shuō)如果手指在屏幕上多次滑動(dòng)的時(shí)候會(huì)多次觸發(fā)move事件,可以利用這一點(diǎn)實(shí)現(xiàn)view 的移動(dòng))
ViewGroup:用來(lái)進(jìn)行事件分發(fā) View:用來(lái)對(duì)事件的處理
分發(fā)流程: Activity#dispatchTouchEvent -> PhoneWindow#superDispatchTouchEvent -> DecorView#superDispatchTouchEvent ->ViewGroup#dispatchTouchEvent -> View#dispatchTouchEvent ->View#OnTouchEvent
從下往上看,先看事件如何被處理的,先看一個(gè)例子
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("hover","onCLick");
}
});
btn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e("hover", "onTouch:" + event.getAction());
//return false;//return false的話兩個(gè)打印日志都有
return true;//只有onTouch日志會(huì)打印
}
});對(duì)同一個(gè)組件設(shè)置兩個(gè)監(jiān)聽(tīng),此處有三個(gè)面試題:
- 1、onTouch的返回值有什么用
- 2、onTouch和onClick哪個(gè)先調(diào)用(還有一個(gè)onTouchEvent)
- 3、在哪里調(diào)用的
帶著問(wèn)題看看View的dispatchTouchEvent

從代碼中可以看出,onTouch的優(yōu)先級(jí)要高于onTouchEvent,而onClick是在onTouchEvent中調(diào)用的,因此onClick的優(yōu)先級(jí)最低

注意以上三個(gè)方法可能都不會(huì)執(zhí)行,因?yàn)槿齻€(gè)方法都是在View的dispatchTouchEvent的執(zhí)行的,如果連dispatchTouchEvent都不執(zhí)行的話,那么三個(gè)方法就都不會(huì)執(zhí)行了
什么情況下View的dispatchTouchEvent會(huì)不執(zhí)行呢:父容器不分發(fā)事件給View,就不會(huì)執(zhí)行,即父容器不會(huì)調(diào)用子View的dispatchTouchEvent方法
那什么時(shí)候父容器不會(huì)分發(fā)事件給View呢?這就需要看看事件分發(fā)的過(guò)程了: Activity#dispatchTouchEvent -> PhoneWindow#superDispatchTouchEvent -> DecorView#superDispatchTouchEvent -> ViewGroup#dispatchTouchEvent
ViewGroup中的dispatchTouchEvent中的核心地方可以用兩句偽代碼來(lái)闡述(摘自Android開(kāi)發(fā)藝術(shù)探索):

如果,ViewGroup的onInterceptTouchEvent方法執(zhí)行了,則表示ViewGroup攔截了當(dāng)前事件,去執(zhí)行自己的 onTouchEvent邏輯,否則將事件分發(fā)給子View去執(zhí)行
ViewGroup的分發(fā)邏輯主要有三個(gè)部分:
第一部分:判斷是否攔截該事件:

第二部分:分發(fā)事件給View,看哪個(gè)子View處理事件(源碼太多了,只粘了后半部分)

注意:當(dāng)Move事件來(lái)的時(shí)候不會(huì)走第二部分代碼?。?!
第三部分:執(zhí)行事件:?jiǎn)沃覆僮鬟€是多指操作

- 1、上下滑動(dòng)的時(shí)候,此時(shí)ViewPager被設(shè)置為不允許攔截,所以事件交給了ListView,ListView正常上下滑動(dòng)沒(méi)問(wèn)題
- 2、左右滑動(dòng)的時(shí)候,此時(shí)ViewPager被設(shè)置為允許攔截,而在ViewPager的onInterceptTouchEvent方法中move事件返回了true,所以攔截事件成功,ViewPager會(huì)執(zhí)行自己的ontouchevent,實(shí)現(xiàn)左右滑動(dòng)
子View去執(zhí)行事件邏輯:
dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign) handled = child.dispatchTouchEvent(event); ? ?
總結(jié):
1、如果父View攔截了事件并消費(fèi)了事件,則子View 的dispatchTouchEvent就不會(huì)執(zhí)行 2、如果父View并沒(méi)有攔截事件,但是所有的子View都沒(méi)有消費(fèi)此事件,則最后也是執(zhí)行父View的dispatchTouchEvent 3、如果父View沒(méi)有攔截事件,且某個(gè)子View攔截了此事件消費(fèi)了,事件就不會(huì)再向下個(gè)子View傳遞,如果沒(méi)有消費(fèi),則會(huì)繼續(xù)遍歷下一個(gè)子View(這段邏輯再第二部分的for循環(huán)中)

如果子View處理了就提前break

如何解決自定View 的滑動(dòng)沖突呢:根據(jù)實(shí)際情況去分配事件
- 1、在子View中去處理(內(nèi)部攔截法) 通常也會(huì)涉及父類的改動(dòng)
- 2、在父View中去處理(外部攔截法)
以內(nèi)部攔截法做一個(gè)例子:ViewPager中嵌套ListView
public class SlideInflictFragment extends Fragment {
private BasePager mPager;
List<MyListView> mListViews = new ArrayList<>();
private String[] data={"Apple","Banana","Orange","Watermelon","Pear","Grape","Pineapple",
"Strawberry","Cherry","Mango","Apple","Banana","Orange","Watermelon","Pear","Grape",
"Pineapple","Strawberry","Cherry","Mango"};
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.slide_inflict_view_layout, container, false);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mPager = view.findViewById(R.id.viewPager);
initListViews();
mPager.setAdapter(new MyPagerAdapter(mListViews));
}
private void initListViews(){
MyListView l1 = new MyListView(getContext());
MyListView l2 = new MyListView(getContext());
MyListView l3 = new MyListView(getContext());
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),R.layout.slide_inflict_list_item,data);
l1.setAdapter(adapter);l2.setAdapter(adapter);l3.setAdapter(adapter);
mListViews.add(l1);mListViews.add(l2);mListViews.add(l3);
}
public class MyPagerAdapter extends PagerAdapter{
public List<MyListView> mListViews;
public MyPagerAdapter(List<MyListView> mListViews) {
this.mListViews = mListViews;
}
@Override
public int getCount() {
return mListViews.size();
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
container.addView(mListViews.get(position));
return mListViews.get(position);
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView(mListViews.get(position));
}
}
}public class MyListView extends ListView {
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private int mLastX,mLastY;
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);//表示父容器不能攔截此事件
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x-mLastX;
int deltaY = x-mLastY;
if(Math.abs(deltaX)>Math.abs(deltaY)){
getParent().requestDisallowInterceptTouchEvent(false);//表示可以攔截
}
break;
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
}public class MyPager extends ViewPager {
public MyPager(@NonNull Context context) {
super(context);
}
public MyPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
super.onInterceptTouchEvent(ev);
return false;//必須要在down事件return false,否則listView就接收不到down事件,也就無(wú)法處理touchevent
}
return true;
}
}原理解釋:
首先down事件傳遞給MyPager, down事件來(lái)的時(shí)候ViewGroup會(huì)重置標(biāo)志位,而且onInterceptTouchEvent方法一定會(huì)執(zhí)行,所以這里一定要返回false,ListView才會(huì)收到Down事件,否則listView是否發(fā)下拉的

按照上述代碼,此后ViewGroup應(yīng)該會(huì)執(zhí)行第二塊代碼塊去分發(fā)事件,即listView去處理事件,在ListView中的down事件調(diào)用getParent().requestDisallowInterceptTouchEvent(true)方法,會(huì)改變ViewGroup中mGroupFlags標(biāo)志位,進(jìn)而影響ViewPager中對(duì)后續(xù)事件的攔截回調(diào)的執(zhí)行與否

當(dāng)Move事件到來(lái)的時(shí)候,由于ListView在Down事件的時(shí)候設(shè)置了不攔截事件,則ViewPager也不會(huì)攔截Move事件,所以此事件落到listView去處理,在ListView中根據(jù)手指滑動(dòng)情況去設(shè)置ViewPager是否攔截move事件:
- 1、上下滑動(dòng)的時(shí)候,此時(shí)ViewPager被設(shè)置為不允許攔截,所以事件交給了ListView,ListView正常上下滑動(dòng)沒(méi)問(wèn)題
- 2、左右滑動(dòng)的時(shí)候,此時(shí)ViewPager被設(shè)置為允許攔截,而在ViewPager的onInterceptTouchEvent方法中move事件返回了true,所以攔截事件成功,ViewPager會(huì)執(zhí)行自己的ontouchevent,實(shí)現(xiàn)左右滑動(dòng)
到此這篇關(guān)于View事件分發(fā)原理和ViewPager+ListView嵌套滑動(dòng)沖突的文章就介紹到這了,更多相關(guān)View的事件分發(fā) 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android使用 Spinner控件實(shí)現(xiàn)下拉框功能
Spinner是android的一種控件,用它我們可以實(shí)現(xiàn)下拉框。下面通過(guò)實(shí)例代碼給大家介紹Android使用 Spinner控件實(shí)現(xiàn)下拉框功能,感興趣的朋友一起看看吧2018-08-08
Android使用Kotlin API實(shí)踐WorkManager
這篇文章主要介紹了Android使用Kotlin API實(shí)踐WorkManager的步驟,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-04-04
android 開(kāi)發(fā)教程之日歷項(xiàng)目實(shí)踐(二)
決定開(kāi)始學(xué)習(xí) Android 平臺(tái)下的軟件開(kāi)發(fā),以日歷作為實(shí)踐項(xiàng)目,進(jìn)行一周后,基本完成,有需要的朋友可以參考下2013-01-01
2021最新Android筆試題總結(jié)美團(tuán)Android崗職能要求
這篇文章主要介紹了2021最新Android筆試題總結(jié)以及美團(tuán)Android崗職能要求,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08
Android ImageView Src 和Background 區(qū)別
這篇文章主要介紹了Android ImageView Src 和Background 區(qū)別的相關(guān)資料,需要的朋友可以參考下2016-09-09
ListView實(shí)現(xiàn)下拉刷新加載更多的實(shí)例代碼(直接拿來(lái)用)
這篇文章主要介紹了ListView實(shí)現(xiàn)下拉刷新加載更多的實(shí)例代碼(直接拿來(lái)用)的相關(guān)資料,需要的朋友可以參考下2016-07-07
Android實(shí)現(xiàn)自定義Crash handler記錄崩潰信息實(shí)例代碼
這篇文章主要給大家介紹了Android實(shí)現(xiàn)自定義Crash handler記錄崩潰信息的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02
Android軟件自動(dòng)更新實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Android軟件自動(dòng)更新實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10

