網站首頁 編程語言 正文
前言:
一個touch事件序列包括:down、move、up(其中move事件會多次觸發,就是說如果手指在屏幕上多次滑動的時候會多次觸發move事件,可以利用這一點實現view 的移動)
ViewGroup:用來進行事件分發 View:用來對事件的處理
分發流程: 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日志會打印
}
});
對同一個組件設置兩個監聽,此處有三個面試題:
- 1、onTouch的返回值有什么用
- 2、onTouch和onClick哪個先調用(還有一個onTouchEvent)
- 3、在哪里調用的
帶著問題看看View的dispatchTouchEvent?
?從代碼中可以看出,onTouch的優先級要高于onTouchEvent,而onClick是在onTouchEvent中調用的,因此onClick的優先級最低
注意以上三個方法可能都不會執行,因為三個方法都是在View的dispatchTouchEvent的執行的,如果連dispatchTouchEvent都不執行的話,那么三個方法就都不會執行了
什么情況下View的dispatchTouchEvent會不執行呢:父容器不分發事件給View,就不會執行,即父容器不會調用子View的dispatchTouchEvent方法
那什么時候父容器不會分發事件給View呢?這就需要看看事件分發的過程了: Activity#dispatchTouchEvent -> PhoneWindow#superDispatchTouchEvent -> DecorView#superDispatchTouchEvent -> ViewGroup#dispatchTouchEvent
ViewGroup中的dispatchTouchEvent中的核心地方可以用兩句偽代碼來闡述(摘自Android開發藝術探索):
如果,ViewGroup的onInterceptTouchEvent方法執行了,則表示ViewGroup攔截了當前事件,去執行自己的 onTouchEvent邏輯,否則將事件分發給子View去執行
ViewGroup的分發邏輯主要有三個部分:
第一部分:判斷是否攔截該事件:?
第二部分:分發事件給View,看哪個子View處理事件(源碼太多了,只粘了后半部分)
注意:當Move事件來的時候不會走第二部分代碼!!!
第三部分:執行事件:單指操作還是多指操作
- 1、上下滑動的時候,此時ViewPager被設置為不允許攔截,所以事件交給了ListView,ListView正常上下滑動沒問題
- 2、左右滑動的時候,此時ViewPager被設置為允許攔截,而在ViewPager的onInterceptTouchEvent方法中move事件返回了true,所以攔截事件成功,ViewPager會執行自己的ontouchevent,實現左右滑動
子View去執行事件邏輯:
dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
handled = child.dispatchTouchEvent(event); ? ?
總結:
1、如果父View攔截了事件并消費了事件,則子View 的dispatchTouchEvent就不會執行 2、如果父View并沒有攔截事件,但是所有的子View都沒有消費此事件,則最后也是執行父View的dispatchTouchEvent 3、如果父View沒有攔截事件,且某個子View攔截了此事件消費了,事件就不會再向下個子View傳遞,如果沒有消費,則會繼續遍歷下一個子View(這段邏輯再第二部分的for循環中)
如果子View處理了就提前break
如何解決自定View 的滑動沖突呢:根據實際情況去分配事件
- 1、在子View中去處理(內部攔截法) 通常也會涉及父類的改動
- 2、在父View中去處理(外部攔截法)
以內部攔截法做一個例子: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方法一定會執行,所以這里一定要返回false,ListView才會收到Down事件,否則listView是否發下拉的
按照上述代碼,此后ViewGroup應該會執行第二塊代碼塊去分發事件,即listView去處理事件,在ListView中的down事件調用getParent().requestDisallowInterceptTouchEvent(true)方法,會改變ViewGroup中mGroupFlags標志位,進而影響ViewPager中對后續事件的攔截回調的執行與否
當Move事件到來的時候,由于ListView在Down事件的時候設置了不攔截事件,則ViewPager也不會攔截Move事件,所以此事件落到listView去處理,在ListView中根據手指滑動情況去設置ViewPager是否攔截move事件:
- 1、上下滑動的時候,此時ViewPager被設置為不允許攔截,所以事件交給了ListView,ListView正常上下滑動沒問題
- 2、左右滑動的時候,此時ViewPager被設置為允許攔截,而在ViewPager的onInterceptTouchEvent方法中move事件返回了true,所以攔截事件成功,ViewPager會執行自己的ontouchevent,實現左右滑動
原文鏈接:https://juejin.cn/post/7051960953574785061
- 上一篇:C++數據結構之雙向鏈表_C 語言
- 下一篇:C#中Linq的入門教程_C#教程
相關推薦
- 2022-05-29 ASP.NET?Core全局異常處理_實用技巧
- 2022-06-28 python神經網絡使用Keras進行模型的保存與讀取_python
- 2023-11-14 使用python獲取指定進程的CPU/內存情況;Python獲取指定進程的CPU和內存使用情況
- 2023-07-27 TypeScript類和多態、抽象類、訪問修飾符
- 2022-02-28 ESlint 報錯 ESlint: this line has a lines of 103.max
- 2022-09-26 tomcat下載安裝及配置環境變量,但打開startup文件出現閃退問題解決方法
- 2022-01-31 (數據)圖像預處理——image augmentation圖像增廣之cutout、Mixup、Cut
- 2023-01-14 Python?使用pip在windows命令行中安裝HDF?reader包的操作方法_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支