文章目錄
- 前言
- 一、為什么樣式會變?
- 二、調試發現原因并解決
- 1.找到原因
- 2.解決
- 總結
前言
最近在負責一些UI相關的工作,測試給到一個UI的bug,說是搜索框在點擊的時候,旁邊的’‘X’'變成按壓的效果了,我轉手就把bug轉給負責公控的同事了,因為這個搜索框是公控同事提供的,但是公控大佬不一會就找到我說,我這里都是好的鴨,是不是你哪里沒用對,我直接就暈乎了,我左看右看也沒干啥鴨,于是開始對線,巴拉巴拉…但是問題還是沒有解決,于是有了這篇文章.
一、為什么樣式會變?
首先公控大佬給我講了他是怎么做X的按壓效果的,就是說監聽了搜索框的onTouchEvent事件,然后當onTouchEvent傳入的rect包含了X的時候,將其設置為點按效果,其他時候是不會對這個X控件的樣式進行改變的,沒辦法,我只好將公控同事的源碼要來,加上日志調試.
二、調試發現原因并解決
1.找到原因
加了日志發現代碼并沒有走到公控同事的代碼邏輯當中,但是進入onTouchEvent事件時,X控件的樣式已經變成點按狀態了,而公控同事的demo項目,X控件的樣式確實并沒有改變,鑒定為G.最后監聽setPressed的時候發現在點擊控件時,會觸發 setPressed = true 事件,隨后分析VIEW 源碼,定位到如下代碼位置:
case MotionEvent.ACTION_DOWN:.....這里忽略部分代碼// Walk up the hierarchy to determine if we're inside a scrolling container.boolean isInScrollingContainer = isInScrollingContainer();// For views inside a scrolling container, delay the pressed feedback for// a short period in case this is a scroll.if (isInScrollingContainer) {mPrivateFlags |= PFLAG_PREPRESSED;if (mPendingCheckForTap == null) {mPendingCheckForTap = new CheckForTap();}mPendingCheckForTap.x = event.getX();mPendingCheckForTap.y = event.getY();postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());} else {// Not inside a scrolling container, so show the feedback right away setPressed(true, x, y);checkForLongClick(ViewConfiguration.getLongPressTimeout(),x,y,TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);}break;
在View.java的源碼當中的onTouchEvent方法的MotionEvent.ACTION_DOWN事件,有一個 isInScrollingContainer的醒目判斷,并加上了注釋,我們繼續看下isInScrollingContainer方法
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)public boolean isInScrollingContainer() {ViewParent p = getParent();while (p != null && p instanceof ViewGroup) {if (((ViewGroup) p).shouldDelayChildPressedState()) {return true;}p = p.getParent();}return false;}
可以看到會判斷一個shouldDelayChildPressedState方法,然后返回true,到這里基本可以確定這就是我和公控同事觸發的不一致的地方,這是一個ViewGroup的方法,點進去過后注釋是這樣寫的:
Return true if the pressed state should be delayed for children or
descendants of this ViewGroup. Generally, this should be done for
containers that can scroll, such as a List. This prevents the pressed
state from appearing when the user is actually trying to scroll the
content. The default implementation returns true for compatibility
reasons. Subclasses that do not scroll should generally override this
method and return false.
我們機翻一下:如果應延遲此ViewGroup的子代或子代的按下狀態,則返回true。通常,這應該針對可以滾動的容器(如List)進行。這防止了當用戶實際試圖滾動內容時出現按下狀態。由于兼容性原因,默認實現返回true。不滾動的子類通常應該覆蓋此方法并返回false。
2.解決
到了上面這里,我們基本可以明白,這是因為我們搜索框公控的父布局使用的可滑動的控件,導致進入isInScrollingContainer返回true,并觸發了setPressed方法,然后改變了控件的樣式 為點按狀態,針對這個問題,我們有這幾種方式可以解決.
- 重寫公控的這個控件的setPressed方法,每次都返回false
- 重寫父布局的shouldDelayChildPressedState方法,使其返回false
- 更改公控代碼對 這種特殊情況做兼容
前面兩種方法改的快,但是不能確保不出其它問題,因此這個問題最后還是由公控大佬對這種情況做兼容,over
總結
通過調試和分析源碼,我們找到了問題的原因和解決方法。問題是因為在可滑動的父布局中,觸發了setPressed方法,導致控件樣式變為點按狀態。解決方法有重寫setPressed方法、重寫shouldDelayChildPressedState方法或者對公控代碼做兼容處理。選擇合適的解決方法,能夠解決這個UI bug。