點擊ViewGroup時其子控件也變成pressed狀態的緣由分析及解決辦法

  這個問題,當初在分析touch事件處理的時候按理應該分析到的,但是因爲我當時以爲這塊代碼和touch的主題不是那麼緊密,android

就這麼忽略掉了,直到後來在這上面遇到了問題。其實這個現象作Android開發的應該或多或少的都遇到過,我在咱們本身的app中app

也發現了這一現象,當初是百思不得其解,由於按照我本身的研究、分析,只有在一個view接受按下的touch事件時,纔會調到viewide

本身的setPressed方法,從而改變background狀態啊。這裏的case明顯沒有按下這個子view啊,按下的是ViewGroup啊,因此一直this

沒想通,當時也就沒多想,就這麼不明不白的過去了(如今回過頭來想,其實只要在android src中搜索下setPressed方法都在哪調用了,spa

這個問題就迎刃而解了)。直到有一天,咱們的公司的Review中又再次遇到了相關的問題,巧合的是咱們的這個fix把原先的bug解了,但.net

不幸的是引入了本文的這個問題。問題又一次出現了,同時咱們的另外一名同事給我看了段ViewGroup裏的代碼,我當時的第一感受是又興奮code

又驚訝。興奮是困擾我許久的問題終於有答案了,驚訝是不太能理解爲啥Android會寫這樣一段在我看來有點「多管閒事」的代碼。好了,故事blog

背景交代的差很少了,下面直接進入主題。遞歸

  對touch事件有了解的同窗大致都知道,View.setPressed(true)調用發生在View.onTouchEvent方法中,視狀況不一樣要麼是DOWN事件

事件、或者DOWN事件推遲TAP_TIMEOUT毫秒以後,要麼是UP事件處理的時候。咱們來看下相關源碼:

    /**
     * Sets the pressed state for this view.
     *
     * @see #isClickable()
     * @see #setClickable(boolean)
     *
     * @param pressed Pass true to set the View's internal state to "pressed", or false to reverts
     *        the View's internal state from a previously set "pressed" state.
     */
    public void setPressed(boolean pressed) {
// 檢查pressed狀態是否改變
final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED); if (pressed) { // 設置標誌位 mPrivateFlags |= PFLAG_PRESSED; } else { mPrivateFlags &= ~PFLAG_PRESSED; } if (needsRefresh) { // 狀態改變了,那麼須要刷新下drawable state,在這裏background會變成合適的樣子 refreshDrawableState(); } dispatchSetPressed(pressed); // 將pressed狀態傳遞到子view } /** * Dispatch setPressed to all of this View's children. * * @see #setPressed(boolean) * * @param pressed The new pressed state */ protected void dispatchSetPressed(boolean pressed) { // view的默認是do nothing,由於它也沒children啊 } // ViewGoup對dispatchSetPressed的重載 @Override protected void dispatchSetPressed(boolean pressed) { // 這是ViewGroup對其的實現 final View[] children = mChildren; final int count = mChildrenCount; for (int i = 0; i < count; i++) { final View child = children[i]; // Children that are clickable on their own should not // show a pressed state when their parent view does. // Clearing a pressed state always propagates. 注意理解這段註釋 if (!pressed || (!child.isClickable() && !child.isLongClickable())) {
// 其實這個if主要是作了2件事,1若是pressed是false則老是調用child的setPressed(false)方法,也就是說這種
// 狀況下always傳遞;2若是pressed是true,則只有child不能響應touch事件即clickable、longClickable都是false
// 的時候才調用child.setPressed(true)方法。說白了,child本身能處理pressed事件則他們本身會處理,不然parent會
// 強迫child處理。同時注意下,這個調用仍是遞歸的,會一直傳遞給子孫後代。。。
child.setPressed(pressed); } } }

  另外須要提1點就是直到Android4.1.x開始ViewGroup.dispatchSetPressed方法纔在遍歷children時加了這個if判斷,也就是說以前

的版本會直接調用child.setPressed(pressed)方法。從上面的分析能夠看出,因爲這裏parent「強迫」child處理pressed事件,因此爲了避

免出現本文一開始的現象(或者叫bug),你有2種方式:

1. 讓某個view本身能處理touch事件,也即設置clickable、longClickable爲true;

2. 若是某個view本身不處理touch事件,那麼你就不該該給它設置個stateful的drawable,或者至少不該該給它pressed狀態設置一個單獨

的drawable,這樣即便發生了setPressed(true)調用,也不要緊。

咱們代碼裏,選用了第2種解決方式,直接在代碼中去掉了其background。這篇小文章算是對前面touch事件處理的補充也是開發過程當中的

經驗、教訓,但願對你們有所幫助,enjoy。。。

  最後,附上一篇csdn的同主題文章:http://blog.csdn.net/cpyyes/article/details/11144497

相關文章
相關標籤/搜索