最近年末了,打算把本身的Android知識都整理一下。git
Android技能書系列:github
Android基礎知識面試
Android技能樹 — Android存儲路徑及IO操做小結app
數據結構基礎知識
算法基礎知識
此次是講View的事件體系。特別是不一樣狀況下的事件分發,我會用很簡單的方式教會你們。
仍是老樣子,先上腦圖,而後具體一塊塊詳細說明。
腦圖連接:View事件體系
咱們經過具體案例來學習
好比咱們如今的需求是這樣的:界面上有一個按鈕,咱們的手指點擊這個按鈕後滑動,這個按鈕能夠跟着咱們的手指一塊兒滑動。(桌面的一些小的清理垃圾的懸浮窗的操做差很少,明白了吧)
具體實現能夠看我之前寫過的文章,十分簡單: 小Demo大知識-控制Button移動來學Android座標
咱們來分析,既然按鈕能夠跟着咱們手指滑動,咱們確定是不停告訴按鈕,當前你的位置是哪裏,既然涉及到一些基本知識點,好比View的位置參數等等。
這裏我配上一張圖,更清楚的來講明這些獲取各自參數的值的說明:
(!!!!!這裏我多畫了getRawX和getRawY方法,View是沒有這二個方法的,請注意!!!!!)
(!!!!!這裏我多畫了getRawX和getRawY方法,View是沒有這二個方法的,請注意!!!!!)
(!!!!!這裏我多畫了getRawX和getRawY方法,View是沒有這二個方法的,請注意!!!!!)
看了這個圖,是否是立刻很清楚了。
注意點:
這裏要說明一個誤區,我面試一些初級水平安卓,我說ViewGroup裏面有個View,這個View的getLeft(),getTop(),getTop(),getBottom()
是什麼,讓他畫給我看下,有些人會給下面這個答案:
這是錯誤的答案,並且根據正確的描述圖,咱們能夠經過getLeft(),getTop(),getTop(),getBottom()
來獲取相應的View的寬高:
width = getRight() - getLeft();
height = getBottom() - getTop();
複製代碼
MotionEvent是什麼,單獨問你們可能有點懵逼,咱們來寫下咱們日常常常寫的設置觸摸的監聽方法:
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
複製代碼
有沒有發現,裏面傳遞過來的參數就有MotionEvent
。
咱們能夠看到,MotionEvent是觸屏事件。當用戶觸摸屏幕時將產生觸屏事件,事件相關細節(發生觸摸的位置、時間、歷史記錄、手勢動做等)被封裝成MotionEvent對象。
具體的介紹真的不少,百度一搜一大把。要細講實在太多了。這裏很少介紹了。
特別提示!!!
不少人會把上面咱們提到過的view.getX/Y()
和這裏的motionvent.getX/Y()
弄混。這裏是有差異的。我再畫個圖來明確下兩者的區別。
因此區別是:
View的getX/Y()是指本身View的左上角相對於父View左上角的距離。MotionEvent的getX/Y()是指點擊處離本身View的左上角的距離。
ps:因此面試官問你getX/Y()的時候,必定問清楚是問的哪一個。!否則很容易回答錯誤。
TouchSlop是系統所能識別出來的被認爲滑動的最小的距離。若是你手指在屏幕上滑動的時候小於這個值,系統就認爲你不是滑動。
滑動時候咱們可能還要監聽速度,好比說咱們的需求就是滑動的快和滑動慢,移動的最終距離不一樣等。這時候咱們必定要知道當前用戶在N時間段內的速度究竟是什麼。這時候咱們就須要速度(Velocity)追蹤者(Tracker)。
咱們先來看看英文翻譯:
沒錯,既然你在屏幕上操做,你多是劃來劃去,多是單擊,多是雙擊。不少狀況。因此這個類就能夠幫咱們來監聽不一樣的操做。
在GestureDetector前面添加了一個Scale。
那就明顯是比例的手勢監測,通俗來講就是放大縮小的手勢監測。
好比咱們的需求是在查看圖片的時候,能夠二個手指放大縮小圖片,那我恩就能夠用這個ScaleGestureDetector
來監測。十分方便。
附上我之前寫過的文章:圖片操做系列 —(1)手勢縮放圖片功能
其實這二個算是基礎知識。
接下去我會用一個真實的例子帶大家更好的理解事件分發,若是講的不合理,能夠提出來哦✧(≖ ◡ ≖✿)
舉個例子:
PS:(若是例子不適合,你們能夠評論反饋。由於若是例子不適合反而誤導了讀者,反而是個人問題了。)
額外提到點:
大家老闆收到了通知就是把這個任務分下去,不可能說第一反應先想一想說我要不要把這個任務攔下來本身作,不要叫手下的人去作了(否則還請大家幹嗎,請了大家還要每次想着要不要本身作)。因此他沒有攔截功能,默認確定不會去攔截,確定第一反應就是直接給手下。
主管都是有權利把任務攔下來的,不給手下的人去作,能夠本身處理,畢竟主管不僅是就分配下任務就夠了,這麼簡單我也想去作主管,可能由於手下都有任務在作,忙不過來的時候,主管會本身去作一些開發任務。
最底層的開發人員,沒有攔截功能,由於任務分到你這裏了。你還能再給誰呢,攔了也是你作,不攔你又沒有下級能夠給背鍋,仍是你作。
因此對比下知道是否是發現跟咱們的Activity,ViewGroup,View
很像:
PS:當收到觸摸事件傳遞到某個層的時候,這個的dispactchEvent會被調用。(至關於上面接受到通知任務的時候會運行這個方法)
老闆 - Activity: 有收到通知的能力,因此會調用dispatchTouchEvent(),而後由於他能夠去通知主管,因此是
客戶通知老闆你有項目了。老闆的dispatchEvent()會被調用。
老闆.dispatchTouchEvent(){
//老闆先通知主管去處理,
若是主管給的回覆是:老闆你不用管接下去的事。咱們會處理的。
if(主管.dispatchTouchEvent()){
return true;//就直接結束了。
}
//手下的人說這個app開發不了,只能老闆出馬作事(跟客戶去溝通去)
return 老闆.onTouchEvent();
}
複製代碼
因此只有dispatchEvent()
和onTouchEvent()
方法。
主管 - ViewGroup
老闆通知了主管有個app要大家部門去開發。主管的dispatchTouchEvent()會被調用
主管.dispatchTouchEvent(){
//主管把這個活攔下來準備本身來開發這個app
if(主管.interceptTouchEvent()){
return 主管.onTouchEvent();//主管也有作事能力
}else{
//主管不攔截,主管也能夠去通知開發人員,
//若是開發人員回饋說主管你別管了。咱們這個app能作好
if(開發人員.dispatchTouchEvent()){
return true; //直接就結束了。
}else{
//若是手下的開發人員也反饋給主管說搞不定。
//就只能主管本身出來作事了。
return 主管.onTouchEvent();
}
}
}
複製代碼
因此有dispatchTouchEvent()、interceptTouchEvent()、onTouchEvent()
。
開發人員 - View
主管通知了開發人員有個app要開發。開發人員的dispatchTouchEvent()會被調用
開發人員.dispatchTouchEvent(){
return 開發人員.onTouchEvent();
}
複製代碼
因此有dispatchTouchEvent()、onTouchEvent()
。
我知道你們必定看到過相似下面的這種圖:
不少人都會死記硬背的去記下來,說return true/false/super等不一樣狀況下不一樣的調用流程。可是這樣其實很很差記住的。不少人會問我是怎麼記住的,我就是用僞代碼來幫忙記住,什麼事僞代碼,上面那種表達方式就是僞代碼。咱們如今正是來看具體的僞代碼。
Activity的真實代碼:
public boolean dispatchTouchEvent(MotionEvent ev){
if(ev.getAction == MotionEvent.ACTION_DOWN){
onUserInteraction();
}
/**
調用window的superDispatchTouchEvent方法,
而後再調用下面的ViewGroup(DecorView)的dispatchTouchEvent()方法。
咱們就直接這麼想,這裏就Activity通知了ViewGroup的dispatchTouchEvent方法。
1.若是這裏getWindow.superDispatchTouchEvent()返回了true,
這時候就會執行return true語句。
2.若是這裏getWindow.superDispatchTouchEvent()返回了false,
這時候就會執行return onTouchEvent(ev);這句,
因此只有當上面的if語句返回false,
纔有機會調用Activity本身的onTouchEvent()方法。
*/
if(getWindow.superDispatchTouchEvent()){
return true;
}
return onTouchEvent(ev);
}
複製代碼
因此不少人會所你重寫Activity的dispatchTouchEvent()方法,返回true/false,都直接結束了事件。返回super才能正常分發,這個說法是不合理的。實際應該這麼描述:
默認重寫Activity的dispatchTouchEvent
方法:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
/**
實際上是調用了super.dispatchTouchEvent方法,
纔會調用上面咱們貼出的Activity的dispatchTouchEvent方法,
才能繼續把事件分發下去。
*/
return super.dispatchTouchEvent(ev);
}
複製代碼
而你們通俗上說返回true/false
就事件結束,是由於沒有調用了super.dispatchTouchEvent(ev);
。因此就不會分發下去,也就事件結束了。
那假如我這麼寫呢:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
super.dispatchTouchEvent(ev);
return true/false;
}
複製代碼
沒錯,事件也是同樣會分發下去。子View的方法也會被調用,而不會說直接結束了。
ViewGroup
由於上面咱們已經說過了getWindow.superDispatchTouchEvent()
能夠直接理解爲是去調用了ViewGroup的dispatchTouchEvent()
;
ViewGroup的僞代碼:
public boolean dispatchTouchEvent(MotionEvent ev){
/**
若是ViewGroup作了攔截,
則直接返回了ViewGroup的onTouchEvent()事件的結果。
*/
if(onInterceptTouchEvent(ev)){
return onTouchEvent(ev);
}else{
/**
若是ViewGroup不作攔截,則先分發給child,
看他們的反應,他們都不接受,則必定會返回false,
則只能ViewGroup本身去執行本身的onTouchEvent(ev);
*/
if(child.dispatchTouchEvent(ev)){
return true;
}else{
return onTouchEvent(ev);
}
}
}
複製代碼
View的僞代碼:
public boolean dispatchTouchEvent(MotionEvent ev){
/**
View 就返回本身的onTouchEvent()
*/
return onTouchEvent();
}
複製代碼
可能不少人仍是說我看了這些代碼仍是不懂啊,我連起來給你看,你就理解了。
這樣,在不一樣狀況下,返回不一樣的false/true,執行順序就知道了。
額外補充:
《補充1》:
固然其實還有更復雜的狀況,咱們知道有ACTION_DOWN,ACTION_MOVE,ACTION_UP,ACTION_CANCEL
等,好比咱們直接ViewGroup攔截Down事件,或者Down事件傳遞到了View後,咱們在MOVE處再攔截,都會執行不一樣的:
《補充2》:
public boolean dispatchTouchEvent(MotionEvent ev){
return onTouchEvent();
}
複製代碼
其實上面是作了簡化,其實除了onTouchEvent
,還有onTouch
事件和onClick
事件,咱們繼續用僞代碼來講明規則:
public boolean dispatchTouchEvent(MotionEvent ev){
if(設置了TouchListener){
if(onTouch的返回值){
return true;
}else{
return onTouchEvent();
}
}
return onTouchEvent();
}
public boolean onTouchEvent(){
if(設置了ClickListener){
執行onClick;
}
.......
}
複製代碼
既然咱們學會了View的事件體系,不少人說那我學會了能怎麼樣,最明顯的就是咱們能夠用來解決不少滑動衝突事件。由於咱們能夠根據實際需求,選擇性的攔截,而後作本身的事件處理。
因此咱們具體來看View的滑動有關的知識:
View的滑動的基本知識我就不特地提出來了。你們能夠分別去搜索。
主要是第二塊View的滑動衝突。咱們就以最簡單的外部左右滑動,內部上下滑動爲例子。
好比咱們規定,滑動的角度是N度之內的時候就是說明咱們在內部滑動,角度是N度之外的時候是外部滑動。
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted=false;
int x= (int) event.getX();
int y= (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
intercepted=false;
//必須不能攔截,不然後續的ACTION_MOME和ACTION_UP事件都會攔截。
break;
case MotionEvent.ACTION_MOVE:
if (父容器須要當前點擊事件){
intercepted=true;
}else {
intercepted=false;
}
break;
case MotionEvent.ACTION_UP:
intercepted=false;
break;
default:
break;
}
mLastXIntercept=x;
mLastXIntercept=y;
return intercepted;
}
複製代碼
子元素的dispatchTouchEvent()重寫:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (父容器須要當前點擊事件) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:{
break;
}
}
mLastXIntercept = x;
mLastYIntercept = y;
return super.dispatchTouchEvent(ev);
}
複製代碼
同時還要修改父容器的onInterceptTouchEvent()方法,不能作攔截,由於若是剛開始DOWN就攔截了,後面的MOVE,UP都沒機會到子元素的上面的代碼。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
複製代碼
歡迎你們查看糾正,😉。。。。讓吐槽來的更猛烈些吧。