先扯一點(diǎn)題外話践啄,就現(xiàn)在的 Android 市場(chǎng)來(lái)說(shuō),可以說(shuō)是不容樂(lè)觀的,只不過(guò)是相對(duì)的铁坎,Android 的坑位有限蜂奸,人又相對(duì)比較多,加上資本寒冬硬萍,像我一樣的菜鳥(niǎo)是最為令人擔(dān)憂的。那么能怎么辦呢围详?只有進(jìn)階到高級(jí)才行朴乖,才能混的下去,高級(jí) Android 工程師的市場(chǎng)還是很廣闊的助赞,所以一起努力吧买羞,少年們!
想進(jìn)階到高級(jí)雹食,自定義 View 這部分是必須要攻克的畜普,這篇也算是開(kāi)篇,主要翻譯一下官方文檔群叶,在補(bǔ)一下基礎(chǔ)部分吃挑,像坐標(biāo)系,位置獲取方式街立,顏色使用方式等舶衬,下面就動(dòng)起來(lái)吧!
1. View 簡(jiǎn)介
1.1 View 介紹
public class View
extends Object implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource
View 繼承 Object赎离,并實(shí)現(xiàn)了一些接口逛犹,現(xiàn)在對(duì)這些有一個(gè)印象就行,等具體分析源碼時(shí)梁剔,再來(lái)看虽画。
This class represents the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling. View is the
base class for widgets, which are used to create interactive UI components (buttons, text fields, etc.). The ViewGroup subclass is the base class for layouts, which are invisible containers > that hold other Views (or other ViewGroups) and define their layout properties.
View 的定義:View 是用戶交互組件的基本構(gòu)建塊。View 占據(jù)屏幕上的一個(gè)矩形區(qū)域荣病,并且負(fù)責(zé)繪制和處理事件码撰。View 是組件的基類,這些組件用來(lái)產(chǎn)生交互功能(如按鈕众雷。文本框等)灸拍。ViewGroup 是 View 的子類,它是用來(lái)管理布局的基類砾省,它是不可見(jiàn)的鸡岗,用來(lái)裝載其他的子 View 或者其他的 ViewGroup,并且可以設(shè)置布局的屬性编兄。
通過(guò)這段話對(duì) View 有了一個(gè)初步的認(rèn)識(shí)轩性,平時(shí)我們?cè)?XML 中定義的布局就是一個(gè) ViewGroup,界面的頂級(jí) View狠鸳,也是一個(gè) ViewGroup(DecorView 是一個(gè) FrameLayout)
Using Views
窗口中的所有視圖都排列在 View 的樹(shù)結(jié)構(gòu)中揣苏,可以通過(guò)代碼或通過(guò)在一個(gè)或多個(gè)XML布局文件中指定視圖來(lái)添加 View 樹(shù)結(jié)構(gòu)中悯嗓。 有許多專門(mén)的視圖子類充當(dāng)控件或能夠顯示文本,圖像或其他內(nèi)容卸察。
Once you have created a tree of views, there are typically a few types of common operations you may wish to perform:
一旦創(chuàng)建了一個(gè)樹(shù)結(jié)構(gòu)脯厨,會(huì)有一系列的通用操作,主要包括以下幾點(diǎn):
Set properties(設(shè)置屬性,如在給 TextView 設(shè)置文本)
Set focus(設(shè)置焦點(diǎn))
Set up listeners(設(shè)置監(jiān)聽(tīng)坑质,如給 Button 設(shè)置 View.OnClickListener)
Set visibility(設(shè)置 View 是否可見(jiàn) setVisibility(int))
Implementing a Custom View(自定義 View)
官方文檔中這部分實(shí)際上就是給了一個(gè)大體的說(shuō)明個(gè)合武,并不是詳細(xì)的一個(gè)自定義 View 的教程。
To implement a custom view, you will usually begin by providing overrides for some of the standard methods that the framework calls on all views. You do not need to override all of these methods. In fact, you can start by just overriding onDraw(android.graphics.Canvas).
為了實(shí)現(xiàn)一個(gè)子自定義 View涡扼,你將要重寫(xiě) View 所調(diào)用的框架中的標(biāo)準(zhǔn)方法稼跳,不需要全部重寫(xiě),事實(shí)上吃沪,僅僅調(diào)用 onDraw 方法就行汤善。也就是說(shuō),通過(guò)調(diào)用 onDraw 方法就可以完成簡(jiǎn)單的自定義 View票彪,對(duì)于復(fù)雜的红淡,需要我們重寫(xiě) View 的比較重要的三個(gè)方法,即 onMeasure抹镊、onLayout锉屈、onDraw。
官方給出了一個(gè)表格垮耳,這個(gè)表格是對(duì) View 中主要的方法進(jìn)行了一個(gè)歸類颈渊,可以作為自定義 View 操作的一個(gè)主要參考和指導(dǎo)。這里就不翻譯了终佛,相信自己可以看明白俊嗽。
1.2 View 屬性
這里的屬性并不是 View 可以設(shè)置屬性,而是官方文檔介紹的 View 相關(guān)的一些東西铃彰,下面一起來(lái)看下绍豁。建議對(duì)這部分通讀一下,對(duì) View 會(huì)有一個(gè)整體的感知牙捉。
IDs
視圖可能會(huì)有一個(gè)與它相關(guān)的 integer 類型的 id竹揍,即我們使用布局時(shí)定義的那個(gè) id。這些 id 是在布局 XML 文件中設(shè)置的邪铲,通過(guò)這些 id 能夠找到樹(shù)結(jié)構(gòu)中的 對(duì)應(yīng)的 View芬位,使用的方式也是我們很熟悉的:
<Button
android:id="@+id/my_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/my_button_text"/>
在布局文件中定義一個(gè) Button,設(shè)置 id带到,然后就可以通過(guò) id 獲取這個(gè) Button昧碉。
Button myButton = findViewById(R.id.my_button);
這些 id 并不一定是唯一的,但是在一個(gè)樹(shù)結(jié)構(gòu)中最好還是保持唯一性,因?yàn)楫?dāng)你找一個(gè)文件時(shí)被饿,會(huì)很方便四康,否當(dāng)你點(diǎn)擊查找時(shí),可能會(huì)彈出很多文件狭握,導(dǎo)致不好篩選闪金。
Position
一個(gè) View 的幾何形狀是矩形的。一個(gè) View 有位置表示论颅,通常是通過(guò)左上形式的坐標(biāo)系毕泌,也就是 X 軸右向是正的,Y 軸向下是正的嗅辣,并且有兩個(gè)尺寸,寬和高挠说。位置信息和尺寸信息都是以像素為單位的澡谭。
獲取位置信息是通過(guò) getLeft() 和 getTop() 這兩個(gè)方法。getLeft() 方法視圖的 X 坐標(biāo)损俭,getTop() 方法返回的是 Y 坐標(biāo)蛙奖。這兩個(gè)坐標(biāo)返回的值都是相對(duì)一父布局的。例如杆兵,getLeft() 返回值是 20雁仲,意味著這個(gè) view 位于它的直接父布局左邊緣的右側(cè) 20 個(gè)像素的位置。
另外琐脏,一些很方便的方法可以用來(lái)計(jì)算位置信息攒砖,可以避免自己計(jì)算的過(guò)程。getRight() 和 getBottom()日裙。
getRight() = getLeft() + getWidth()吹艇。
getBottom() = getTop() + getHeight()。
這些在后面介紹坐標(biāo)系時(shí)會(huì)詳細(xì)介紹下昂拂。
Size, padding and margins
一個(gè) view 的尺寸大小表達(dá)為寬和高受神,它通常擁有兩對(duì)寬和高。
第一對(duì):測(cè)量的寬和高格侯,這些尺寸定義一個(gè) view 在父布局中占據(jù)的位置有多大鼻听,通過(guò)調(diào)用 getMeasuredWidth() 和 getMeasuredHeight() 這兩個(gè)方法可以獲取。
第二對(duì):就是簡(jiǎn)單寬和高联四,有時(shí)也叫繪制的寬和高撑碴。這些尺寸定義 view 早屏幕上實(shí)際的尺寸,時(shí)機(jī)是在繪制時(shí)碎连,在 layout 之后灰羽。這兩個(gè)尺寸和 測(cè)量的寬和高可能是一樣的,通過(guò)調(diào)用 getWidth() 和 getHeight() 這兩個(gè)方法獲取。
一個(gè) view 可以定義 padding廉嚼,但是它不提供 margins玫镐。margins 是 ViewGroup 提供的布局限制的。
Layout
布局的過(guò)程包括兩個(gè):測(cè)量和布局怠噪。測(cè)量的過(guò)程是在 measure(int, int) 方法中實(shí)現(xiàn)的恐似,并且是自上向下遍歷樹(shù)結(jié)構(gòu)的過(guò)程。每個(gè) view 都會(huì)把尺寸規(guī)則在這個(gè)遞歸過(guò)程中傳遞出去傍念。在測(cè)量結(jié)束之后矫夷,每個(gè) view 都會(huì)儲(chǔ)存它的測(cè)量值。第二個(gè)過(guò)程發(fā)生在 layout(int, int, int, int) 這個(gè)方法中憋槐,并且它和測(cè)量過(guò)程類似双藕。在這個(gè)過(guò)程中,每個(gè)父布局都會(huì)通過(guò)在測(cè)量過(guò)程中得到的尺寸來(lái)給子布局分配位置阳仔。
但完成測(cè)量過(guò)程時(shí)忧陪,即在 measure() 方法返回時(shí),它的 getMeasuredWidth() 和 getMeasuredHeight() 一定是能夠獲取到值的近范,它的子布局也是一樣嘶摊。 一個(gè)子 view 的測(cè)量寬度和測(cè)量高度必須和父布局的限制保持一致,例如父布局設(shè)置了大小评矩,子布局不能的大小不能超過(guò)父布局的大小叶堆。這可以保證在測(cè)量結(jié)束時(shí),所有的父布局能夠接受子布局的測(cè)量尺寸斥杜。一個(gè)父布局可能測(cè)量多次虱颗。例如父布局可能測(cè)量每個(gè)未指定尺寸的子布局,找到他們需要設(shè)置為多大果录,然后再次調(diào)用 measure() 方法來(lái)判斷這些子布局的總大小是否過(guò)大還是過(guò)小上枕。
測(cè)量過(guò)程中使用兩個(gè)類來(lái)表述尺寸,View.MeasureSpec 用來(lái)告訴父布局如何測(cè)量和布局弱恒。LayoutParams 類用來(lái)描述 view 的寬和高有多大辨萍,對(duì)于每個(gè) view,可以指定下面中一種規(guī)格:
(1)具體的數(shù)值
(2)MATCH_PARENT返弹,表示 view 的大小和父布局大小相同(需要減去padding的大行庥瘛)
(3)WRAP_CONTENT,表示剛好包含內(nèi)容的大幸迤稹(需要加上padding的大欣场)
不同的 ViewGroup 有不同的 LayoutParams 的子類。例如默终,AbsoluuteLayout 布局有自己的 LayoutParams椅棺,包含 X 和 Y 的值犁罩。
測(cè)量規(guī)格用來(lái)從父布局傳到子布局當(dāng)中,有下面三種規(guī)格:
(1)UNSPECIFIED: 用于父布局來(lái)決定子布局的尺寸的規(guī)格两疚,例如床估,LinearLayout 可能調(diào)用 measure(),測(cè)量它的子布局诱渤,子布局的高度測(cè)量規(guī)格是 UNSPECIFIED丐巫,寬度是準(zhǔn)確的值,為240勺美,通過(guò)這兩個(gè)規(guī)格來(lái)告訴子布局的寬度和高度递胧,主要高度部分,父布局會(huì)計(jì)算好赡茸,幫助子布局確定尺寸缎脾。
(2)EXACTLY: 用于父布局來(lái)給子布局設(shè)置一個(gè)準(zhǔn)確的尺寸值。子布局使用這個(gè)尺寸占卧,并且保證所有的控件都在這個(gè)尺寸內(nèi)赊锚。
(3)AT_MOST: 用于父布局給子布局設(shè)置一個(gè)最大的約束。子布局必須保證它和所有的控件都在這個(gè)尺寸范圍內(nèi)
To initiate a layout, call requestLayout(). This method is typically called by a view on itself when it believes that is can no longer fit within its current bounds.
為了初始化一個(gè)布局屉栓,可以通過(guò)調(diào)用 requestLayout() 方法。這個(gè)方法通常被一個(gè) view 自己調(diào)用耸袜,調(diào)用的時(shí)機(jī)是它不在它當(dāng)前的區(qū)域時(shí)友多,換句話說(shuō),也就是 view 的位置發(fā)生變化時(shí)堤框,可以通過(guò)這個(gè)方法進(jìn)行重新布局域滥。
Drawing
繪制的過(guò)程,通過(guò)遍歷樹(shù)結(jié)構(gòu)蜈抓,并且記錄需要更新的view的繪制命令來(lái)進(jìn)行的启绰,在這之后,整個(gè)樹(shù)的繪圖命令被發(fā)送到屏幕沟使,剪切到新?lián)p壞的區(qū)域委可,意思是就是視圖會(huì)被顯示到屏幕上待顯示的區(qū)域。
這個(gè)樹(shù)結(jié)構(gòu)記錄并按順序繪制腊嗡,父布局先繪制着倾,子布局后繪制,子控件繪制的順序也是按照他們?cè)跇?shù)結(jié)構(gòu)中出現(xiàn)的先后順序來(lái)犀勒。如果為一個(gè) view 設(shè)置了背景(Drawable)挨下,那么 這個(gè) View 會(huì)先繪制背景阻塑,然后在繪制本身。子 view 的繪制順序可以進(jìn)行自定義崇决,通過(guò) setZ(float) 方法設(shè)置 Z 值材诽。
強(qiáng)制一個(gè) view 重繪,可以通過(guò)調(diào)用 invalidate() 方法恒傻。
Event Handling and Threading
一個(gè) View 的基本循環(huán)如下:
一個(gè)事件傳遞過(guò)來(lái)脸侥,并分發(fā)給合適的 view,這個(gè) view 處理這個(gè)事件并回調(diào)相應(yīng)的監(jiān)聽(tīng)方法碌冶。
在處理事件的過(guò)程當(dāng)中湿痢,視圖的區(qū)域(尺寸)可能發(fā)生改變,這時(shí)視圖會(huì)調(diào)用 requestLayout() 這個(gè)方法扑庞。
類似的譬重,如果在處理事件的過(guò)程中,視圖的外觀發(fā)生改變罐氨,這時(shí)會(huì)調(diào)用 invalidate() 方法臀规。
無(wú)論是 requestLayout() 還是 invalidate() 被調(diào)用,系統(tǒng)框架會(huì)合適的處理視圖結(jié)構(gòu)樹(shù)的測(cè)量栅隐,布局塔嬉,繪制。
注意:整個(gè)樹(shù)結(jié)構(gòu)是單線程的租悄,當(dāng)有方法作用在 view 上時(shí)谨究,必須在 UI 線程(主線程)上進(jìn)行。如果想在其他線程工作并且其他線程想要更新視圖的狀態(tài)泣棋,可以使使用 Handler胶哲。
Focus Handling
系統(tǒng)框架將會(huì)處理用戶輸入時(shí)的焦點(diǎn)。包括當(dāng) view 被移除或者隱藏時(shí)潭辈,一個(gè)新的 view 變得可見(jiàn)時(shí)鸯屿。通過(guò) isFocusable() 方法可以知道視圖是否處于獲取焦點(diǎn)狀態(tài)。為了改變一個(gè) view 能夠獲取焦點(diǎn)把敢,通過(guò)調(diào)用 setFocusable(boolean)方法寄摆。當(dāng)處于觸摸狀態(tài)的view,可以通過(guò)調(diào)用 isFocusableInTouchMode() 方法判斷是否處于獲取焦點(diǎn)狀態(tài)修赞,可以通過(guò)調(diào)用 setFocusableInTouchMode(boolean) 方法來(lái)改變焦點(diǎn)狀態(tài)婶恼。
擁有焦點(diǎn)的時(shí)刻是通過(guò)一個(gè)算法,這個(gè)算法來(lái)找到最近的一個(gè)在指定方向的控件柏副。在一些情況下熙尉,默認(rèn)的算法并不能夠匹配開(kāi)發(fā)者的需求。在這種情況下搓扯,可以提供一個(gè)準(zhǔn)確的復(fù)寫(xiě)來(lái)完成检痰,在XML 文件中定義屬性。包括以下幾個(gè)屬性:
- nextFocusDown
- nextFocusLeft
- nextFocusRight
- nextFocusUp
當(dāng)需要使一個(gè) view 獲取焦點(diǎn)是锨推,可以通過(guò)調(diào)用 requestFocus() 方法铅歼。
Touch Mode
當(dāng)一個(gè)用戶控制一個(gè)交互動(dòng)作時(shí)公壤,通過(guò)像 D-pad 這樣的方向鍵,有需要給定這些控件以焦點(diǎn)椎椰,如 Button 按鈕厦幅,所用用戶能夠看到什么可以輸入。如果設(shè)備有觸摸功能慨飘,用戶可以通過(guò)觸摸開(kāi)始進(jìn)行交互操作确憨,就沒(méi)有必要時(shí)刻保持高亮,或者給指定的 view 保持焦點(diǎn)瓤的,這種方式就是所謂的 觸摸模式休弃。
對(duì)于一個(gè)觸摸設(shè)備,一旦用戶觸摸屏幕圈膏,設(shè)備將進(jìn)入觸摸狀態(tài)塔猾,從這個(gè)時(shí)刻起,只有 哪些處于 isFocusableInTouchMode() 方法返回 true的 view才是獲取焦點(diǎn)狀態(tài)的稽坤,如文本編輯框丈甸。其他的 view 可以觸摸,如 button尿褪,但是不占有焦點(diǎn)睦擂,它們只會(huì)觸發(fā)監(jiān)聽(tīng)事件。
任意時(shí)間杖玲,用戶點(diǎn)擊了方向鍵祈匙,如 D-pad,這個(gè)設(shè)備鍵退出觸摸模式天揖,并找到一個(gè) view,并會(huì)獲取焦點(diǎn)跪帝,所以用戶能夠再次和用戶交互入口進(jìn)行交互交互動(dòng)作今膊,不需要再次觸摸屏幕。
觸摸狀態(tài)由 Activity 來(lái)維護(hù)伞剑,通過(guò)調(diào)用 isInTouchMode() 方法來(lái)判斷設(shè)備當(dāng)前是否處于觸摸狀態(tài)斑唬。
Scrolling
框架為 view 提供基本的滑動(dòng)操作,可以滑動(dòng)內(nèi)部的內(nèi)容黎泣。包括記錄 X 和 Y 向 的滑動(dòng)恕刘,可以查看 scrollBy(int, int), scr
Tags
不像 ID, tags 不是用于識(shí)別 view 的抒倚,tags 用于標(biāo)記和這個(gè) view 相關(guān)的額外信息的褐着。常用于存儲(chǔ)和這個(gè)view相關(guān)的信息,它比使用單獨(dú)的結(jié)構(gòu)來(lái)存儲(chǔ)更加方便托呕。
tags 可以在 XML 文件中使用字符序列定義一個(gè)單獨(dú)的 tag含蓉,標(biāo)簽是 android:tag频敛,或者使用多個(gè)標(biāo)簽,以 <tag> 作為子元素馅扣。
<View ...
android:tag="@string/mytag_value" />
<View ...>
<tag android:id="@+id/mytag"
android:value="@string/mytag_value" />
</View>
Tags 也可以使用代碼來(lái)實(shí)現(xiàn)斟赚,獲取 view 之后,使用 setTag(Object) 或 setTag(int, Object) 方法差油。
Themes
默認(rèn)情況下拗军, view 使用 Context 提供的主題,然而蓄喇,如果想使用一個(gè)不同的主題发侵,可以在 XML 文件中 通過(guò) android:theme 屬性來(lái)定義或者通過(guò)在代碼中傳遞一個(gè) ContextThemeWrapper 給構(gòu)造函數(shù)。
當(dāng) android:theme 屬性使用在 XML 中時(shí)公罕,指定的主題應(yīng)用在 context 的上層中器紧,同時(shí) view 的子元素也使用這個(gè)主題。
在下面的這個(gè)例子中楼眷,LinearLayout 內(nèi)部的子 view 都使用 Material dark 這個(gè)主題铲汪,然而,由于是在 LinearLayout 內(nèi)部定義的主題罐柳,相當(dāng)于疊加在上層的主題掌腰, android:colorAccent 的值,它是在上層主題中定義的张吉,所以也會(huì)出現(xiàn)齿梁。
<LinearLayout
...
android:theme="@android:theme/ThemeOverlay.Material.Dark">
<View ...>
</LinearLayout>
Properties
View 類暴露一個(gè) ALPHA 屬性(透明度屬性),同時(shí)也有一些平移旋轉(zhuǎn)屬性肮蛹,如 TRANSLATION_X勺择, TRANSLATION_Y。這些屬性在類中都有 setter/getter 方法可以設(shè)置伦忠。這些屬性可以設(shè)置和 view 渲染屬性相關(guān)的狀態(tài)省核。這些屬性也可以用于動(dòng)畫(huà)相關(guān)的設(shè)置,可以在動(dòng)畫(huà)部分查看詳細(xì)信息昆码。
Animation
從 Android 3.0開(kāi)始气忠,給 view 添加動(dòng)畫(huà)的最好方式就是使用 android.animation 包下的 APIs.這些動(dòng)畫(huà)基類改變 view 的實(shí)際屬性,如 透明度和 X軸 平移量赋咽。和 3.0之前的動(dòng)畫(huà)基類相比旧噪,僅僅是改變 view 在屏幕上的顯示效果,并沒(méi)有改變 view 的屬性脓匿。特別的淘钟,通過(guò) ViewPropertyAnimator 類使得這些 view 的屬性使用起來(lái)更加簡(jiǎn)單和有效。
相對(duì)的陪毡,你可以使用 3.0之前的動(dòng)畫(huà)類來(lái)進(jìn)行 view 的渲染日月「の停可以將一個(gè)動(dòng)畫(huà)附著到 view 上,使用 setAnimation(Animation) 或者 startAnimation(Animation) 方法爱咬。這個(gè)動(dòng)畫(huà)可以縮放尺借,旋轉(zhuǎn),平移和透明度精拟,并能夠隨時(shí)間改變 view 屬性燎斩。如果這個(gè)動(dòng)畫(huà)被附著到一個(gè) view 上,并且 view 上包含子類蜂绎,這個(gè)動(dòng)畫(huà)會(huì)影響這個(gè)節(jié)點(diǎn)之下的所有子類栅表。當(dāng)開(kāi)啟動(dòng)畫(huà)之后,框架會(huì)開(kāi)始重繪這個(gè) view师枣,直到 動(dòng)畫(huà)結(jié)束
Security
一個(gè)應(yīng)用有必要確認(rèn)一個(gè)動(dòng)作怪瓶,告知用戶用戶的內(nèi)容,如保證權(quán)限請(qǐng)求践美,點(diǎn)擊廣告的操作等等洗贰。不幸的是,有很多應(yīng)用嘗試引導(dǎo)用戶完成這些一些動(dòng)作陨倡,并且在沒(méi)有意識(shí)的情況下敛滋,通過(guò)在這個(gè) view 下隱藏目的。作為補(bǔ)救措施兴革,框架提供一些觸摸的過(guò)濾機(jī)制绎晃,來(lái)提高 view 的安全性,來(lái)保證一些敏感的訪問(wèn)操作杂曲。
為了能夠進(jìn)行觸摸的過(guò)濾操作庶艾,通過(guò)調(diào)用 setFilterTouchesWhenObscured(boolean) 方法,或者設(shè)置 android:filterTouchesWhenObscured 屬性為 true擎勘,當(dāng)可以進(jìn)行過(guò)濾時(shí)咱揍,框架將會(huì)摒棄觸摸操作,當(dāng)view 的窗口被另外一個(gè) 窗口占據(jù)時(shí)货抄。這樣,view 將不會(huì)接受觸接收觸摸操作朱转,當(dāng)一個(gè) toast 蟹地,dialog,或者其他的窗口出想在這個(gè) view 的窗口的上面時(shí)藤为。
為了更好地控制安全性的問(wèn)題怪与,考慮重寫(xiě) onFilterTouchEventForSecurity(MotionEvent) 方法來(lái)實(shí)現(xiàn)你的安全策略,可以查看MotionEvent.FLAG_WINDOW_IS_OBSCURED 這個(gè)屬性缅疟。
這個(gè)方法必須在開(kāi)始創(chuàng)建這個(gè) UI 元素的線程中調(diào)用分别,通常就是應(yīng)用的主線程遍愿。
2. View 分類
上面把官方對(duì) view 的描述做了一遍翻譯,對(duì) view 有了一個(gè)全面的了解耘斩,下面看下 view 的分類沼填。
上面一張圖,就是 view 的樹(shù)形結(jié)構(gòu)括授,可以有很多層坞笙,然后通過(guò)一層一層遍歷,最后繪制出來(lái)荚虚。當(dāng)然薛夜,嵌套層次過(guò)多時(shí),就會(huì)出現(xiàn)過(guò)度繪制版述,會(huì)變的卡頓梯澜,所以在設(shè)計(jì)布局時(shí),避免多層嵌套使用渴析。 view 的三個(gè)主要過(guò)程晚伙,measure、layout檬某、draw 都是通過(guò)這個(gè)樹(shù)結(jié)構(gòu)從根節(jié)點(diǎn)一層一層遞歸遍歷完成的撬腾。另外,根布局就是 DecorView恢恼,是 FrameLayout民傻。
3. View 的位置描述
3.1 坐標(biāo)系
屏幕坐標(biāo)系和數(shù)學(xué)坐標(biāo)系的區(qū)別:由于移動(dòng)設(shè)備一般定義屏幕左上角為坐標(biāo)原點(diǎn),向右為x軸增大方向场斑,向下為y軸增大方向漓踢, 所以在手機(jī)屏幕上的坐標(biāo)系與數(shù)學(xué)中常見(jiàn)的坐標(biāo)系是稍微有點(diǎn)差別的,詳情如下:
實(shí)際屏幕上的默認(rèn)坐標(biāo)系如下:
PS: 假設(shè)其中棕色部分為手機(jī)屏幕
3.2 位置獲取
(1)4個(gè)頂點(diǎn)的位置描述分別由4個(gè)值決定:
(請(qǐng)記茁┮:View的位置是相對(duì)于父控件而言的)
Top:子View上邊界到父view上邊界的距離
Left:子View左邊界到父view左邊界的距離
Bottom:子View下邊距到父View上邊界的距離
Right:子View右邊界到父view左邊界的距離
(2)獲取4個(gè)頂點(diǎn)可以通過(guò)一下4個(gè)函數(shù):
getTop(); //獲取子View左上角距父View頂部的距離
getLeft(); //獲取子View左上角距父View左側(cè)的距離
getBottom(); //獲取子View右下角距父View頂部的距離
getRight(); //獲取子View右下角距父View左側(cè)的距離
(3) MotionEvent中 getXX 和 getRaw 的區(qū)別:
event.getX(); //觸摸點(diǎn)相對(duì)于其所在組件坐標(biāo)系的坐標(biāo)
event.getY();
event.getRawX(); //觸摸點(diǎn)相對(duì)于屏幕默認(rèn)坐標(biāo)系的坐標(biāo)
event.getRawY();
3.3 Android 中的角度與弧度
為了精確描述一個(gè)角的大小引入了角度與弧度的概念喧半。
名稱 | 定義 |
---|---|
角度 | 兩條射線從圓心向圓周射出,形成一個(gè)夾角和夾角正對(duì)的一段弧青责。當(dāng)這段弧長(zhǎng)正好等于圓周長(zhǎng)的360分之一時(shí)挺据,兩條射線的夾角的大小為1度. |
弧度 | 兩條射線從圓心向圓周射出,形成一個(gè)夾角和夾角正對(duì)的一段弧脖隶。當(dāng)這段弧長(zhǎng)正好等于圓的半徑時(shí)扁耐,兩條射線的夾角大小為1弧度. |
如圖:
轉(zhuǎn)換公式:
rad 是弧度, deg 是角度
公式 | 例子 |
---|---|
rad = deg x π / 180 | 2π = 360 x π / 180 |
deg = rad x 180 / π | 360 = 2π x 180 / π |
維基百科的公式:
rad 是弧度产阱, deg 是角度
在常見(jiàn)的數(shù)學(xué)坐標(biāo)系中角度增大方向?yàn)槟鏁r(shí)針婉称,
在默認(rèn)的屏幕坐標(biāo)系中角度增大方向?yàn)轫槙r(shí)針。
4 Android 中 color
4.1 簡(jiǎn)介
安卓支持的顏色模式:
顏色模式 | 備注 |
---|---|
ARGB8888 | 四通道高精度(32位) |
ARGB4444 | 四通道低精度(16位) |
RGB565 | 屏幕默認(rèn)模式(16位) |
Alpha8 | 僅有透明通道(8位) |
PS:其中字母表示通道類型,數(shù)值表示該類型用多少位二進(jìn)制來(lái)描述王暗。如ARGB8888則表示有四個(gè)通道(ARGB),每個(gè)對(duì)應(yīng)的通道均用8位來(lái)描述悔据。
注意:我們常用的是ARGB8888和ARGB4444,而在所有的安卓設(shè)備屏幕上默認(rèn)的模式都是RGB565,請(qǐng)留意這一點(diǎn)俗壹。
以ARGB8888為例介紹顏色定義:
類型 | 解釋 | 0(0x00) | 255(0xff) |
---|---|---|---|
A(Alpha) | 透明度 | 透明 | 不透明 |
R(Red) | 紅色 | 無(wú)色 | 紅色 |
G(Green) | 綠色 | 無(wú)色 | 綠色 |
B(Blue) | 藍(lán)色 | 無(wú)色 | 藍(lán)色 |
其中 A R G B 的取值范圍均為0255(即16進(jìn)制的0x000xff)
A 從0x00到0xff表示從透明到不透明科汗。
RGB 從0x00到0xff表示顏色從淺到深。
當(dāng)RGB全取最小值(0或0x000000)時(shí)顏色為黑色策肝,全取最大值(255或0xffffff)時(shí)顏色為白色
4.2 幾種創(chuàng)建或使用顏色的方式
(1)java中定義顏色
int color = Color.GRAY; //灰色
由于Color類提供的顏色僅為有限的幾個(gè)肛捍,通常還是用ARGB值進(jìn)行表示。
int color = Color.argb(127, 255, 0, 0); //半透明紅色
int color = 0xaaff0000; //帶有透明度的紅色
(2)在xml文件中定義顏色
在/res/values/color.xml 文件中如下定義:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="red">#ff0000</color>
<color name="green">#00ff00</color>
</resources>
詳解: 在以上x(chóng)ml文件中定義了兩個(gè)顏色之众,紅色和藍(lán)色拙毫,是沒(méi)有alpha(透明)通道的。
定義顏色以‘#’開(kāi)頭棺禾,后面跟十六進(jìn)制的值缀蹄,有如下幾種定義方式:
#f00 //低精度 - 不帶透明通道紅色
#af00 //低精度 - 帶透明通道紅色
#ff0000 //高精度 - 不帶透明通道紅色
#aaff0000 //高精度 - 帶透明通道紅色
(3)在java文件中引用xml中定義的顏色:
int color = getResources().getColor(R.color.mycolor);
int color = getColor(R.color.myColor); //API 23 及以上支持該方法
(4)在xml文件(layout或style)中引用或者創(chuàng)建顏色
<!--在style文件中引用-->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/red</item>
</style>
android:background="@color/red" //引用在/res/values/color.xml 中定義的顏色
android:background="#ff0000" //創(chuàng)建并使用顏色
4.3 顏色拾取工具
顏色都是用RGB值定義的,而我們一般是無(wú)法直觀的知道自己需要顏色的值膘婶,需要借用取色工具直接從圖片或者其他地方獲取顏色的RGB值缺前。
(1)屏幕取色工具
取色調(diào)色工具,可以從屏幕取色或者使用調(diào)色板調(diào)制顏色悬襟,非常小而精簡(jiǎn)衅码。
(2)Picpick
功能更加強(qiáng)大的工具:PicPick。
PicPick具備了截取全屏脊岳、活動(dòng)窗口逝段、指定區(qū)域、固定區(qū)域割捅、手繪區(qū)域功能奶躯,支持滾動(dòng)截屏,屏幕取色亿驾,支持雙顯示器嘹黔,具備白板、屏幕標(biāo)尺莫瞬、直角座標(biāo)或極座標(biāo)顯示與測(cè)量儡蔓,具備強(qiáng)大的圖像編輯和標(biāo)注功能。
4.4 顏色混合模式(Alpha通道相關(guān))
通過(guò)前面介紹我們知道顏色一般都是四個(gè)通道(ARGB)的疼邀,其中(RGB)控制的是顏色,而A(Alpha)控制的是透明度喂江。
因?yàn)槲覀兊娘@示屏是沒(méi)法透明的,因此最終顯示在屏幕上的顏色里可以認(rèn)為沒(méi)有Alpha通道檩小。Alpha通道主要在兩個(gè)圖像混合的時(shí)候生效开呐。
默認(rèn)情況下烟勋,當(dāng)一個(gè)顏色繪制到Canvas上時(shí)的混合模式是這樣計(jì)算的:
(RGB通道) 最終顏色 = 繪制的顏色 + (1 - 繪制顏色的透明度) × Canvas上的原有顏色规求。
注意:
1.這里我們一般把每個(gè)通道的取值從0(0x00)到255(0xff)映射到0到1的浮點(diǎn)數(shù)表示筐付。
2.這里等式右邊的“繪制的顏色"、“Canvas上的原有顏色”都是經(jīng)過(guò)預(yù)乘了自己的Alpha通道的值阻肿。如繪制顏色:0x88ffffff瓦戚,那么參與運(yùn)算時(shí)的每個(gè)顏色通道的值不是1.0,而是(1.0 * 0.5333 = 0.5333)丛塌。 (其中0.5333 = 0x88/0xff)
使用這種方式的混合较解,就會(huì)造成后繪制的內(nèi)容以半透明的方式疊在上面的視覺(jué)效果。
其實(shí)還可以有不同的混合模式供我們選擇赴邻,用Paint.setXfermode印衔,指定不同的PorterDuff.Mode。
下表是各個(gè)PorterDuff模式的混合計(jì)算公式:(D指原本在Canvas上的內(nèi)容dst姥敛,S指繪制輸入的內(nèi)容src奸焙,a指alpha通道,c指RGB各個(gè)通道)
混合模式 | 計(jì)算公式 |
---|---|
ADD | Saturate(S + D) |
CLEAR | [0, 0] |
DARKEN | [Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] |
DST | [Da, Dc] |
DST_ATOP | [Sa, Sa * Dc + Sc * (1 - Da)] |
DST_IN | [Sa * Da, Sa * Dc] |
DST_OUT | [Da * (1 - Sa), Dc * (1 - Sa)] |
DST_OVER | [Sa + (1 - Sa)Da, Rc = Dc + (1 - Da)Sc] |
LIGHTEN | [Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] |
MULTIPLY | [Sa * Da, Sc * Dc] |
SCREEN | [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] |
SRC | [Sa, Sc] |
SRC_ATOP | [Da, Sc * Da + (1 - Sa) * Dc] |
SRC_IN | [Sa * Da, Sc * Da] |
SRC_OUT | [Sa * (1 - Da), Sc * (1 - Da)] |
SRC_OVER | [Sa + (1 - Sa)Da, Rc = Sc + (1 - Sa)Dc] |
XOR | [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] |
用示例圖來(lái)查看使用不同模式時(shí)的混合效果如下(src表示輸入的圖彤敛,dst表示原Canvas上的內(nèi)容):
4.4 顏色相關(guān)其他 (也比較重要与帆,所以拷貝過(guò)來(lái),為后面使用 Pain 做鋪墊)
(1) 填充顏色
之前說(shuō)過(guò)Canvas.draw*指定了繪制的區(qū)域墨榄。而區(qū)域里的填充顏色是由Paint來(lái)指定的玄糟。
Paint.setColor指定純色。
Paint.setShader可指定:BitmapShader, LinearGradient, RadialGradient, SweepGradient, ComposeShader袄秩。
BitmapShader:圖片填充阵翎。
LinearGradient, RadialGradient, SweepGradient:漸變填充。
ComposeShader:疊加前面的某兩種播揪≈可選擇PorterDuff混合模式。
如果既調(diào)用了setColor猪狈,又調(diào)用了setShader箱沦,則setShader生效。如果同時(shí)用setColor或setAlpha設(shè)置了透明度雇庙,則透明度也會(huì)生效谓形。(會(huì)和Shader的透明度疊加)
如果使用drawBitmap輸入一個(gè)只有alpha的圖片(可用Bitmap.extractAlpha方法獲得),則會(huì)以alpha圖片為mask疆前,繪制出shader/color的顏色寒跳。
(2)ColorFilter
通過(guò)ColorFilter可以對(duì)一次繪制的所有像素做一個(gè)通用處理。
Paint.setColorFilter: LightingColorFilter, PorterDuffColorFilter, ColorMatrixColorFilter竹椒。
這可以整體上改變這一次draw的內(nèi)容童太,比如讓顏色更暗、更亮等。
這里重點(diǎn)介紹下ColorMatrixColorFilter书释。
ColorMatrix是4x5矩陣翘贮,定義其每個(gè)元素如下:
{ a, b, c, d, e,
f, g, h, i, j,
k, l, m, n, o,
p, q, r, s, t }
則ColorMatrix的最終運(yùn)算方式如下:
R' = aR + bG + cB + dA + e;
G' = fR + gG + hB + iA + j;
B' = kR + lG + mB + nA + o;
A' = pR + qG + rB + sA + t;
(3)繪圖API架構(gòu)
整個(gè)繪制流水線大概如下:(我們能定義的部分用藍(lán)色表示)
考慮動(dòng)畫(huà)實(shí)現(xiàn)的時(shí)候一般從兩個(gè)角度來(lái)思考:
宏觀角度:有幾個(gè)變化量,分別是什么爆惧。動(dòng)畫(huà)從開(kāi)始到結(jié)束的流程狸页。
微觀角度:從某一幀上去想,在變化量為某個(gè)數(shù)值時(shí)的圖像扯再,該怎么繪制芍耘。
把這兩者分開(kāi)去想,就會(huì)比較清晰熄阻。