關於Android中TextView的setText()問題?

當調用TextView.setText(CharSequence text)之後會調用一次TextView.onDraw(Canvas canvas)方法。

例如當我點擊一次按鈕時則調用TextView.setText(CharSequence text)方法給TextView設置文本,此時會調用一次TextView.onDraw(Canvas canvas)。

代碼如下:

這樣每次我點擊按鈕時textView都會調用一次onDraw(Canvas canvas)方法。

但是當我使用for循環來多次調用setText(CharSequence text)卻不會多次調用onDraw(Canvas canvas)方法。

例如這樣:

或者這樣時:

都只會調用一次onDraw(Canvas canvas)方法,請問大家為什麼會產生這種現象?


在一個循環內或者是連續的調用view.setText(),每一次的調用setText都會觸發view的invalidate()函數,然而invalidate()函數並非調用了一次就會立即執行一次繪製操作(參考答案Why isn"t view.invalidate immediately redrawing the screen in my android game),而是需要等UI線程完成了現有的work後才會來執行繪製操作;而三次的invalidate()操作也會被優化成一個命令來完成。

=====update

剛才查看了一下老羅的博客,裡面有講到:

每一個View都被抽象成了一個RenderNode,而每一個node都關聯有一個Display List Renderer。DisplayList是一個繪製命令緩衝區。也就是說,當View的成員函數onDraw被調用時,我們調用通過參數傳遞進來的Canvas的drawXXX成員函數繪製圖形時,我們實際上只是將對應的繪製命令以及參數保存在一個List中。接下來再通過DisplayList Renderer執行這個List中的繪製命令。

引進DisplayList的概念主要是兩個好處:第一個好處是在下一幀繪製中,如果一個View的內容不需要更新,那麼就不用重建DisplayList,也就是不需要調用它的onDraw成員函數。第二個好處是在下一幀中,如果一個View僅僅是一些簡單的屬性發生變化,例如位置和text值發生變化,也不需要重建它的DisplayList,只需要在上一次建立的Display List中修改一下對應的屬性就可以了,這也意味著不需要調用它的onDraw成員函數。這兩個好處使用在繪製應用程序窗口的一幀時,省去很多應用程序代碼的執行,也就是大大地節省了CPU的執行時間。

鏈接地址: http://blog.csdn.net/luoshengyang/article/details/45601143


首先你的說法是錯的。調用setText並不會直接調用onDraw,調用的是requestLayout。而requestLayout只是給TextView設置了一個標記而已,要等到ViewRootImpl的下一次traversal才會實際對TextView做測量和更新繪製並清除標記,所以你在下一次的traversal前不管做多少次的setText都只會執行一次onDraw

PS:有空可以看看源碼,很有幫助


消息循環機制,簡單抽象如下:

Looper 取出 Click 的處理子程序,在主線程執行之;

子程序進行一系列操作,包括對 UI 的操作;

每次 setText 方法調用都會向消息列隊中 post 一個 invalidate 通知,標記一塊區域為臟區域;

Looper 執行完上述子程序,繼續取消息,得到 invalidate 消息,這裡是 onDraw 被調用的發源地,onDraw 之後那塊區域不是臟區域了,遂後續 onDraw 調用不會產生。


感覺很好理解吧,同一次UI繪製(你寫for循環或者手動set3次都算在同一次UI繪製過程中),你給同一個textview對象賦值,只有最後一個會生效,所以只走了最後一個ondraw一次

你可以timer寫一個延時post到handler,然後再給這個對象settext,應該就是每次都在走ondraw,因為它每次UI繪製不是同一次


謝邀,你試試每次setText時改變文字內容。

估計是因為內容一樣所以沒繪製。

==== 更新 ====

估計是你太快,刷新趕不上。可能Android控制項是被動刷新


setText會引起繪製,每次繪製是需要向Looper發消息的,放到消息隊列的尾部。等到輪詢到當前消息時才會調用到繪製的方法。如果你發送多個繪製消息,也只是重置繪製的Flag位。而不是立即就執行繪製方法。並且在ViewRootImpl裡面,有標誌位來判斷是否已經有繪製進來,有的話就不會在做處理了。

兩個例子:

1.第二個,第三個setText你放到Handler.post裡面去,你看onDraw會執行幾次?應該是三次

2.在setText之後列印一個Log.(A),在onDraw裡面列印一個Log.(B),你覺得會先列印哪個?應該是A先列印。

update-----------------------------

看了上面答案,基本沒說到點子上


刷新有個時間間隔吧,16.66ms,

你連著輸入三次settext,

settext 123

settext 456

settext 789

實際上只刷新了最後一個settext 789

因為很明顯的,你要在主線程調用settext

而刷新也即是ondraw也要在主線程里調用

所以最終的執行順序肯定是先執行了3次settext

再執行ondraw

所以即便調用了3次ondraw,它也不會依次刷新123 456 789

而且只能刷新三次789,因為textview裡面在你最後一次settext 789的時候早已把123 456給覆蓋了

所以,既然如此,android系統它又何必刷新三次789呢,一次不就好了


invalidate這個單詞很有意思,它的中文翻譯是「使無效」而不是「刷新」,這兩個意思幾乎是天壤之別。「使無效」是一個被動的過程,只是把view的某個屬性改變,比如繪製區域變得dirty。而「刷新」是一個主動過程。

Android view在繪製的時候是一個被動過程,等到消息隊列循環到刷新view命令的時候,就去檢查那些標誌位有沒有變化,這一次命令就是一次刷新過程,等下一個16ms再檢查一次(刷新一次)。

所以雖然快速設置了三次text,實際只有一次檢查(16ms以內)。

Google工程師方法名起的真幾把好。。。。。


推薦閱讀:

Android studio如何使用SVN進行版本控制?
Android Studio 比 Eclipse 好用在哪裡?
okhttp,retrofit,android-async-http,volley應該選擇哪一個?

TAG:Android開發 | Java | Java編程 | Android | AndroidStudio |