繪製的基本要素
自定義繪製的最基本的步驟是,提前創建好Paint對象,重寫onDraw()
,把繪製代碼寫在onDraw
裏面.
一、Canvas.drawXXX() 和 Paint 基礎
-
drawXXX()
方法:在整個繪製區域統一塗上指定的顏色。-
Canvas.drawColor(@ColorInt int color) 顏色填充
-
drawRGB(int r, int g, int b) 和 drawARGB(int a, int r, int g, int b) 作用同上
-
drawCircle(float centerX, float centerY, float radius, Paint paint) 畫圓
-
drawRect(float left, float top, float right, float bottom, Paint paint) 畫矩形
-
drawPoint(float x, float y, Paint paint) 畫點
-
drawPoints(float[] pts, int offset, int count, Paint paint) / drawPoints(float[] pts, Paint paint) 畫多個點
-
drawOval(float left, float top, float right, float bottom, Paint paint) 畫橢圓
left
,top
,right
,bottom
是這個橢圓的左、上、右、下四個邊界點的座標。 -
drawLine(float startX, float startY, float stopX, float stopY, Paint paint) 畫線
-
drawLines(float[] pts, int offset, int count, Paint paint) / drawLines(float[] pts, Paint paint) 畫多條線
-
drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint) 畫圓角矩形
-
drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 繪製弧形或扇形
startAngle
是弧形的起始角度(x 軸的正,是 0 度的位置;順時針爲正角度,逆時針爲負角度),sweepAngle
是弧形劃過的角度;useCenter
表示是否連接到圓心,如果不連接到圓心,就是弧形,如果連接到圓心,就是扇形 -
drawPath(Path path, Paint paint) 畫自定義圖形
path
參數是用來描述圖形路徑的對象,path
的類型是Path
。Path
可以描述直線、二次曲線、三次曲線、圓、橢圓、弧形、矩形、圓角矩形。Path
有兩類方法,一類是直接描述路徑的,另一類是輔助的設置或計算1. 直接描述路徑
這一類的方法可以分鐘兩組:添加子圖形和畫線
-
addXxx()
——添加子圖形狀path.AddXxx())
+canvas.drawPath(path, paint)
這種寫法,和直接使用canvas.drawXxx()
的效果是一樣的,所以如果只畫一個圖形,沒必要用Path
,直接用drawXXX
就可以了,drawPath
一般是在繪製組合圖形時纔會使用。 -
xxxTo
——畫線(包括直線和曲線)這一組和上面的區別在於,上一組是添加的完整封閉圖像,而這一組添加的只有一條線
-
lineTo(float x, float y) / rLineTo(float x, float y) 從當前位置向目標位置畫一條直線
-
quadTo(float x1, float y1, float x2, float y2) / rQuadTo(float dx1, float dy1, float dx2, float dy2) 畫二次貝塞爾曲線,x1, y1 和 x2, y2 則分別是控制點和終點的座標
貝塞爾曲線:貝塞爾曲線是幾何上的一種曲線,它通過起點、控制點和終點描述一條曲線,主要用於計算機圖形學
-
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) / rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 畫三次貝塞爾曲線
-
moveTo(float x, float y) / rMoveTo(float x, float y) 移動到目標位置(不畫線了)
moveTo(x,y)
不添加圖形,它會設置圖形的起點 -
arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)畫弧形
forceMoveTo
參數用於表述從當前點到弧的起點之間是否要畫線,爲true是不畫線 -
addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle)
這也是一個添加弧形的方法,它是直接使用
forceMoveTo=true
的簡化版本 -
close 封閉當前字圖形
把當前的子圖形封閉,即由當前位置向當前子圖形的起點繪製一條直線
-
2.輔助的設置或計算
-
Path.setFillType(fillType)
前面在說 dir 參數的時候提到, Path.setFillType(fillType) 是用來設置圖形自相交時的填充算法的:
FillType的取值有四個:- EVEN_ODD
- WINDING(默認值)
- INVERSE_EVEN_ODD
- INVENRSE_SINDING
後面的兩個帶有 INVERSE_ 前綴的,只是前兩個的反色版本,只要弄懂前兩個即可
EVEN_ODD 和 WINDING 的原理
-
EVEN_ODD
即even_odd rule(奇偶原則):對於平面中的任意一點,想任何方向射出一條射線,這條射線和圖形相交(不包含相切)的次數如果是奇數,則這個點被認爲在圖像內部,是要被塗色的區域;如果是偶數,則這個點被認爲在圖形外部,是不被塗色的區域。
如下爲左右兩圓相交的示例
-
WINDING
即 non-zero winding rule(非零環繞數原則):首先他需要你圖形的所有線條都是有繪製方向的,然後同樣是從平面中的點向任意方向射出一條射線,以0爲初始值,對於射線和圖形的所有焦點,遇到每個順時針的交點把結果加1,遇到每個逆時針的交點把結果減1,最終把所有的交點都算上,如果得到的結果不是0,則認爲這個點在圖形內部,是要被塗色的區域;如果是0則認爲這個點在圖形的外部,是不被塗色的區域
-
-
drawBitmap(Bitmap bitmap, float left, float top, Paint paint) 畫 Bitmap
-
drawText(String text, float x, float y, Paint paint) 繪製文字
-
-
Paint基礎
-
Paint.setColor(int color) 設置顏色
設置畫筆的顏色,後面使用該畫筆繪製的圖形將用這裏設置的顏色填充
-
Paint.setStyle(Paint.Style style) 設置繪製模式
Style有三種模式:FILL
,STROKE
,FILL_AND_STROKE
。FILL
是填充模式,STROKE
是畫線模式(描邊),FILL_AND_STROKE
是兩種模式一併使用,即畫線有填充。它的默認只是FILL
,填充模式 -
Paint.setStrokeWidth(float width) 設置線條寬度
設置描邊的線條寬度
-
Paint.setAntiAlias(boolean aa) 設置抗鋸齒開關
或者在
new Paint()
的時候加上一個ANTI_ALIAS_FLAG
參數也可以沒有開啓抗鋸齒的時候,圖形或有毛邊現象。對比效果如下
爲什麼抗鋸齒開啓之後的圖形邊緣會更加平滑呢?因爲抗鋸齒的原理是:修改圖形邊緣處的像素顏色,從而讓圖形在肉眼看來具有更加平滑的感覺
未開啓抗鋸齒的圓,所有像素都是同樣的黑色,而開啓了抗鋸齒的圓,邊緣的顏色被略微改變了。這種改變可以讓人眼有邊緣平滑的感覺,但從某種角度講,它也造成了圖形的顏色失真。 -
Paint.setTextSize(float textSize) 設置文字的大小
Android 的座標系:
在 Android 裏,每個 View 都有一個自己的座標系,彼此之間是不影響的。這個座標系的原點是 View 左上角的那個點;水平方向是 x 軸,右正左負;豎直方向是 y 軸,下正上負,
如下圖
-
二、Paint詳解
henCoder鏈接https://juejin.im/post/596baf5f6fb9a06bb15a3df9
Paint的API大致可以分爲4類
- 顏色
- 效果
- drawText相關
- 初始化
1)顏色
Canvas
繪製的內容,有三層對顏色的處理:基本顏色,ColorFilter,Xfermode
-
基本顏色
像素的基本顏色,根據繪製內容而有不同的控制方式:Canvas的顏色填充類方法
drawColor/RGB/ARGB()
的顏色,直接寫在方法參數裏;drawBitmap()
的顏色,是有Bitmap
對象來提供的;除此之外的圖形和文字的繪製,他們的顏色使用paint參數來額外設置;paint設置顏色有兩種方法:一種是直接用
Paint.setColor/ARGB()
來設置顏色,另一種是使用Shader
來指定着色方案paint使用 setShader(Shader shader)方法 來設置Shader
Shader,中文名爲着色器,它是圖像領域裏的一個通用概念,它和直接設置顏色的區別是,着色器設置的是一個顏色方案,或者說是一套着色規則。當設置了Shader之後,paint在繪製圖形和文字時就不使用
setColor/ARGB()
設置的顏色了,而是使用Shader
的方案中的顏色Android中繪製不直接使用
Shader
,而是使用它的幾個子類。包括-
LinearGradient
線性漸變設置兩個點和兩種顏色,以這兩個點位斷點,使用兩種顏色的漸變來繪製顏色
-
RadiaGraient
輻射漸變從中心向周圍輻射狀的漸變。需要設置中心的顏色和邊緣顏色
-
SweepGradient
掃描漸變以掃描中心爲原點,從x軸正向開始,從開始顏色掃描360度到終止顏色
-
BitmapShader
用Bitmap
來着色用Bitmap的像素來作爲圖形和文字的填充
-
ComposeShader
把兩個Shader
混合使用
構造方法:
ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)
最後的參數mode用於指定兩個Shader的疊加模式
PorterDuff.Mode
PorterDuff.Mode
是用來指定兩個圖像共同繪製時的顏色策略的。【顏色策略】是指把***源圖像繪製到目標圖像*** 處時應該怎樣確定二者結合後的顏色。PorterDuff.Mode
一共有 17 個,可以分爲兩類 -
-
setColorFilter(ColorFilter colorFilter)
ColorFilter,爲繪製設置顏色過濾,也就是爲繪製的內容設置一個統一的過濾策略,然後Canvas.drawXxx()方法會對每個像素都進行過濾後再繪製出來。
在Android裏ColorFilter並不被直接使用,而是使用他的三個子類
- LightingColorFilter
LightingColorFilter用來模擬簡單的光照效果。
LightingColorFilter的構造方法是
LightingColorFilter(int mul, int add)
,參數裏的mul
和add
都是和顏色值格式相同的int值,mul
用來和目標像素相乘,add
用來和目標相素相加R' = R * mul.R / 0xff + add.R G' = G * mul.G / 0xff + add.G B' = B * mul.B / 0xff + add.B
-
PorterDuffColorFilter
PorterDuffColorFilter
的作用是使用一個指定的顏色和一種指定的PorterDuff.Mode
來與繪製對象進行合成,它的構造方法是PorterDuffColorFilter(int color, PorterDuff.Mode mode)
其中的color
參數是指定的顏色,mode
參數是指定的PorterDuff.Mode
,跟ComposeShader
的一樣,不同的是PorterDuffColorFilter
只能指定一種顏色作爲源,而不是Bitmap
-
ColorMatrixColorFilter
ColorMatrixColorFilter
使用一個ColorMatrix
來對顏色進行處理。ColorMatrix
這個類,內部是一個4*5的矩陣[ a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t ]
通過計算,
ColorMatrix
可以把要繪製的像素進行轉換,對於顏色[R,G,B,A],轉換算法爲R’ = a*R + b*G + c*B + d*A + e; G’ = f*R + g*G + h*B + i*A + j; B’ = k*R + l*G + m*B + n*A + o; A’ = p*R + q*G + r*B + s*A + t;
-
setXfermode(Xfermode xfermode)
“Xfermode"其實就是"Transfer mode",用"X"來代替”Trans“是美國人喜歡用的簡寫方式.
Xfermode
指的是你要繪製的內容和Canvas
的目標位置的內容應該怎樣結合計算出最終的顏色。就是以 要繪製的內容 作爲 源圖像,以View中 已有的內容 作爲 目標圖像,選取一個 PorterDuff.Mode
作爲繪製內容的顏色處理方案。可以這麼記憶:把源圖像(要繪製的內容)繪製到目標圖像(已有的內容)
實際上創建 Xfermode
的的時候要使用它的子類PorterDuffXfermode
。
**Xfermode **注意事項
使用離屏緩衝
由於在繪製是,目標圖像是真個
View
的所有內容,而且View
自身的底色並不是默認的透明色,而且是一種不知道的顏色,導致在與源圖像混合計算後,不僅僅是已有的圖像進行了覆蓋,還使得圖像之外都變成裏黑色,就像下面導致要想使用
serXfermode()
正常繪製,必須使用離屏緩存把內容繪製在額外的層上,再把繪製的內容貼會到View中。使用離屏緩衝有如下兩種方式
Canvas.saveLaver()
saveLayer()
可以做短時的離屏緩衝。只要繪製前調用該方法保存,繪製後恢復即可int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(rectBitmap, 0, 0, paint); // 畫方 paint.setXfermode(xfermode); // 設置 Xfermode canvas.drawBitmap(circleBitmap, 0, 0, paint); // 畫圓 paint.setXfermode(null); // 用完及時清除 Xfermode canvas.restoreToCount(saved);
View.setLayerType
View.setLayerType()
是直接把整個View
都繪製在離屏緩衝中。
setLayerType(LAYER_TYPE_HARDWARE)
是使用GPU來緩衝,
setLayerType(LAYER_TYPE_SOFTWARE)
是直接用一個Bitmap
來緩衝控制好透明區域
使用Xfermode來繪製內容,除了上面還應該注意它的透明區域不要太小,要讓它足夠覆蓋到要和它結合的繪製的內容,否則結果無法達到我們的目標
2)效果
-
setAntiAlias (boolean aa) 設置抗鋸齒
-
setStyle(Paint.Style style)
-
線條形狀
設置線條形狀一共有四個方法
-
setStrokeWidth(float width)
設置線條寬度,默認值爲0
線條寬度爲0和1的區別
在進行幾何變換後,當線條放大到2倍時,如果線條寬度爲1,則線條寬度變爲2個像素,而若線條寬度爲0則它被鎖定爲1個像素,不受幾何變換的影響
Google 在文檔中把線條寬度爲 0 時稱作「hairline mode(髮際線模式)」。
-
setStrokeCap(Paint.Cap cap)
設置線頭的形狀。線頭形狀有三種
BUTT
平頭、ROUND
圓頭、SQUARE
方頭。默認爲BUTT
。當線條的寬度是1像素時,這三種線頭的表現是一致的,全爲一個像素的點,但當線條變粗時,他們將表現出不同的樣子
-
setStrokeJoin(Paint.Join join)
設置拐角的形狀。有三個值可以選擇:
MITER
尖角、BEVEL
平角和ROUND
圓角。默認爲MITER
。 -
setStrokeMiter(float miter)
這個方法是對
setStrokeJoin(Paint.Join join)
的補充,它用於設置MITER
型拐角的延長線的最大值。當線條拐角爲
MITER
時,拐角處的邊緣需要使用延長線來補償但是如果拐角的角度太小,就有可能會出現連接點過長的情況
所以爲了避免過長的尖角出現,
MITER
型連接點在尖角太長時,自動改用BEVEL
的方式來渲染連接點。setStrokeMiter(miter)
方法中的miter
參數就是對於轉角長度的限制,具體是指尖角的外援端點和內部拐角的距離與線條寬度的比。如下
-
-
色彩優化
-
setDither(bolean dither)
設置圖像抖動抖動是指把圖像從較高色彩深度(可用的顏色數)向較低色彩深度的區域繪製時(譬如 32 位的
ARGB_8888
->16 位色的ARGB_4444
),在圖像中有意地插入噪點,通過有規律的擾亂圖像來讓圖像對於肉眼更加真實的做法。抖動不可用在純色的繪製中。在實際的場景中,抖動更多的作用是在圖像降低色彩深度繪製時,避免出現大片的色帶與色塊。 -
setFilterBitmap(boolean filter)
設置是否使用雙線性過濾來繪製Bitmap
圖像在方法繪製的時候,默認使用的是最近鄰插值過濾,這種算法簡單,但會出現馬賽克效應;而如果開啓了雙線性過濾,就可以讓結果圖像顯得更加平滑
-
-
setPathEffect(PathEffect effect)
設置圖像輪廓效果Andorid 有6種
PathEffect
-
CornerPathEffect
把所有拐角都變爲圓角 -
DiscretePathEffect
把線條進行隨機的偏離具體的顯示方式是:把繪製改爲使用定長的線段來拼接,並且在拼接的時候對路徑進項隨機偏離
構造方法
DiscretePathEffect(float segmentLength, float deviation)
,其中segmentLength
是用來拼接的每個線段的長度,deviation
是偏離量 -
DashPathEffect
使用虛線來繪製線條構造方法
DashPathEffect(float[] intervals, float phase)
,第一個參數intervals
是一個數組,指定了虛線的格式:數組中的元素必須爲大於0的偶數,按照[劃線長度、空白長度、劃線長度、空白長度…]的順序排列,第二個參數phase
是虛線的便宜量 -
PathDashPathEffect
使用路徑來繪製虛線線條構造方法
PathDashPathEffect(Path shape, float advance, float phase, PathDashPathEffect.Style style)
,其中第一參數shape
是用來繪製的Path
;advance
是兩個相鄰的shape
段起點之間的間隔;phase
和上面一樣是虛線的偏移;最後一個參數style
是來用來指定拐彎改變時shape
的轉換方式。style
的類型爲PathDashPathEffect.Style
,是一個枚舉,有三個值:TRANSLATE
:位移ROTATE
:旋轉MORPH
:變體
-
SumPathEffect
組合兩種PathEffect
分別對目標同時進行繪製 -
ComposePathEffect
組合兩個PathEffect
,先使用一種PathEffect
,然後在對結果使用另一種PathEffect
進行改變
構造方法
ComposePathEffect(PathEffect outerpe, PathEffect innerpe)
,其中innerpe
是先應用的,outerpe
是後應用的。注意:
PathEffect
在有些情況下不支持硬件加速,需要關閉硬件加速才能正常使用:Canvas.drawLine()
和Canvas.drawLines()
方法畫直線時,setPathEffect()
是不支持硬件加速的;PathDashPathEffect
對硬件加速的支持也有問題,所以當使用PathDashPathEffect
的時候,最好也把硬件加速關了。 -
-
setShadowLayer(float radius, float dx, float dy, int shadowColor)
在之後繪製的內容下面加一層陰影,方法的參數裏,
radius
是陰影的模糊範圍;dx
dy
是陰影的偏移量;shadowColor
是陰影的顏色如果要清楚陰影層,使用
cleanShadowLayer
注意:
- 在硬件加速時,
setShadowLayer()
只支持文字的繪製,文字之外的繪製必須關閉硬件加速才能正常繪製陰影。 - 如果
shadowColor
是半透明的,陰影的透明度就是shadowColor
自己的透明度;而如果shadowColor
是不透明的,陰影的透明度就是paint
的透明度
- 在硬件加速時,
-
setMaskFilter(MaskFilter maskfilter)
爲之後的繪製的設置
MashFilter
。它與上面的陰影相反,他是繪製在繪製層上方的附加效果(遮罩),類似於背景和前景MaskFilter
有兩種-
BlurMaskFilter
模糊效果的MaskFilter
構造方法
BlurMaskFilter(float radius, BlurMaskFilter.Blur style)
,其中,radius
參數是模糊的範圍,style
四模糊的類型。一種有四種:-
NORMAL
:內外都模糊繪製 -
SOLID
:內部正常繪製,外部模糊 -
INNER
:內部模糊,外部不繪製 -
OUTER
:內部不繪製,外部模糊
-
-
EmbossMashFilter
浮雕效果的
MaskFilter
構造方法
EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius)
,其中direction
是一個 3 個元素的數組,指定了光源的方向;ambient
是環境光的強度,數值範圍是 0 到 1;specular
是炫光的係數;blurRadius
是應用光線的範圍
-
-
獲取繪製的Path
根據
paint
的設置,計算出繪製Path
或文字時的 實際 Path。所謂「實際Path
,指的就是drawPath()
的繪製內容的輪廓,要算上線條寬度和設置的PathEffect
。-
getFillPath(Path src, Path dst)
src
是原Path
,而dst
就是實際Path
的保存位置。getFillPath(src, dst)
會計算出實際Path
,然後把結果保存在dst
裏。 -
getTextPath(String text, int start, int end, float x, float y, Path path)
/getTextPath(char[] text, int index, int count, float x, float y, Path path)
getTextPath()
方法,獲取的就是目標文字所對應的Path
。這個就是所謂「文字的Path
」
3)初始化類
這一類方法專門用來初始化
Paint
對象,或者批量設置Paint
的多個屬性的方法-
reset()
重置
Paint
的所有屬性爲默認值,相當於一個重新new
一個,不過耗性能低 -
set(Paint src)
把
src
的所有屬性全部複製過來。 -
setFlags(int flags)
批量設置flags。相當於依次調用它們的
set
方法paint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
相當於
paint.setAntiAlias(true); paint.setDither(true);
setFlags(flags)
對應的get
方法是int getFlags()
。
-
三、文字的繪製
1)Canvas繪製文字的方式
-
drawText(String text, float x, float y, Paint paint)
text
是文字內容,x
和y
是文字的座標。這個座標並不是文字的左上角,而是一個與左下角比較接近的位置。drawText()
參數中的y
,指的是文字的**基線( baseline )** 的位置。x
點並不是文字左邊的位置,而是比它的左邊再往左一點點。是因爲絕大多數的字符,它們的寬度都是要略微大於實際顯示的寬度的。字符的左右兩邊會留出一部分空隙,用於文字之間的間隔,以及文字和邊框的間隔。而往左的那一點點就是文字的間隔 -
drawTextRun()
這個方法對中國人沒用,忽略
-
drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)
沿着一條
Path
來繪製文字。hOffset
和vOffset
。它們是文字相對於Path
的水平偏移量和豎直偏移量,利用它們可以調整文字的位置注意:
drawTextOnPath()
使用的Path
,拐彎處全用圓角,別用尖角。否則會像上面一樣很難看 -
StaticLayout
Canvas.drawText()
只能繪製單行的文字,而不能換行。而StaticLayout
支持換行,StaticLayout
並不是一個View
或者ViewGroup
,而是android.text.Layout
的子類,它是純粹用來繪製文字的。它既可以爲文字設置寬度上限來讓文字自動換行,也會在\n
處主動換行。構造方法是
StaticLayout(CharSequence source, TextPaint paint, int width, Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad)
,其中參數裏width
是文字區域的寬度,文字到達這個寬度後就會自動換行;
align
是文字的對齊方向;
spacingmult
是行間距的倍數,通常情況下填 1 就好;
spacingadd
是行間距的額外增加值,通常情況下填 0 就好;
includeadd
是指是否在文字上下添加額外的空間,來避免某些過高的字符的繪製出現越界。使用方法:
String text1 = "Lorem Ipsum is simply dummy text of the printing and typesetting industry."; StaticLayout staticLayout1 = new StaticLayout(text1, paint, 600, Layout.Alignment.ALIGN_NORMAL, 1, 0, true); canvas.save(); staticLayout1.draw(canvas); canvas.restore();
2)Paint對文字繪製的輔助
-
設置顯示效果類
-
setTextSize(float textSize)
設置文字大小。 -
setTypeface(Typeface typeface)
設置字體。paint.setTypeface(Typeface.DEFAULT); canvas.drawText(text, 100, 150, paint); paint.setTypeface(Typeface.SERIF); canvas.drawText(text, 100, 300, paint); paint.setTypeface(Typeface.createFromAsset(getContext().getAssets(), "Satisfy-Regular.ttf")); canvas.drawText(text, 100, 450, paint);
typeface 指的是某套字體(即 font family ),而 font 指的是一個 typeface 具體的某個 weight 和 size 的分支。
-
setFakeBoldText(boolean fakeBoldText)
否使用僞粗體。它並不是通過選用更高 weight 的字體讓文字變粗,而是通過程序在運行時把文字給「描粗」
-
setStrikeThruText(boolean strikeThruText)
是否加刪除線。 -
setUnderlineText(boolean underlineText)
是否條件下劃線 -
setTextSkewX(float skewX)
設置文字橫向錯切角度。 -
setTextScaleX(float scaleX)
設置文字橫向放縮 -
setLetterSpacing(float letterSpacing)
設置字符間距 -
setFontFeatureSettings(String settings)
用 CSS 的font-feature-settings
的方式來設置文字。 -
setTextAlign(Paint.Align align)
設置文字的對齊方式一共有三個值:
LEFT
CETNER
和RIGHT
。默認值爲LEFT
。 -
setTextLocale(Locale locale)
設置繪製所使用的Locale
-
setHinting(int mode)
設置是否啓用字體的 hinting (字體微調)Android 設備大多數都是是用的矢量字體。在字號較大的時候,矢量字體也能夠保持字體的圓潤。但是當文字的尺寸過小(比如高度小於 16 像素),有些文字會由於失去過多細節而變得不太好看。 hinting 技術就是爲了解決這種問題的:通過向字體中加入 hinting 信息,讓矢量字體在尺寸過小的時候得到針對性的修正,從而提高顯示效果。
-
setSubpixelText(boolean subpixelText)
是否開啓次像素級的抗鋸齒
-
-
測量文字尺寸類
-
float getFontSpacing()
獲取推薦的行距。
獲取推薦的兩行文字的baseline 的距離。這個值是系統根據文字的字體和字號自動計算的。它的作用是當你要手動繪製多行文字的時候,可以在換行draw的時候確認下一行文字的
y
座標 -
FontMetircs getFontMetrics()
獲取
Paint
的FontMetrics
FontMetrics
提供了幾個文字排版方面的數值:ascent
,descent
,top
,bottom
,leading
.
ascent
和descent
是上圖中綠色和橙色的線,它們限制普通字符的的頂部和底部範圍。在Android中,ascent
的值是圖中綠色線和baseline
的相對位移,它的值爲負(y座標向下爲正);descent
的值是圖中橙色和baseline
的相對位移,它的值爲正top
/bottom
的作用是限制所有字形的頂部和底部範圍。除了普通字符,有些字形的顯示範圍會超過ascent
和descent
,但是包括這些特殊字符都不會超過top
和bottom
。同上類似,top
的值是藍線相對baseline
的位移,值爲負;bottom
是紅線相對baseline
的值,值爲正leading
指的是行的額外間距,即在相鄰的兩行中上一行的bottom
和下一行的top
的距離leading
的本意是行距,即連個相鄰行baseline
中間的距離,但是在很多非專業領域,leading
被用來指代航的額外間距,Android中就是這樣。FontMetrics
提供的就是Paint
根據當前字體和字號,得出的這些值的推薦值。它把這些值以變量的形式存儲;ascent
和descent
這兩個值還可以通過Paint.ascent()
和Paint.descent()
來快捷獲取 -
getTextBounds(String text, int start, int end, Rect bounds)
獲取文字的顯示範圍
text
是要測量的文字,start
和end
分別是文字的起始和結束位置,bounds
是存儲文字顯示範圍的對象,方法在測算完成之後會把結果寫進 `bounds該方法測量的是文字的顯示範圍
-
float measureText(String text)
測量文字的寬度並返回
該方法測量的是文字繪製時所佔用的寬度,一個文字在界面中,往往需要佔用比他的實際顯示寬度更多一點的寬度。
-
getTextWidths(String text, float[] widths)
獲取字符串中每個字符的寬度,並把結果填入
widths
-
int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)
在給出最大寬度的前提下測量文字寬度,如果文字的寬度超出限制,則在臨近超限的位置截斷文字。
其返回值是截取的文字個數(沒有超限時,是文字總個數)。
方法中,
text
是要測量的文字;measureForwards
表示文字的測量方向,true
表示由左往右測量;maxWidth
是給出的寬度上限;measuredWidth
是用於接受數據,而不是用於提供數據的:方法測量完成後會把截取的文字寬度(如果寬度沒有超限,則爲文字總寬度)賦值給measuredWidth[0]
。這個方法可以用於多行文字的折行計算
-
光標相關
-
getRunAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset)
對於文字
text
計算出offset
個字符處光標的x
座標。start
end
是文字的起始和結束座標;contextStart
contextEnd
是上下文的起始和結束座標;isRtl
是文字的方向。 -
getOffsetForAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance)
給出一個位置的像素值
advance
,計算出文字text
中最接近這個位置的字符偏移量。start
end
是文字的起始和結束座標;contextStart
contextEnd
是上下文的起始和結束座標;isRtl
是文字方向;返回結果是對應的字符偏移量。 -
hasGlyph(String string)
檢查指定的字符串中是否是一個單獨的字形。
-
-
四、Canvas 對繪製的輔助
-
範圍裁切
-
clipRect()
將畫布裁切成一個矩形範圍,後面的繪製都在這個範圍內,超出範圍的部分將會被裁掉
-
clipPath
同上,只不過形狀不是矩形了,而是一個Path
-
-
幾何變換
-
使用
Canvas
來做常見的二維變幻-
Canvas.translate(float dx, float dy)
平移將畫布分別沿着x軸平移
dx
y軸平移dy
-
Canvas.rotate(float degrees, float px, float py)
旋轉degrees
是旋轉角度,同樣是對畫布的旋轉,選裝中心是px
,py
-
Canvas.scale(float sx, float sy, float px, float py)
放縮sx
sy
是橫向和縱向的放縮倍數;px
py
是放縮的軸心。 -
skew(float sx, float sy)
錯切參數裏的
sx
和sy
是 x 方向和 y 方向的錯切係數
-
-
使用
Matrix
來做變換-
使用
Matrix
來做常見變換一般步驟爲:
- 創建
Matrix
對象; - 調用
Matrix
的pre/postTranslate/Rotate/Scale/Skew()
方法來設置幾何變換; - 使用
Canvas.setMatrix(matrix)
或Canvas.concat(matrix)
來把幾何變換應用到Canvas
。
把
Matrix
應用到Canvas
有兩個方法:Canvas.setMatrix(matrix)
:用Matrix
直接替換Canvas
當前的變換矩陣(hencoder 說不同的手機系統中 setMatrix(matrix) 的行爲可能不一致,建議用下面的)。Canvas.concat(matrix)
:用Canavas
當前的變換矩陣和Matrix
相乘,及將Canvas
當前的變換疊加上Matrix
的變換
- 創建
-
使用
Matrix
來做自定義變換Matrix
的自定義變換使用的是setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount)
方法setPolyToPoly()
的作用是通過多點映射的方式來直接設置變換。多點影射的意思就是把指定的點移動到給出的位置,從而發生形變
src
和dst
是源點集合目標點集;srcIndex
和dstIndex
是第一個點的偏移;pointCount
是採集的點的個數(個數不能大於 4,因爲大於 4 個點就無法計算變換了)Matrix matrix = new Matrix(); float pointsSrc = {left, top, right, top, left, bottom, right, bottom}; float pointsDst = {left - 10, top + 50, right + 120, top - 90, left + 20, bottom + 30, right + 20, bottom + 60}; ... matrix.reset(); matrix.setPolyToPoly(pointsSrc, 0, pointsDst, 0, 4); canvas.save(); canvas.concat(matrix); canvas.drawBitmap(bitmap, x, y, paint); canvas.restore();
-
-
-
使用
Camera
來做三維變換-
Camera.rotate*()
三維旋轉rotateX(deg)
rotateY(deg)
rotateZ(deg)
rotate(x, y, z)
-
Camera.translate(float x, float y, float z)
移動 -
Camera.setLocation(x, y, z)
設置虛擬相機的位置它的參數的單位不是像素,而是 inch,英寸
Camera 的位置單位是英寸,英寸和像素的換算單位被寫死爲了 72 像素
-
五、繪製順序
-
寫在
super.onDraw()
的下面由於繪製代碼會在原有內容繪製結束之後才執行,所以繪製內容就會蓋住控件原來的內容。
常見的情況:爲控件增加點綴性內容;比如,在 Debug 模式下繪製出
ImageView
的圖像尺寸信息 -
寫在
super.onDraw()
的上面由於繪製代碼會執行在原有內容的繪製之前,所以繪製的內容會被控件的原內容蓋住
一般可以通過在文字的下層繪製純色矩形來作爲「強調色」
-
dispatchDraw()
:繪製子 View 的方法當我們繼承一個
ViewGroup
類型的View時,在onDraw
裏繪製了內容會正常顯示(前提是做了setWillNotDraw(false)
等會讓ViewGroup
走Ondraw
方法的設置)但是當我們添加自view後,我們的繪製信息會消失,即使我們的繪製內容寫在
super.onDraw()
的下面。這是因爲在繪製過程中,每一個
ViewGroup
會先調用自己的onDraw()
來繪製完自己的主體之後再去繪製它的子 View。所以在onDraw
這個方法裏我們永遠無法讓我們的繪製內容顯示在子View上方上面說的繪製子View的方法叫做
dispatchDraw()
,我們只要重寫這個方法,並把內容寫在super.dispatchDraw()
的下面即可 -
繪製過程簡述
一個完整的繪製過程會依次繪製以下幾個內容:
- 背景
- 主體(
onDraw()
) - 子 View(
dispatchDraw()
) - 滑動邊緣漸變和滑動條
- 前景
-
onDrawForeground()
這個方法是 API 23 才引入的,所以在重寫這個方法的時候要確認你的
minSdk
達到了 23,不然低版本的手機裝上你的軟件會沒有效果如果你把繪製代碼寫在了
super.onDrawForeground()
的下面,繪製代碼會在滑動邊緣漸變、滑動條和前景之後被執行,那麼繪製內容將會蓋住滑動邊緣漸變、滑動條和前景。如果你把繪製代碼寫在了
super.onDrawForeground()
的上面,繪製內容就會在dispatchDraw()
和super.onDrawForeground()
之間執行,那麼繪製內容會蓋住子 View,但被滑動邊緣漸變、滑動條以及前景蓋住:無法在滑動邊緣漸變、滑動條和前景之間插入繪製代碼
-
draw() 總調度方法
draw() 是繪製過程的總調度方法。一個 View 的整個繪製過程都發生在
draw()
方法裏。背景、主體、子 View 、滑動相關以及前景的繪製,它們其實都是在draw()
方法裏的。// View.java 的 draw() 方法的簡化版大致結構(是大致結構,不是源碼哦): public void draw(Canvas canvas) { ... drawBackground(Canvas); // 繪製背景(不能重寫) onDraw(Canvas); // 繪製主體 dispatchDraw(Canvas); // 繪製子 View onDrawForeground(Canvas); // 繪製滑動相關和前景 ... }
如果把繪製代碼寫在
super.draw()
的下面,那麼這段代碼會在其他所有繪製完成之後再執行,也就是說,它的繪製內容會蓋住其他的所有繪製內容。如果把繪製代碼寫在
super.draw()
的上面,那麼這段代碼會在其他所有繪製之前被執行,所以這部分繪製內容會被其他所有的內容蓋住,包括背景。是的,背景也會蓋住它。
- 在
ViewGroup
的子類中重寫除dispatchDraw()
以外的繪製方法時,可能需要調用setWillNotDraw(false)
;- 在重寫的方法有多個選擇時,優先選擇
onDraw()
。因爲 Android 有相關的優化,可以在不需要重繪的時候自動跳過onDraw()
的重複執行