Android View事件機制 21問21答


1.View的坐標參數 主要有哪些?分別有什么注意的要點?

答:Left,Right,top,Bottom 注意這4個值其實就是 view 和 他的父控件的 相對坐標值。 並非是距離屏幕左上角的絕對值,這點要注意。

  此外,X和Y 其實也是相對於父控件的坐標值。 TranslationX,TranslationY 這2個值 默認都為0,是相對於父控件的左上角的偏移量。

  換算關系:

  x=left+tranX,y=top+tranY.

    很多人不理解,為什么事這樣,其實就是View 如果有移動的話,比如平移這種,你們就要注意了,top和left 這種值 是不會變化的。

      無論你把view怎么拖動,但是 x,y,tranX,tranY 的值是隨着拖動平移 而變化的。想明白這點 就行了。

 

2.onTouchEvent和GestureDetector 在什么時候用哪個比較好?

答:只有滑動需求的時候 就用前者,如果有雙擊等這種行為的時候 就用后者。

 

3.Scroller 用來解決什么問題?

答:view的scrollTo和scrollBy 滑動效果太差了,是瞬間完成。而scroller可以配合view的computeScroll 來完成 漸變的滑動效果。體驗更好。

 

4.ScrollTo和ScrollBy 有什么需要注意的?

答:前者是絕對滑動,后者是相對滑動。滑動的是view的內容 而不是view本身。這很重要。比如textview 調用這2個方法  滑動的就是顯示出來的字的內容。

一般而言 我們用scrollBy會比較多一些。傳值的話 其實 記住幾個法則就可以了。 右-左 x為正 否則x為負  上-下 y為負,否則y為正。

可以稍微看一下 這2個的源碼:

 1 public void scrollTo(int x, int y) {
 2         if (mScrollX != x || mScrollY != y) {
 3             int oldX = mScrollX;
 4             int oldY = mScrollY;
 5             mScrollX = x;
 6             mScrollY = y;
 7             invalidateParentCaches();
 8             onScrollChanged(mScrollX, mScrollY, oldX, oldY);
 9             if (!awakenScrollBars()) {
10                 postInvalidateOnAnimation();
11             }
12         }
13     }
14 
15  public void scrollBy(int x, int y) {
16         scrollTo(mScrollX + x, mScrollY + y);
17     }
View Code

看到里面有2個變量 mScrollX 和mScrollY 這2個東西沒,這2個單位的 值是像素,前者代表 view的左邊緣和view內容左邊緣的距離。 后者代表 view上邊緣和view內容上邊緣的距離。

 

5.使用動畫來實現view的滑動 有什么后果?

答:實際上view動畫 是對view的表面ui 也就是給用戶呈現出的視覺效果 來做的移動,動畫本身並不能移動view的真正位置。屬性動畫除外。動畫播放結束以后,view最終還是會

回到自己的位置的,。當然了你可以設置fillafter 屬性 來讓動畫播放結束以后 view表象停留在 變化以后的位置。所以這會帶來一個很嚴重的后果。比如你的button在屏幕的左邊,

你現在用個動畫 並且設置了fillafter屬性讓他去了右邊。你會發現 點擊右邊的button 沒有click事件觸發,但是點擊左邊的 卻可以觸發,原因就是右邊的button 只是view的表象,

真正的button 還在左邊沒有動過。你一定要這么做的話 可以提前在右邊button移動后的位置放一個新的button,當你動畫執行結束以后  把右邊的enable 左邊的讓他gone就可以了。

這么做就可以規避上述問題。

 

6.讓view滑動總共有幾種方式,分別要注意什么?都適用於那些場景?

答:總共有三種:

a:scrollto,scrollby。這種是最簡單的,但是只能滑動view的內容 不可以滑動view本身。

b:動畫。動畫可以滑動view內容,但是注意非屬性動畫 就如我們問題5說的內容 會影響到交互,使用的時候要多注意。不過多數復雜的滑動效果都是屬性動畫來完成的,屬於大殺器級別、

c:改變布局參數。這種最好理解了,無非是動態的通過java代碼來修改 margin等view的參數罷了。不過用的比較少。我本人不怎么用這種方法。

 

7.Scroller是干嘛的?原理是什么?

答:Scroller就是用於 讓view有滑動漸變效果的。用法如下:

 1 package com.example.administrator.motioneventtest;
 2 
 3 import android.content.Context;
 4 import android.util.AttributeSet;
 5 import android.widget.Scroller;
 6 import android.widget.TextView;
 7 
 8 /**
 9  * Created by Administrator on 2016/2/2.
10  */
11 public class CustomTextView extends TextView{
12 
13     private Scroller mScroller;
14 
15 
16     public CustomTextView(Context context) {
17         super(context);
18         mScroller=new Scroller(context);
19     }
20 
21     public CustomTextView(Context context, AttributeSet attrs) {
22         super(context, attrs);
23         mScroller=new Scroller(context);
24 
25     }
26 
27     public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr) {
28         super(context, attrs, defStyleAttr);
29         mScroller=new Scroller(context);
30 
31     }
32 
33     //調用此方法滾動到目標位置
34     public void smoothScrollTo(int fx, int fy) {
35         int dx = fx - mScroller.getFinalX();
36         int dy = fy - mScroller.getFinalY();
37         smoothScrollBy(dx, dy);
38     }
39 
40     //調用此方法設置滾動的相對偏移
41     public void smoothScrollBy(int dx, int dy) {
42 
43         //設置mScroller的滾動偏移量
44         mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy,4000);
45         invalidate();//這里必須調用invalidate()才能保證computeScroll()會被調用,否則不一定會刷新界面,看不到滾動效果
46     }
47 
48     //使用scroller最重要不要遺漏這個方法
49     @Override
50     public void computeScroll() {
51         if (mScroller.computeScrollOffset())
52         {
53             scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
54             //這個方法不要忘記調用。
55             postInvalidate();
56         }
57         super.computeScroll();
58     }
59 }
View Code

其實上述代碼 很多人應該都能搜到。我們這里主要講一下 他的原理。

  1 //參數很好理解 前面滑動起始點 中間滑動距離 最后一個是 漸變時間
  2 //而且我們看到startScroll 這個方法就是設置了一下參數 並沒有什么滑動的代碼在
  3 //回到前面的demo能看到我們通常調用完這個方法以后 都會馬上調用invalidate()方法
  4  public void startScroll(int startX, int startY, int dx, int dy, int duration) {
  5         mMode = SCROLL_MODE;
  6         mFinished = false;
  7         mDuration = duration;
  8         mStartTime = AnimationUtils.currentAnimationTimeMillis();
  9         mStartX = startX;
 10         mStartY = startY;
 11         mFinalX = startX + dx;
 12         mFinalY = startY + dy;
 13         mDeltaX = dx;
 14         mDeltaY = dy;
 15         mDurationReciprocal = 1.0f / (float) mDuration;
 16     }
 17 
 18 
 19 //我們都知道invalidate 會觸發view的 draw方法 
 20 //我們跟進去看 會發現draw方法里 會調用下面的代碼:
 21 //也就是說會調用    computeScroll方法 而view本身這個方法
 22 //是空的所以會留給我們自己實現
 23  int sx = 0;
 24         int sy = 0;
 25         if (!drawingWithRenderNode) {
 26             computeScroll();
 27             sx = mScrollX;
 28             sy = mScrollY;
 29         }
 30 
 31 
 32 //然后回到我們的customtextview 可以看到我們實現的    computeScroll方法如下:
 33 //你看在這個方法里 我們調用了scrollTo方法 來實現滑動,滑動結束以后再次觸發view的重繪
 34 //然后又會再次觸發computeScroll 實現一個循環。
 35 public void computeScroll() {
 36         if (mScroller.computeScrollOffset())
 37         {
 38             scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
 39             //這個方法不要忘記調用。
 40             postInvalidate();
 41         }
 42         super.computeScroll();
 43     } 
 44 
 45 
 46 //返回true就代表滑動還沒結束 false就是結束了
 47 //其實這個方法 就跟屬性動畫里的插值器一樣 你在使用startScroll方法的時候 會傳一個事件的值,
 48 //這個方法就是根據這個事件的值來計算你每一次scrollx和scrolly的值
 49 public boolean computeScrollOffset() {
 50         if (mFinished) {
 51             return false;
 52         }
 53 
 54         int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
 55     
 56         if (timePassed < mDuration) {
 57             switch (mMode) {
 58             case SCROLL_MODE:
 59                 final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
 60                 mCurrX = mStartX + Math.round(x * mDeltaX);
 61                 mCurrY = mStartY + Math.round(x * mDeltaY);
 62                 break;
 63             case FLING_MODE:
 64                 final float t = (float) timePassed / mDuration;
 65                 final int index = (int) (NB_SAMPLES * t);
 66                 float distanceCoef = 1.f;
 67                 float velocityCoef = 0.f;
 68                 if (index < NB_SAMPLES) {
 69                     final float t_inf = (float) index / NB_SAMPLES;
 70                     final float t_sup = (float) (index + 1) / NB_SAMPLES;
 71                     final float d_inf = SPLINE_POSITION[index];
 72                     final float d_sup = SPLINE_POSITION[index + 1];
 73                     velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
 74                     distanceCoef = d_inf + (t - t_inf) * velocityCoef;
 75                 }
 76 
 77                 mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
 78                 
 79                 mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
 80                 // Pin to mMinX <= mCurrX <= mMaxX
 81                 mCurrX = Math.min(mCurrX, mMaxX);
 82                 mCurrX = Math.max(mCurrX, mMinX);
 83                 
 84                 mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
 85                 // Pin to mMinY <= mCurrY <= mMaxY
 86                 mCurrY = Math.min(mCurrY, mMaxY);
 87                 mCurrY = Math.max(mCurrY, mMinY);
 88 
 89                 if (mCurrX == mFinalX && mCurrY == mFinalY) {
 90                     mFinished = true;
 91                 }
 92 
 93                 break;
 94             }
 95         }
 96         else {
 97             mCurrX = mFinalX;
 98             mCurrY = mFinalY;
 99             mFinished = true;
100         }
101         return true;
102     }
103     
View Code

 

8.view的滑動漸變效果總共有幾種方法?

答:三種,第一種是scroller 也是使用最多的。問題7里有解釋。還有一種就是動畫,動畫我就不多說了,不屬於本文范疇。最后一種也是我們經常使用的就是用handler ,每隔一個時間間隔 來更新view的狀態。

代碼不寫了很簡單。 自行體會。

 

9.view的事件傳遞機制 如何用偽代碼來表示?

答:

 1 /**
 2      * 對於一個root viewgroup來說,如果接受了一個點擊事件,那么首先會調用他的dispatchTouchEvent方法。
 3      * 如果這個viewgroup的onInterceptTouchEvent 返回true,那就代表要攔截這個事件。接下來這個事件就
 4      * 給viewgroup自己處理了,從而viewgroup的onTouchEvent方法就會被調用。如果如果這個viewgroup的onInterceptTouchEvent
 5      * 返回false就代表我不攔截這個事件,然后就把這個事件傳遞給自己的子元素,然后子元素的dispatchTouchEvent
 6      * 就會被調用,就是這樣一個循環直到 事件被處理。
 7      *
 8      */
 9 public boolean dispatchTouchEvent(MotionEvent ev)
10 {
11     boolean consume=false;
12     if (onInterceptTouchEvent(ev)) {
13         consume=onTouchEvent(ev);
14     }else
15     {
16         consume=child.dispatchTouchEvent(ev);
17     }
18     return consume;
19 }
View Code

 

10.view的onTouchEvent,OnClickListerner和OnTouchListener的onTouch方法 三者優先級如何?

答:onTouchListener優先級最高,如果onTouch方法返回 false ,那onTouchEvent就被調用了,返回true 就不會被調用。至於onClick 優先級最低。

 

11.點擊事件的傳遞順序如何?

答:Activity-Window-View。從上到下依次傳遞,當然了如果你最低的那個view onTouchEvent返回false 那就說明他不想處理 那就再往上拋,都不處理的話

最終就還是讓Activity自己處理了。舉個例子,pm下發一個任務給leader,leader自己不做 給架構師a,小a也不做 給程序員b,b如果做了那就結束了這個任務。

b如果發現自己搞不定,那就找a做,a要是也搞不定 就會不斷向上發起請求,最終可能還是pm做。

 1  
 2 //activity的dispatchTouchEvent 方法 一開始就是交給window去處理的
 3 //win的superDispatchTouchEvent 返回true 那就直接結束了 這個函數了。返回false就意味
 4 //這事件沒人處理,最終還是給activity的onTouchEvent 自己處理 這里的getwindow 其實就是phonewindow
 5  public boolean dispatchTouchEvent(MotionEvent ev) {
 6         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
 7             onUserInteraction();
 8         }
 9         if (getWindow().superDispatchTouchEvent(ev)) {
10             return true;
11         }
12         return onTouchEvent(ev);
13     }
14 
15 
16 //來看phonewindow的這個函數 直接把事件傳遞給了mDecor
17 
18  @Override
19     public boolean superDispatchTouchEvent(MotionEvent event) {
20         return mDecor.superDispatchTouchEvent(event);
21     }
22 
23 //devorview就是 我們的rootview了 就是那個framelayout 我們的setContentView里面傳遞的那個layout
24 //就是這個decorview的 子view了
25      @Override
26     public final View getDecorView() {
27         if (mDecor == null) {
28             installDecor();
29         }
30         return mDecor;
31     }
View Code

 

 

12.事件分為幾個步驟?

答:down事件開頭,up事件結尾,中間可能會有數目不定的move事件。

 

13.ViewGroup如何對點擊事件分發?

答:

 1 viewgroup就是在actionMasked == MotionEvent.ACTION_DOWN 和 mFirstTouchTarget != null 這兩種情況來判斷是否會進入攔截事件的流程
 2 
 3 看代碼可以知道 如果是ACTION_DOWN事件  那就肯定進入 是否要攔截事件的流程
 4 
 5 如果不是ACTION_DOWN事件 那就要看mFirstTouchTarget != null 這個條件是否成立
 6 
 7 這個地方有點繞但是也好理解,其實就是 對於一個事件序列來說 down是事件的開頭 所以肯定進入了這個事件是否攔截的流程 也就是if 括號內。
 8 
 9 
10 mFirstTouchTarget其實是一個單鏈表結構他指向的是 成功處理事件的子元素。
11 
12 也就是說 如果有子元素成功處理了 事件,那這個值就不為NULL。反過來說
13 
14 只要viewgroup攔截了事件,mFirstTouchTarget就不為NULL,所以括號內就不會執行,也就側面說明了一個結論:
15 
16 某個view 一旦決定攔截事件,那么這個事件所屬的事件序列 都只能由他來執行。並且onInterceptTouchEvent 這個方法不會被調用了
17 
18             final boolean intercepted;
19             if (actionMasked == MotionEvent.ACTION_DOWN
20                     || mFirstTouchTarget != null) {
21                 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
22                 if (!disallowIntercept) {
23                     intercepted = onInterceptTouchEvent(ev);
24                     ev.setAction(action); // restore action in case it was changed
25                 } else {
26                     intercepted = false;
27                 }
28             } else {
29                 // There are no touch targets and this action is not an initial down
30                 // so this view group continues to intercept touches.
31                 intercepted = true;
32             }
View Code

 

14.如果某個view 處理事件的時候 沒有消耗down事件 會有什么結果?

答:假如一個view,在down事件來的時候 他的onTouchEvent返回false, 那么這個down事件 所屬的事件序列 就是他后續的move 和up 都不會給他處理了,全部都給他的父view處理。

 

15.如果view 不消耗move或者up事件 會有什么結果?

答:那這個事件所屬的事件序列就消失了,父view也不會處理的,最終都給activity 去處理了。

 

16.ViewGroup 默認攔截事件嗎?

答:默認不攔截任何事件,onInterceptTouchEvent返回的是false。

 

17.一旦有事件傳遞給view,view的onTouchEvent一定會被調用嗎?

答:是的,因為view 本身沒有onInterceptTouchEvent方法,所以只要事件來到view這里 就一定會走onTouchEvent方法。

並且默認都是消耗掉,返回true的。除非這個view是不可點擊的,所謂不可點擊就是clickable和longgclikable同時為fale

Button的clickable就是true 但是textview是false。

 

18.enable是否影響view的onTouchEvent返回值?

答:不影響,只要clickable和longClickable有一個為真,那么onTouchEvent就返回true。

 

19.requestDisallowInterceptTouchEvent 可以在子元素中干擾父元素的事件分發嗎?如果可以,是全部都可以干擾嗎?

答:肯定可以,但是down事件干擾不了。

 

20.dispatchTouchEvent每次都會被調用嗎?

答:是的,onInterceptTouchEvent則不會。

 

21.滑動沖突問題如何解決 思路是什么?

答。要解決滑動沖突 其實最主要的就是有一個核心思想。你到底想在一個事件序列中,讓哪個view 來響應你的滑動?比如 從上到下滑,是哪個view來處理這個事件,從左到右呢?

用業務需求 來想明白以后 剩下的 其實就很好做了。核心的方法 就是2個 外部攔截也就是父親攔截,另外就是內部攔截,也就是子view攔截法。 學會這2種 基本上所有的滑動沖突

都是這2種的變種,而且核心代碼思想都一樣。

外部攔截法:思路就是重寫父容器的onInterceptTouchEvent即可。子元素一般不需要管。可以很容易理解,因為這和android自身的事件處理機制 邏輯是一模一樣的

 1  @Override
 2     public boolean onInterceptTouchEvent(MotionEvent ev) {
 3 
 4         boolean intercepted = false;
 5         int x = (int) ev.getX();
 6         int y = (int) ev.getY();
 7 
 8         switch (ev.getAction()) {
 9             //down事件肯定不能攔截 攔截了后面的就收不到了
10             case MotionEvent.ACTION_DOWN:
11                 intercepted = false;
12                 break;
13             case MotionEvent.ACTION_MOVE:
14                 if (你的業務需求) {
15                     //如果確定攔截了 就去自己的onTouchEvent里 處理攔截之后的操作和效果 即可了
16                     intercepted = true;
17                 } else {
18                     intercepted = false;
19                 }
20                 break;
21             case MotionEvent.ACTION_UP:
22                 //up事件 我們一般都是返回false的 一般父容器都不會攔截他。 因為up是事件的最后一步。這里返回true也沒啥意義
23                 //唯一的意義就是因為 父元素 up被攔截。導致子元素 收不到up事件,那子元素 就肯定沒有onClick事件觸發了,這里的
24                 //小細節 要想明白
25                 intercepted = false;
26                 break;
27             default:
28                 break;
29         }
30         return intercepted;
31     }
View Code

內部攔截法:內部攔截法稍微復雜一點,就是事件到來的時候,父容器不管,讓子元素自己來決定是否處理。如果消耗了 就最好,沒消耗 自然就轉給父容器處理了。

子元素代碼:

 1  @Override
 2     public boolean dispatchTouchEvent(MotionEvent event) {
 3         int x = (int) event.getX();
 4         int y = (int) event.getY();
 5         switch (event.getAction()) {
 6             case MotionEvent.ACTION_DOWN:
 7                 getParent().requestDisallowInterceptTouchEvent(true);
 8                 break;
 9             case MotionEvent.ACTION_MOVE:
10                 if (如果父容器需要這個點擊事件) {
11                     getParent().requestDisallowInterceptTouchEvent(false);
12                 }//否則的話 就交給自己本身view的onTouchEvent自動處理了
13                 break;
14             case MotionEvent.ACTION_UP:
15                 break;
16             default:
17                 break;
18         }
19         return super.dispatchTouchEvent(event);
20     }
View Code

父親容器代碼也要修改一下,其實就是保證父親別攔截down:

1  @Override
2     public boolean onInterceptTouchEvent(MotionEvent ev) {
3 
4         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
5             return false;
6 
7         }
8         return true;
9     }
View Code


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM