屏幕繪圖基礎(chǔ)
Android 中的GUI系統(tǒng)是客戶端和服務(wù)端配合的窗口系統(tǒng),即后臺(tái)運(yùn)行了一個(gè)繪制服務(wù),每個(gè)應(yīng)用程序都是該服務(wù)端的一個(gè)客戶端,當(dāng)客戶需要繪制時(shí),首先請(qǐng)求服務(wù)端創(chuàng)造一個(gè)窗口,然后在窗口中進(jìn)行具體的試圖內(nèi)容繪制;對(duì)于每個(gè)客戶而而言,他們都感覺(jué)自己獨(dú)占了屏幕,而對(duì)于服務(wù)端而言,它會(huì)給每一個(gè)客戶端窗口分配不同的層值,并根據(jù)用戶的交互情況動(dòng)態(tài)改變窗口的層值,這就給用戶造成了所謂的前臺(tái)窗口和后臺(tái)窗口的概念:
Android的屏幕繪制架構(gòu)如下圖:
- SufaceFlinger進(jìn)程:簡(jiǎn)稱(chēng)sf,該進(jìn)程在系統(tǒng)開(kāi)機(jī)時(shí)自動(dòng)啟動(dòng),在init.rc中定義,他的作用就是給每個(gè)客戶端分配窗口,在程序中用Surface類(lèi)來(lái)表示這個(gè)窗口,即每個(gè)窗口都是一個(gè)平面,而每個(gè)平面都在程序中都會(huì)對(duì)應(yīng)一塊屏幕緩沖區(qū).
- SystemServer進(jìn)程:前面已經(jīng)重點(diǎn)講了該進(jìn)程的相關(guān)信息了,他的zygote進(jìn)程孵化出的第一個(gè)進(jìn)程,里面加載的是所有android系統(tǒng)運(yùn)行所需的各種系統(tǒng)服務(wù),當(dāng)然最重要的就是AMS和WMS,而WMS正是屏幕繪圖服務(wù)端最重要的窗口管理服務(wù),客戶端應(yīng)用程序要請(qǐng)求窗口展示直接與WMS打交道,然后WMS進(jìn)行相關(guān)的窗口管理,并通過(guò)sf的客戶端驅(qū)動(dòng)接口和sf打交道,從而完成窗口內(nèi)容的繪制:
- Surface/Cancas類(lèi):主要用來(lái)記錄窗口的寬,高,位置,層值等相關(guān)信息,WMS就是用他來(lái)和sf客戶端驅(qū)動(dòng)打交道(新版本中實(shí)際是通過(guò)SurfaceSession來(lái)作為紐帶)具體的是當(dāng)Surface初始化時(shí)會(huì)通過(guò)sf客戶端接口創(chuàng)建真正窗口需要的屏幕緩沖區(qū);完成之后應(yīng)用程序通過(guò)lockCanvas()來(lái)獲得一個(gè)Canvas對(duì)象,再之后便可以調(diào)用各式各樣的api完成具體內(nèi)容的繪制;
注意:Suface對(duì)象并不能由應(yīng)用程序直接初始化,他只對(duì)SDK內(nèi)部開(kāi)放,主要在ViewRootImpl中創(chuàng)建;不過(guò)android系統(tǒng)提供了一個(gè)SufaceVIew可以讓?xiě)?yīng)用程序來(lái)間接的使用suface; - Skia圖形庫(kù):用C/C++編寫(xiě)的圖形驅(qū)動(dòng)庫(kù),用來(lái)完成各種平面繪制,Canvas類(lèi)中的各種drawXXX方法實(shí)際上都是交由Skia庫(kù)執(zhí)行的;
Cannas/Drawable/Paint的關(guān)系與區(qū)別
Canvas:畫(huà)布,所有的繪圖都需要經(jīng)過(guò)他在調(diào)用底層Skia完成具體繪制,畫(huà)布一般是通過(guò)Sueface完成屏幕緩沖區(qū)初始化之后獲??;
Drawable:一個(gè)抽象類(lèi),其子類(lèi)代表某個(gè)特定的圖案,他僅僅是一個(gè)功能類(lèi),無(wú)法完成具體的繪圖工作,但是它提供了一個(gè)abstact方法draw(Canvas canvas),通過(guò)他直接轉(zhuǎn)交給canvas實(shí)例處理,你可以根據(jù)需要實(shí)現(xiàn)的自定義的Drawable;
Paint:畫(huà)筆,用來(lái)保存圖案繪制時(shí)所用的顏色,樣式(是否有陰影,粗細(xì),圓角,字體,對(duì)齊方式等),一般在canvas.drawXXX中作為參數(shù)進(jìn)行使用;
View樹(shù)遍歷
View狀態(tài)的分類(lèi)
在View視圖的定義了多種和界面效果相關(guān)的狀態(tài),比如擁有焦點(diǎn)Focused,按下Pressed等,不同的狀態(tài)一般回想時(shí)處不同的壓面效果,而視圖的狀態(tài)會(huì)因?yàn)橛脩舻牟僮鞫淖?,一般通過(guò)xml文件中的selector標(biāo)簽來(lái)申明不同狀態(tài)下的背景圖;所有的狀態(tài)碼位于StateListDrawable中,常用的狀態(tài)碼包括:
- enable:當(dāng)前View是否可用,這個(gè)狀態(tài)可由setEnable()改變,他完全有開(kāi)發(fā)者控制;當(dāng)狀態(tài)不可用時(shí),View將不會(huì)響應(yīng)任何事件.
- focuse:當(dāng)前View是否正擁有焦點(diǎn),一個(gè)窗口中只能有一個(gè)View擁有焦點(diǎn),一般隨用戶操作而主動(dòng)改變,該狀態(tài)主要是針對(duì)按鍵的,因?yàn)樗械陌存I消息都將派發(fā)給focusd視圖.
- pressed:當(dāng)前View是否正在被按下,主要是針對(duì)觸摸消息的,一般用戶按下視圖會(huì)有一個(gè)明顯的變化,也是隨用戶操作而動(dòng)態(tài)改變.
- selected:當(dāng)前View是否被選中,一個(gè)窗口中可以有多個(gè)視圖處于選中狀態(tài);開(kāi)發(fā)者可以通過(guò)setSelected()改變,他完全由開(kāi)發(fā)者控制.
導(dǎo)致View樹(shù)重新遍歷的總體誘因
遍歷View樹(shù)意味著整個(gè)View需要重新對(duì)其包含的子View分配大小并重繪;一般情況下導(dǎo)致重新遍歷的原因有三個(gè):其一,視圖內(nèi)部狀態(tài)發(fā)生變化,比如顯示屬性從GONE到VISIBLE;其二,ViewGroup中添加或刪除了視圖導(dǎo)致需要重新為子視圖分配位置,其三,視圖本身大小進(jìn)行變化,比如TextView中的文本內(nèi)容變多或者變少了;
在代碼層面這是三種情況最后都會(huì)直接或間接調(diào)用到View中的是三個(gè)函數(shù):requsetLayout / requestFucus / invalidate ;由于是View樹(shù)遍歷,所以最后都會(huì)執(zhí)行到最頂級(jí)父視圖中的ViewRootImpl.scheduleTraversals();該方法內(nèi),系統(tǒng)會(huì)發(fā)起一個(gè)異步消息,然后在異步消息執(zhí)行過(guò)程中performTraversals()完成具體的View樹(shù)遍歷.
View中超多的數(shù)形變量如何管理?
在龐大的View類(lèi)中涉及到非常多的狀態(tài)碼,比如是否可用,是否處于按壓狀態(tài),等等,View樹(shù)在遍歷重繪時(shí)會(huì)根據(jù)不同的變量值來(lái)進(jìn)行相應(yīng)的操作,為此View中引入了bit標(biāo)示位來(lái)管理.
其中mViewFlags變量主要用來(lái)保存和視圖狀態(tài)相關(guān)的值,比如是否可單擊,是否可雙擊,是否可用,是否擁有焦點(diǎn)等;
mPrivateFlages變量主要用來(lái)保存和內(nèi)部邏輯相關(guān)的屬性,比如是否需要重新分配位置,是否需要重繪,是否刷新View緩存
注意:這兩個(gè)變量之間是有緊密聯(lián)系的,經(jīng)常會(huì)需要兩個(gè)變量同時(shí)設(shè)置某些狀態(tài)值,可以參見(jiàn)setFlags()方法:
-
requestLayout()
該方法的執(zhí)行過(guò)程很簡(jiǎn)單,因?yàn)楫?dāng)View樹(shù)重新進(jìn)行布局時(shí),總是重新給所有視圖進(jìn)行布局,而不像重繪是可以指定只繪某一小區(qū)域的;
從代碼層面他只是為mPrivateFlags變量添加一個(gè)FORCE_LAYOUT標(biāo)識(shí)而已,然后逐層請(qǐng)求mParent.requestLayout();詳見(jiàn)下圖:
image -
invalidate()
該方法的作用是請(qǐng)求View樹(shù)重繪;視圖極其父視圖在界面上是層次先后顯示的,父視圖位于子視圖的下面,繪制過(guò)程中,首先繪制最底層的根視圖,然后繪制其包含的子視圖,子視圖若是ViewGroup,則繼續(xù)繪制其子視圖.如此迭代至沒(méi)有子視圖為止;
在具體的重繪過(guò)程中,一般不會(huì)對(duì)所有視圖都進(jìn)行重繪,而只是繪制那些需要繪制的視圖,那如何找到需要繪制的區(qū)域呢?這就是invalidate方法要完成的事情.
大致的思路是:當(dāng)View需要重繪時(shí)會(huì)給mPrivateFlags變量添加DRAWN表示,然后根據(jù)帶有該標(biāo)識(shí)的邊界一起確定最終要繪制的舉行區(qū)塊,
image.png
代碼的具體執(zhí)行過(guò)程:
- View.invalide()中設(shè)置必要的狀態(tài)標(biāo)識(shí)之后,會(huì)執(zhí)行到mParent.invalidateChild();這里的mParent有兩種情況,一種是有父視圖ViewGroup,另一種是已經(jīng)到頂層為ViewRootlmpl
- 若是ViewGroup,會(huì)執(zhí)行完invalidataChildINparent()之后繼續(xù)調(diào)用mParent.invalidateChildInParent();
- 最終調(diào)用到ViewRootImpl.invalidateChildInParent(),進(jìn)而執(zhí)行scheduleTraversals();注意:這里會(huì)提前判斷mWillDramSoon局部變量值,若當(dāng)前已經(jīng)執(zhí)行了performTraversals()遍歷重繪了,那就不會(huì)調(diào)用scheduleTravesals(),也就是說(shuō)不會(huì)發(fā)起重繪的異步消息了,但View中設(shè)置各種狀態(tài)值仍然是有效的,只會(huì)在下次重繪時(shí)生效.
-
scheduleTranversals()
該方法會(huì)在多個(gè)地方被調(diào)用,比如requestLayout() / invalidate()中,而我們又會(huì)經(jīng)常看到連續(xù)調(diào)用這兩個(gè)方法的情況,那這樣豈不是會(huì)連續(xù)發(fā)起兩次VIew樹(shù)重回遍歷請(qǐng)求?其實(shí)不會(huì)的,因?yàn)樯厦嬉呀?jīng)說(shuō)到,scheduleTranversals方法內(nèi)設(shè)置了一個(gè)局部變量mTranversalSchedduled,若先執(zhí)行了requestLayout(),那此時(shí)mTranversalSchedduled的值由false變?yōu)閠rue,接著調(diào)用invalidate()時(shí),判斷mTranversalSchedduled為true那么不會(huì)再重復(fù)發(fā)布一個(gè)異步消息了.
image.png -
performTraversals()
該方法是系統(tǒng)內(nèi)進(jìn)行View樹(shù)遍歷并進(jìn)行重繪的核心方法,內(nèi)部邏輯還是非常復(fù)雜,大致流程為:根據(jù)之前設(shè)置好的各種狀態(tài)值,判斷是否重新計(jì)算視圖大?。ǎ蚭asure),是否重新分配視圖的位置(Layout)是否需要重繪(Draw)具體如下:
image.png
View重繪過(guò)程
計(jì)算視圖大小的過(guò)程(Measure)
視圖大小,準(zhǔn)確的來(lái)說(shuō)是視圖的布局大小,我們?cè)贚ayout.xml中的每個(gè)UI控件設(shè)置的layout_width/layout_height兩個(gè)屬性被用來(lái)設(shè)置父視圖給當(dāng)前試圖分配的窗口大小,為了開(kāi)發(fā)方便和對(duì)不同的屏幕分辨率的兼容適配對(duì)這兩個(gè)參數(shù)的賦值一般都是用相對(duì)值,比如WRAP_CONTENT/MATCH_PARENT,計(jì)算視圖布局大小的過(guò)程就是本質(zhì)上就是吧視圖布局是使用的"相對(duì)值"轉(zhuǎn)換為具體值的過(guò)程;
Measure遞歸調(diào)用過(guò)程
View系統(tǒng)啟動(dòng)measure過(guò)程是從ViewRootImpl中調(diào)用host.measure()開(kāi)始的,詳見(jiàn)下圖:

從上圖中可以看出measure過(guò)程中主要就是從頂層父視圖向子視圖的遞歸調(diào)用view.measure(),注意以下幾點(diǎn):
- View.measure()該方法是final的,不允許重載,View子類(lèi)只能通過(guò)重載onMeasure()來(lái)完善自己的邏輯.
- MeasureSpec測(cè)量規(guī)格在measure過(guò)程經(jīng)常作為輸入?yún)?shù),該值為int型,其值有兩部分組成,高16位代表規(guī)格specMode,低16位代表具體尺寸,其中specMode只有種值:
1)MeasureSpec.EXACTLY:確定模式,即父視圖希望子視圖的大小是確定的,由specSize決定;
2)MeasureSpec.AT_MOST:最多模式,即父視圖希望子視圖的大小最多是specSize指定的值;
3)MeasureSpec.UNSPECIFIED:未指定模式,此時(shí)父視圖完全尊重子視圖的設(shè)計(jì); - 最頂層視圖DecorView測(cè)量時(shí)的MeasureSpec從何而來(lái)呢?
是在ViewRootImpl中調(diào)用 getRootMeasureSpec(..)獲得,LayoutParam寬高參數(shù)均為MATCH_PARENT;
獲得的specMode就是EXACTLY,specSize為物理屏幕大小; - 視圖的布局大小又父視圖與子視圖共同決定;
layout.xml中對(duì)含有子視圖的布局器中的layout_width/layout_height屬性實(shí)際扮演了2個(gè)角色;一個(gè)是和父視圖一起對(duì)布局器自身進(jìn)行measure操作;另一個(gè)角色是作為其子視圖的父視圖參與子視圖的measure操作; - ViewGroup類(lèi)中提供了 measureChildWithMargins(…)方法,用來(lái)抽象和簡(jiǎn)化父子視圖之間的padding、margin、實(shí)際內(nèi)容區(qū)域間的測(cè)量和尺寸計(jì)算,讓開(kāi)發(fā)者可以無(wú)需關(guān)注這些公共的邊界測(cè)量區(qū)域;
layout的遞歸調(diào)用過(guò)程
View系統(tǒng)啟動(dòng)layout過(guò)程是從ViewRootImpl中調(diào)用host.layout(…)開(kāi)始的,參見(jiàn)下圖:

從上圖中可以看出layout過(guò)程也是從頂層父視圖向子視圖的遞歸調(diào)用view.layout(…),即父視圖根據(jù)上一步measure子視圖所得到的布局大小和布局參數(shù),將子視圖放在合適的位置上,布局參數(shù)核心指layout_gravity;注意以下幾點(diǎn):
- 與measure方法不同,View.layout(…)方法可被重載,ViewGroup.layout(…)為final的不可重載,ViewGroup.onLayout(…)為abstract的,子類(lèi)必須重載,里面可以實(shí)現(xiàn)自己的位置分配邏輯;
- measure操作完成后得到的是對(duì)每個(gè)View經(jīng)測(cè)量過(guò)的寬高measuredWidth/measuredHeight;
layout操作完成之后得到的是對(duì)每個(gè)View進(jìn)行位置分配后的mLeft/mTop/mRight/mBottom; - layout_gravity一般出現(xiàn)于布局容器中,比如LinearLayout,他指的是當(dāng)前容器內(nèi)子視圖的排列方式和順序;
gravity一般出現(xiàn)于具體的View中,比如TextView,他指的就是TextView中實(shí)際文字的位置排放方式; - 凡是以layout_開(kāi)頭的布局參數(shù)基本都針對(duì)的是包含子視圖的容器視圖的,比如核心的layout_width/layout_height/layout_weight/layout_gravity,會(huì)在初始化LayoutParam時(shí)從xml中讀取并轉(zhuǎn)換為相應(yīng)參數(shù);
當(dāng)對(duì)一個(gè)沒(méi)有父容器的View設(shè)置相關(guān)layout_開(kāi)頭的屬性時(shí),實(shí)際上是沒(méi)有任何意義的;
Draw遞歸調(diào)用過(guò)程
繪制過(guò)程就是把View對(duì)象繪制到屏幕上,如果該View是一個(gè)容器ViewGroup,則需要遞歸繪制其所包含的所有子視圖;視圖中可繪制的元素包括:
- View背景Backgroud:每個(gè)視圖都可以有一個(gè)背景,背景可以是一個(gè)顏色值,也可以是一副圖片,甚至可以是任何Drawable對(duì)象;
- 視圖自身的內(nèi)容:一般由視圖設(shè)計(jì)者在視圖的onDraw方法中完成具體的內(nèi)容繪制,比如TextView的內(nèi)容就是具體的文字,若是視圖容器ViewGroup,則需要遞歸完成子視圖的具體內(nèi)容繪制;
只有這項(xiàng)內(nèi)容是需要開(kāi)發(fā)者設(shè)計(jì)并實(shí)現(xiàn)的,其余三項(xiàng)內(nèi)容均由系統(tǒng)自動(dòng)完成繪制; - 漸變邊框FadingEdge:其作用是為了讓視圖的邊框看起來(lái)更有層次感,其本質(zhì)就是一個(gè)Shader對(duì)象,當(dāng)然可以通過(guò)配置項(xiàng)關(guān)閉該效果;
-
滾動(dòng)條ScrollBar:用來(lái)顯示當(dāng)前滾動(dòng)的位置和狀態(tài),與PC不一樣,該滾動(dòng)條一般不能直接按住拖動(dòng);
View系統(tǒng)啟動(dòng)draw過(guò)程是從ViewRootImpl中調(diào)用host.draw(…)開(kāi)始的,總體過(guò)程與measure/layout極其類(lèi)似,即從頂級(jí)父視圖開(kāi)始向子視圖進(jìn)行遞歸調(diào)用view.draw(…),具體可參見(jiàn)下圖:
image.png
- 下面就來(lái)看看這個(gè)過(guò)程中的幾個(gè)核心方法都做了哪些具體的事情;
-
ViewRootImpl.draw()
image.png
里面要特別注意的一個(gè)核心點(diǎn)就是會(huì)根據(jù)根視圖內(nèi)部的Scroller對(duì)象來(lái)調(diào)用mScroller.computeScrollOffset()判斷當(dāng)前視圖是否還處于滾動(dòng)狀態(tài),若處于滾動(dòng)狀態(tài),則會(huì)進(jìn)行滾動(dòng)偏移量計(jì)算,并且在最后再次調(diào)用scheduleTraversals()來(lái)發(fā)送一個(gè)異步重繪請(qǐng)求;
另外一點(diǎn)就是:Surface會(huì)按照底層驅(qū)動(dòng)模式自動(dòng)執(zhí)行顯卡模式或CPU模式;前者采用顯卡來(lái)進(jìn)行頁(yè)面繪制,支持硬件圖形加速,一般基于OpenGL實(shí)現(xiàn);后者也被成為軟件模式,即通過(guò)CPU及內(nèi)存來(lái)模擬圖形繪制,不支持硬件加速;目前的一些高端機(jī)型幾乎都是顯卡模式,低端機(jī)型更多采用CPU模式; - View.draw()內(nèi)部流程主要就是為了完成前面提到的視圖中的各種具體元素的繪制,大致過(guò)程如下:
- 繪制背景,由于可能會(huì)出現(xiàn)滾動(dòng)條,所以繪制時(shí)可能會(huì)涉及到Canvas畫(huà)布的平移和恢復(fù),即 canvas.translate(scrollX, scrollY);
- 判斷是否需要顯示漸變框,若不需要?jiǎng)t直接進(jìn)入后續(xù)3、4、5繪制邏輯;
- 繪制視圖自身內(nèi)容,通過(guò)回調(diào)onDraw()實(shí)現(xiàn)
- 調(diào)用dispatchDraw()繪制子視圖,對(duì)于ViewGroup而言,默認(rèn)已重載該方法,如有特別需求子類(lèi)無(wú)需再次重載該函數(shù);
- 調(diào)用onDrawScrollBars()繪制滾動(dòng)條;
-
ViewGroup.dispatchDraw()的作用是繪制父視圖中包含的子視圖,其本質(zhì)就是給不同的子視圖分配合適的畫(huà)布Canvas,至于子視圖具體如何繪制,則又會(huì)遞歸回調(diào)View.draw()方法;該方法內(nèi)部將根據(jù)onLayout()中為子視圖分配的具體區(qū)塊調(diào)整Canvas的內(nèi)部剪切區(qū),從而讓子視圖認(rèn)為畫(huà)布是他自己獨(dú)享的,坐標(biāo)也是從(0,0)開(kāi)始;其內(nèi)部具體執(zhí)行流程參見(jiàn)下圖:
image.png
有幾點(diǎn)需要注意:
- 區(qū)分View動(dòng)畫(huà)和ViewGroup布局動(dòng)畫(huà):前者指的是View自身的動(dòng)畫(huà),可以通過(guò)setAnimation(.)添加;而后者是專(zhuān)門(mén)針對(duì)ViewGroup而言的,指的是該ViewGroup在顯示內(nèi)部的子視圖時(shí)而設(shè)置的動(dòng)畫(huà),可以在layout.xml中對(duì)容器標(biāo)簽設(shè)置layoutAnimation屬性,比如可以對(duì)LinearLayout設(shè)置子視圖在顯示時(shí)出現(xiàn)逐行顯示、隨機(jī)顯示、或落下等不同的動(dòng)畫(huà)效果;
2)在獲取畫(huà)布剪切區(qū)時(shí)會(huì)自動(dòng)處理掉padding,讓子視圖獲取的畫(huà)布無(wú)需關(guān)注這些附加邏輯;
3)默認(rèn)情況下子視圖的ViewGroup.drawChild()繪制順序與子視圖被添加的順序一致,但開(kāi)發(fā)者可以重載ViewGroup.getChildDrawingOrder()方法提供不同的順序;
4)當(dāng)給一個(gè)子視圖添加了移除動(dòng)畫(huà)時(shí),該子視圖會(huì)被添加到mDisappearingChildren隊(duì)列中,在動(dòng)畫(huà)結(jié)束之前該子視圖將一致存在,但此時(shí)該子視圖無(wú)法被點(diǎn)擊,也無(wú)法獲得任何消息事件,僅僅是可見(jiàn)而已;
-
ViewGroup.drawChild()的核心過(guò)程是為子視圖分配合適的Canvas剪切區(qū),其大小取決于child的布局大小,位置取決于child的內(nèi)部滾動(dòng)值、以及當(dāng)前動(dòng)畫(huà),其內(nèi)部流程參見(jiàn)下圖:
image.png
有幾點(diǎn)需要注意:
1)該方法在新的版本(比如4.2.2)中已經(jīng)被放在View中了,ViewGroup中直接中轉(zhuǎn)調(diào)用child.draw(canvas, this, drawingTime);
這是在看android源碼時(shí)需要特別注意的,View/ViewGroup之間有很多方法重載,且存在父子視圖的遞歸調(diào)用,經(jīng)常會(huì)把人搞暈,看代碼時(shí)需要非常平靜才行;
2)對(duì)于ViewGroup中子視圖的動(dòng)畫(huà)支持有兩種方式,一種是通過(guò)setAnimation(.)添加,另外一種是通過(guò)重載ViewGroup.getChildStaticTransformation(View child, Transformation t)方法實(shí)現(xiàn); -
View.onDrawScrollBars()的作用是繪制滾動(dòng)條;
1)滾動(dòng)條是View的基本元素之一,每個(gè)View都可以有滾動(dòng)條,只是某些視圖從用戶體驗(yàn)的角度無(wú)需顯示而已,可以在layout.xml中通過(guò)scrollBarXXX相關(guān)的屬性設(shè)置滾動(dòng)條;
2)滾動(dòng)條可以是水平的、垂直的、或者兩者均有,滾動(dòng)條的展示封裝在ScrollBarDrawable中,包括三個(gè)基本尺寸range/offset/extent、以及用來(lái)標(biāo)識(shí)滾動(dòng)條背景和自身的兩個(gè)圖片track/thumb;
range:代表滾動(dòng)條從頭至尾滾動(dòng)過(guò)程中所跨越的范圍有多大,比如你想用滾動(dòng)條來(lái)標(biāo)示一萬(wàn)行代碼,那range就可以設(shè)置10000;
offset:代表滾動(dòng)條當(dāng)前的偏移量,或者是可視的第一行在整個(gè)滾動(dòng)跨度的什么位置,比如當(dāng)前已經(jīng)滾動(dòng)到600行了,那offset就是600;
extent:代表顯示滾動(dòng)條的視圖View在屏幕上的可視高度或?qū)挾?,比?00dp;
track:代表滾動(dòng)條顯示的背景或軌道,一般其寬高度和extent一致;
thumb:代表滾動(dòng)條顯示的具體前景,其寬高度、位置會(huì)根據(jù)range/offset/extent三者的具體值進(jìn)行計(jì)算獲得;
image.png
3)滾動(dòng)條一共有三種狀態(tài)ON/OFF/FADDING,即顯示、隱藏、正在隱藏(處于顯示狀態(tài),但通過(guò)設(shè)置透明度悄悄改變狀態(tài));
一般而言,從用戶體驗(yàn)的角度來(lái)說(shuō)滾動(dòng)條會(huì)在滾動(dòng)完畢后自動(dòng)隱藏,而滾動(dòng)時(shí)自動(dòng)顯示,開(kāi)發(fā)者可以通過(guò)scrollBarFadeDuration參數(shù)設(shè)置自動(dòng)隱藏間隔時(shí)間,也可以調(diào)用setScrollbarFadingEnabled()設(shè)置是否自動(dòng)隱藏;
4)開(kāi)發(fā)者可重載3個(gè) computeVerticalScrollXXX()方法來(lái)實(shí)現(xiàn)對(duì)滾動(dòng)條具體顯示位置和thumb大小的控制,可以調(diào)用awakenScrollBars()方法強(qiáng)制顯示滾動(dòng)條;
自定義滾動(dòng)位置時(shí)可調(diào)用scrollTo()/scrollBy()來(lái)完成滾動(dòng)到具體位置、或者滾動(dòng)具體距離;







