View事件分發(fā)原理和ViewPager+ListView嵌套滑動沖突
前言:
一個touch事件序列包括:down、move、up(其中move事件會多次觸發(fā),就是說如果手指在屏幕上多次滑動的時候會多次觸發(fā)move事件,可以利用這一點實現(xiàn)view 的移動)
ViewGroup:用來進行事件分發(fā) View:用來對事件的處理
分發(fā)流程: Activity#dispatchTouchEvent -> PhoneWindow#superDispatchTouchEvent -> DecorView#superDispatchTouchEvent ->ViewGroup#dispatchTouchEvent -> View#dispatchTouchEvent ->View#OnTouchEvent
從下往上看,先看事件如何被處理的,先看一個例子
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的話兩個打印日志都有 return true;//只有onTouch日志會打印 } });
對同一個組件設(shè)置兩個監(jiān)聽,此處有三個面試題:
- 1、onTouch的返回值有什么用
- 2、onTouch和onClick哪個先調(diào)用(還有一個onTouchEvent)
- 3、在哪里調(diào)用的
帶著問題看看View的dispatchTouchEvent
從代碼中可以看出,onTouch的優(yōu)先級要高于onTouchEvent,而onClick是在onTouchEvent中調(diào)用的,因此onClick的優(yōu)先級最低
注意以上三個方法可能都不會執(zhí)行,因為三個方法都是在View的dispatchTouchEvent的執(zhí)行的,如果連dispatchTouchEvent都不執(zhí)行的話,那么三個方法就都不會執(zhí)行了
什么情況下View的dispatchTouchEvent會不執(zhí)行呢:父容器不分發(fā)事件給View,就不會執(zhí)行,即父容器不會調(diào)用子View的dispatchTouchEvent方法
那什么時候父容器不會分發(fā)事件給View呢?這就需要看看事件分發(fā)的過程了: Activity#dispatchTouchEvent -> PhoneWindow#superDispatchTouchEvent -> DecorView#superDispatchTouchEvent -> ViewGroup#dispatchTouchEvent
ViewGroup中的dispatchTouchEvent中的核心地方可以用兩句偽代碼來闡述(摘自Android開發(fā)藝術(shù)探索):
如果,ViewGroup的onInterceptTouchEvent方法執(zhí)行了,則表示ViewGroup攔截了當(dāng)前事件,去執(zhí)行自己的 onTouchEvent邏輯,否則將事件分發(fā)給子View去執(zhí)行
ViewGroup的分發(fā)邏輯主要有三個部分:
第一部分:判斷是否攔截該事件:
第二部分:分發(fā)事件給View,看哪個子View處理事件(源碼太多了,只粘了后半部分)
注意:當(dāng)Move事件來的時候不會走第二部分代碼!?。?/p>
第三部分:執(zhí)行事件:單指操作還是多指操作
- 1、上下滑動的時候,此時ViewPager被設(shè)置為不允許攔截,所以事件交給了ListView,ListView正常上下滑動沒問題
- 2、左右滑動的時候,此時ViewPager被設(shè)置為允許攔截,而在ViewPager的onInterceptTouchEvent方法中move事件返回了true,所以攔截事件成功,ViewPager會執(zhí)行自己的ontouchevent,實現(xiàn)左右滑動
子View去執(zhí)行事件邏輯:
dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign) handled = child.dispatchTouchEvent(event); ? ?
總結(jié):
1、如果父View攔截了事件并消費了事件,則子View 的dispatchTouchEvent就不會執(zhí)行 2、如果父View并沒有攔截事件,但是所有的子View都沒有消費此事件,則最后也是執(zhí)行父View的dispatchTouchEvent 3、如果父View沒有攔截事件,且某個子View攔截了此事件消費了,事件就不會再向下個子View傳遞,如果沒有消費,則會繼續(xù)遍歷下一個子View(這段邏輯再第二部分的for循環(huán)中)
如果子View處理了就提前break
如何解決自定View 的滑動沖突呢:根據(jù)實際情況去分配事件
- 1、在子View中去處理(內(nèi)部攔截法) 通常也會涉及父類的改動
- 2、在父View中去處理(外部攔截法)
以內(nèi)部攔截法做一個例子: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事件,也就無法處理touchevent } return true; } }
原理解釋:
首先down事件傳遞給MyPager, down事件來的時候ViewGroup會重置標志位,而且onInterceptTouchEvent方法一定會執(zhí)行,所以這里一定要返回false,ListView才會收到Down事件,否則listView是否發(fā)下拉的
按照上述代碼,此后ViewGroup應(yīng)該會執(zhí)行第二塊代碼塊去分發(fā)事件,即listView去處理事件,在ListView中的down事件調(diào)用getParent().requestDisallowInterceptTouchEvent(true)方法,會改變ViewGroup中mGroupFlags標志位,進而影響ViewPager中對后續(xù)事件的攔截回調(diào)的執(zhí)行與否
當(dāng)Move事件到來的時候,由于ListView在Down事件的時候設(shè)置了不攔截事件,則ViewPager也不會攔截Move事件,所以此事件落到listView去處理,在ListView中根據(jù)手指滑動情況去設(shè)置ViewPager是否攔截move事件:
- 1、上下滑動的時候,此時ViewPager被設(shè)置為不允許攔截,所以事件交給了ListView,ListView正常上下滑動沒問題
- 2、左右滑動的時候,此時ViewPager被設(shè)置為允許攔截,而在ViewPager的onInterceptTouchEvent方法中move事件返回了true,所以攔截事件成功,ViewPager會執(zhí)行自己的ontouchevent,實現(xiàn)左右滑動
到此這篇關(guān)于View事件分發(fā)原理和ViewPager+ListView嵌套滑動沖突的文章就介紹到這了,更多相關(guān)View的事件分發(fā) 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android導(dǎo)入第三方j(luò)ar包報錯 如何正確導(dǎo)入jar包
怎樣在android平臺上使用第三方j(luò)ar包,為什么我在引入了,編譯時沒有錯誤,運行時就有錯誤,報無法實例化錯誤,請問這是什么原因,本文給于解決方法,需要了解的朋友可以參考下2012-12-12Android 中使用RecyclerView實現(xiàn)底部翻頁
這篇文章主要介紹了Android 中使用RecyclerView實現(xiàn)底部翻頁功能,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-11-11在當(dāng)前Activity之上創(chuàng)建懸浮view之WindowManager懸浮窗效果
這篇文章主要介紹了在當(dāng)前Activity之上創(chuàng)建懸浮view之WindowManager懸浮窗效果的相關(guān)資料,需要的朋友可以參考下2016-01-01Kotlin Flow封裝類SharedFlow StateFlow LiveData使用
這篇文章主要為大家介紹了Kotlin Flow封裝類SharedFlow StateFlow LiveData使用對比,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08Android仿微信圖片上傳帶加號且超過最大數(shù)隱藏功能
這篇文章給大家分享android仿照微信空間上傳圖片,顯示圖片數(shù)量以及超過最大,上傳按鈕隱藏功能,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2018-03-03Android ViewPager實現(xiàn)無限循環(huán)的實例
這篇文章主要介紹了Android ViewPager實現(xiàn)無限循環(huán)的實例的相關(guān)資料,需要的朋友可以參考下2017-07-07