6.自定義View-繪製

繪製的基本要素

自定義繪製的最基本的步驟是,提前創建好Paint對象,重寫onDraw(),把繪製代碼寫在onDraw裏面.

一、Canvas.drawXXX() 和 Paint 基礎

  1. drawXXX() 方法:在整個繪製區域統一塗上指定的顏色。

    1. Canvas.drawColor(@ColorInt int color) 顏色填充

    2. drawRGB(int r, int g, int b) 和 drawARGB(int a, int r, int g, int b) 作用同上

    3. drawCircle(float centerX, float centerY, float radius, Paint paint) 畫圓

    4. drawRect(float left, float top, float right, float bottom, Paint paint) 畫矩形

    5. drawPoint(float x, float y, Paint paint) 畫點

    6. drawPoints(float[] pts, int offset, int count, Paint paint) / drawPoints(float[] pts, Paint paint) 畫多個點

    7. drawOval(float left, float top, float right, float bottom, Paint paint) 畫橢圓

      left, top, right, bottom 是這個橢圓的左、上、右、下四個邊界點的座標。

    8. drawLine(float startX, float startY, float stopX, float stopY, Paint paint) 畫線

    9. drawLines(float[] pts, int offset, int count, Paint paint) / drawLines(float[] pts, Paint paint) 畫多條線

    10. drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint) 畫圓角矩形

    11. drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 繪製弧形或扇形

      startAngle 是弧形的起始角度(x 軸的正,是 0 度的位置;順時針爲正角度,逆時針爲負角度),sweepAngle 是弧形劃過的角度;useCenter 表示是否連接到圓心,如果不連接到圓心,就是弧形,如果連接到圓心,就是扇形

    12. drawPath(Path path, Paint paint) 畫自定義圖形

      path參數是用來描述圖形路徑的對象,path的類型是PathPath可以描述直線、二次曲線、三次曲線、圓、橢圓、弧形、矩形、圓角矩形。

      Path有兩類方法,一類是直接描述路徑的,另一類是輔助的設置或計算

      1. 直接描述路徑

      這一類的方法可以分鐘兩組:添加子圖形和畫線

      1. addXxx() ——添加子圖形狀

        path.AddXxx()) + canvas.drawPath(path, paint) 這種寫法,和直接使用 canvas.drawXxx() 的效果是一樣的,所以如果只畫一個圖形,沒必要用Path,直接用drawXXX就可以了,drawPath一般是在繪製組合圖形時纔會使用。

      2. 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.輔助的設置或計算
      1. Path.setFillType(fillType)

        前面在說 dir 參數的時候提到, Path.setFillType(fillType) 是用來設置圖形自相交時的填充算法的:

        在這裏插入圖片描述
        FillType的取值有四個:

        • EVEN_ODD
        • WINDING(默認值)
        • INVERSE_EVEN_ODD
        • INVENRSE_SINDING

        後面的兩個帶有 INVERSE_ 前綴的,只是前兩個的反色版本,只要弄懂前兩個即可

        EVEN_ODD 和 WINDING 的原理
        1. EVEN_ODD

          即even_odd rule(奇偶原則):對於平面中的任意一點,想任何方向射出一條射線,這條射線和圖形相交(不包含相切)的次數如果是奇數,則這個點被認爲在圖像內部,是要被塗色的區域;如果是偶數,則這個點被認爲在圖形外部,是不被塗色的區域。

          如下爲左右兩圓相交的示例

          6eab1882cbbc67c5c9bef7ffff7cb11e

        2. WINDING

          即 non-zero winding rule(非零環繞數原則):首先他需要你圖形的所有線條都是有繪製方向的,然後同樣是從平面中的點向任意方向射出一條射線,以0爲初始值,對於射線和圖形的所有焦點,遇到每個順時針的交點把結果加1,遇到每個逆時針的交點把結果減1,最終把所有的交點都算上,如果得到的結果不是0,則認爲這個點在圖形內部,是要被塗色的區域;如果是0則認爲這個點在圖形的外部,是不被塗色的區域
          af36e2f187093a0cf0377a52797306f7

    13. drawBitmap(Bitmap bitmap, float left, float top, Paint paint) 畫 Bitmap

    14. drawText(String text, float x, float y, Paint paint) 繪製文字

  2. Paint基礎

    • Paint.setColor(int color) 設置顏色

      設置畫筆的顏色,後面使用該畫筆繪製的圖形將用這裏設置的顏色填充

    • Paint.setStyle(Paint.Style style) 設置繪製模式
      Style有三種模式:FILLSTROKEFILL_AND_STROKEFILL是填充模式,STROKE是畫線模式(描邊),FILL_AND_STROKE是兩種模式一併使用,即畫線有填充。它的默認只是FILL,填充模式

    • Paint.setStrokeWidth(float width) 設置線條寬度

      設置描邊的線條寬度

    • Paint.setAntiAlias(boolean aa) 設置抗鋸齒開關

      或者在new Paint() 的時候加上一個 ANTI_ALIAS_FLAG 參數也可以

      沒有開啓抗鋸齒的時候,圖形或有毛邊現象。對比效果如下
      1aeb52f20ba02eee091205ecef13280c

      爲什麼抗鋸齒開啓之後的圖形邊緣會更加平滑呢?因爲抗鋸齒的原理是:修改圖形邊緣處的像素顏色,從而讓圖形在肉眼看來具有更加平滑的感覺

      cdca72028f8aed2888826e5fef014260
      未開啓抗鋸齒的圓,所有像素都是同樣的黑色,而開啓了抗鋸齒的圓,邊緣的顏色被略微改變了。這種改變可以讓人眼有邊緣平滑的感覺,但從某種角度講,它也造成了圖形的顏色失真。

    • 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

  1. 基本顏色

    像素的基本顏色,根據繪製內容而有不同的控制方式: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度到終止顏色

    • BitmapShaderBitmap 來着色

      用Bitmap的像素來作爲圖形和文字的填充

    • ComposeShader 把兩個Shader混合使用

    構造方法:ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)

    最後的參數mode用於指定兩個Shader的疊加模式

    PorterDuff.Mode

    PorterDuff.Mode 是用來指定兩個圖像共同繪製時的顏色策略的。【顏色策略】是指把***源圖像繪製到目標圖像*** 處時應該怎樣確定二者結合後的顏色。

    PorterDuff.Mode 一共有 17 個,可以分爲兩類

    1. Alpha合成

      Alpha合成,就是【PorterDuff】這個詞所代指的算法,來源於Tomas Porter和Tom Duf兩個人共同發表的論文,論文描述了12種將兩個圖像共同繪製的算法。而這些算法都是關於Alpha通道計算的,後來就吧這類計算稱爲Alpha 合成

      3444dfcd8745677dc668ffd94fd26cb8

      Alpha合成

      在這裏插入圖片描述

    2. 混合

      這一類操作的是顏色的本身,並不是Alpha通道,不屬於Alpha合成,和 Porter 與 Duff 這兩個人也沒什麼關係

      混合

      4be41991c90e3eacf92e9378168b681d

  2. setColorFilter(ColorFilter colorFilter)

    ColorFilter,爲繪製設置顏色過濾,也就是爲繪製的內容設置一個統一的過濾策略,然後Canvas.drawXxx()方法會對每個像素都進行過濾後再繪製出來。

    在Android裏ColorFilter並不被直接使用,而是使用他的三個子類

    • LightingColorFilter

    LightingColorFilter用來模擬簡單的光照效果。

    LightingColorFilter的構造方法是 LightingColorFilter(int mul, int add) ,參數裏的muladd都是和顏色值格式相同的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;
      
  3. setXfermode(Xfermode xfermode)

“Xfermode"其實就是"Transfer mode",用"X"來代替”Trans“是美國人喜歡用的簡寫方式.

Xfermode指的是你要繪製的內容和Canvas的目標位置的內容應該怎樣結合計算出最終的顏色。就是以 要繪製的內容 作爲 源圖像,以View中 已有的內容 作爲 目標圖像,選取一個 PorterDuff.Mode 作爲繪製內容的顏色處理方案。可以這麼記憶:把源圖像(要繪製的內容)繪製到目標圖像(已有的內容)

實際上創建 Xfermode 的的時候要使用它的子類PorterDuffXfermode

**Xfermode **注意事項

  1. 使用離屏緩衝

    由於在繪製是,目標圖像是真個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來緩衝

  2. 控制好透明區域

    使用Xfermode來繪製內容,除了上面還應該注意它的透明區域不要太小,要讓它足夠覆蓋到要和它結合的繪製的內容,否則結果無法達到我們的目標

2)效果
  1. setAntiAlias (boolean aa) 設置抗鋸齒

  2. setStyle(Paint.Style style)

  3. 線條形狀

    設置線條形狀一共有四個方法

    • setStrokeWidth(float width)

      設置線條寬度,默認值爲0

      線條寬度爲0和1的區別

      在進行幾何變換後,當線條放大到2倍時,如果線條寬度爲1,則線條寬度變爲2個像素,而若線條寬度爲0則它被鎖定爲1個像素,不受幾何變換的影響

      Google 在文檔中把線條寬度爲 0 時稱作「hairline mode(髮際線模式)」。

    • setStrokeCap(Paint.Cap cap)

      設置線頭的形狀。線頭形狀有三種BUTT 平頭、ROUND 圓頭、SQUARE 方頭。默認爲 BUTT

      當線條的寬度是1像素時,這三種線頭的表現是一致的,全爲一個像素的點,但當線條變粗時,他們將表現出不同的樣子

      8fa8c7cfb47fa0f6d5d3c8c18635ae82

    • setStrokeJoin(Paint.Join join)

      設置拐角的形狀。有三個值可以選擇:MITER 尖角、 BEVEL 平角和 ROUND 圓角。默認爲 MITER

      6a5f1de602276ce42cc5a7313a59fc08

    • setStrokeMiter(float miter)

      這個方法是對setStrokeJoin(Paint.Join join)的補充,它用於設置 MITER 型拐角的延長線的最大值。

      當線條拐角爲MITER時,拐角處的邊緣需要使用延長線來補償

      img

      但是如果拐角的角度太小,就有可能會出現連接點過長的情況

      img

      所以爲了避免過長的尖角出現,MITER 型連接點在尖角太長時,自動改用BEVEL 的方式來渲染連接點。

      setStrokeMiter(miter) 方法中的miter參數就是對於轉角長度的限制,具體是指尖角的外援端點和內部拐角的距離與線條寬度的比。如下

      img
  4. 色彩優化

    1. setDither(bolean dither) 設置圖像抖動

      抖動是指把圖像從較高色彩深度(可用的顏色數)向較低色彩深度的區域繪製時(譬如 32 位的 ARGB_8888 ->16 位色的ARGB_4444 ),在圖像中有意地插入噪點,通過有規律的擾亂圖像來讓圖像對於肉眼更加真實的做法。抖動不可用在純色的繪製中。在實際的場景中,抖動更多的作用是在圖像降低色彩深度繪製時,避免出現大片的色帶與色塊。

      img
    2. setFilterBitmap(boolean filter) 設置是否使用雙線性過濾來繪製 Bitmap

      圖像在方法繪製的時候,默認使用的是最近鄰插值過濾,這種算法簡單,但會出現馬賽克效應;而如果開啓了雙線性過濾,就可以讓結果圖像顯得更加平滑

      img

      最近鄰插值及雙線性插值

  5. setPathEffect(PathEffect effect) 設置圖像輪廓效果

    Andorid 有6種PathEffect

    • CornerPathEffect 把所有拐角都變爲圓角

      img
    • DiscretePathEffect 把線條進行隨機的偏離

      具體的顯示方式是:把繪製改爲使用定長的線段來拼接,並且在拼接的時候對路徑進項隨機偏離

      構造方法 DiscretePathEffect(float segmentLength, float deviation),其中segmentLength 是用來拼接的每個線段的長度,deviation是偏離量

      img
    • DashPathEffect 使用虛線來繪製線條

      構造方法 DashPathEffect(float[] intervals, float phase) ,第一個參數intervals是一個數組,指定了虛線的格式:數組中的元素必須爲大於0的偶數,按照[劃線長度、空白長度、劃線長度、空白長度…]的順序排列,第二個參數phase是虛線的便宜量

      img
    • PathDashPathEffect 使用路徑來繪製虛線線條

      構造方法 PathDashPathEffect(Path shape, float advance, float phase, PathDashPathEffect.Style style) ,其中第一參數shape是用來繪製的Pathadvance是兩個相鄰的shape段起點之間的間隔;phase和上面一樣是虛線的偏移;最後一個參數style是來用來指定拐彎改變時shape的轉換方式。style的類型爲PathDashPathEffect.Style,是一個枚舉,有三個值:

      • TRANSLATE:位移
      • ROTATE:旋轉
      • MORPH:變體
      img
    • SumPathEffect 組合兩種PathEffect分別對目標同時進行繪製

      img
    • ComposePathEffect 組合兩個PathEffect,先使用一種PathEffect,然後在對結果使用另一種PathEffect進行改變

    構造方法 ComposePathEffect(PathEffect outerpe, PathEffect innerpe) ,其中 innerpe 是先應用的, outerpe 是後應用的。

    注意: PathEffect 在有些情況下不支持硬件加速,需要關閉硬件加速才能正常使用:

    Canvas.drawLine()Canvas.drawLines() 方法畫直線時,setPathEffect() 是不支持硬件加速的;

    PathDashPathEffect 對硬件加速的支持也有問題,所以當使用 PathDashPathEffect 的時候,最好也把硬件加速關了。

  6. setShadowLayer(float radius, float dx, float dy, int shadowColor)

    在之後繪製的內容下面加一層陰影,方法的參數裏,radius是陰影的模糊範圍;dx dy是陰影的偏移量;shadowColor是陰影的顏色

    如果要清楚陰影層,使用cleanShadowLayer

    注意:

    • 在硬件加速時,setShadowLayer() 只支持文字的繪製,文字之外的繪製必須關閉硬件加速才能正常繪製陰影。
    • 如果 shadowColor 是半透明的,陰影的透明度就是 shadowColor 自己的透明度;而如果 shadowColor 是不透明的,陰影的透明度就是paint的透明度
  7. setMaskFilter(MaskFilter maskfilter)

    爲之後的繪製的設置MashFilter。它與上面的陰影相反,他是繪製在繪製層上方的附加效果(遮罩),類似於背景和前景

    MaskFilter 有兩種

    • BlurMaskFilter 模糊效果的MaskFilter

      構造方法 BlurMaskFilter(float radius, BlurMaskFilter.Blur style),其中,radius參數是模糊的範圍,style四模糊的類型。一種有四種:

      • NORMAL:內外都模糊繪製

      • SOLID:內部正常繪製,外部模糊

      • INNER:內部模糊,外部不繪製

      • OUTER:內部不繪製,外部模糊

        img
    • EmbossMashFilter

      浮雕效果的 MaskFilter

      構造方法 EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius) ,其中direction 是一個 3 個元素的數組,指定了光源的方向; ambient 是環境光的強度,數值範圍是 0 到 1; specular 是炫光的係數; blurRadius 是應用光線的範圍

      img
  8. 獲取繪製的Path

    根據 paint 的設置,計算出繪製 Path 或文字時的 實際 Path。所謂「實際 Path ,指的就是 drawPath() 的繪製內容的輪廓,要算上線條寬度和設置的 PathEffect

    • getFillPath(Path src, Path dst)

      src 是原 Path ,而 dst 就是實際 Path 的保存位置。 getFillPath(src, dst) 會計算出實際 Path,然後把結果保存在 dst 裏。

      img
    • 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

      img
    3)初始化類

    這一類方法專門用來初始化Paint對象,或者批量設置Paint的多個屬性的方法

    1. reset()

      重置Paint的所有屬性爲默認值,相當於一個重新new一個,不過耗性能低

    2. set(Paint src)

      src的所有屬性全部複製過來。

    3. 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繪製文字的方式
  1. drawText(String text, float x, float y, Paint paint)

    text 是文字內容,xy 是文字的座標。這個座標並不是文字的左上角,而是一個與左下角比較接近的位置。

    drawText() 參數中的 y ,指的是文字的**基線( baseline )** 的位置。

    x 點並不是文字左邊的位置,而是比它的左邊再往左一點點。是因爲絕大多數的字符,它們的寬度都是要略微大於實際顯示的寬度的。字符的左右兩邊會留出一部分空隙,用於文字之間的間隔,以及文字和邊框的間隔。而往左的那一點點就是文字的間隔

    img
  2. drawTextRun()

    這個方法對中國人沒用,忽略

  3. drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)

    沿着一條 Path 來繪製文字。

    hOffsetvOffset。它們是文字相對於 Path 的水平偏移量和豎直偏移量,利用它們可以調整文字的位置

    img

    注意:drawTextOnPath() 使用的 Path ,拐彎處全用圓角,別用尖角。否則會像上面一樣很難看

  4. 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對文字繪製的輔助
  1. 設置顯示效果類

    • 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 CETNERRIGHT。默認值爲 LEFT

    • setTextLocale(Locale locale) 設置繪製所使用的 Locale

    • setHinting(int mode) 設置是否啓用字體的 hinting (字體微調)

      Android 設備大多數都是是用的矢量字體。在字號較大的時候,矢量字體也能夠保持字體的圓潤。但是當文字的尺寸過小(比如高度小於 16 像素),有些文字會由於失去過多細節而變得不太好看。 hinting 技術就是爲了解決這種問題的:通過向字體中加入 hinting 信息,讓矢量字體在尺寸過小的時候得到針對性的修正,從而提高顯示效果。

    • setSubpixelText(boolean subpixelText) 是否開啓次像素級的抗鋸齒

  2. 測量文字尺寸類

    1. float getFontSpacing()

      獲取推薦的行距。

      獲取推薦的兩行文字的baseline 的距離。這個值是系統根據文字的字體和字號自動計算的。它的作用是當你要手動繪製多行文字的時候,可以在換行draw的時候確認下一行文字的y座標

    2. FontMetircs getFontMetrics()

      獲取 PaintFontMetrics

      FontMetrics 提供了幾個文字排版方面的數值:ascent,descent,top,bottom,leading.
      在這裏插入圖片描述
      ascentdescent是上圖中綠色和橙色的線,它們限制普通字符的的頂部和底部範圍。在Android中,ascent的值是圖中綠色線和baseline的相對位移,它的值爲負(y座標向下爲正);descent的值是圖中橙色和baseline的相對位移,它的值爲正

      top/bottom的作用是限制所有字形的頂部和底部範圍。除了普通字符,有些字形的顯示範圍會超過ascentdescent,但是包括這些特殊字符都不會超過topbottom。同上類似,top的值是藍線相對baseline的位移,值爲負;bottom是紅線相對baseline的值,值爲正

      leading指的是行的額外間距,即在相鄰的兩行中上一行的bottom和下一行的top的距離

      leading的本意是行距,即連個相鄰行baseline中間的距離,但是在很多非專業領域,leading被用來指代航的額外間距,Android中就是這樣。

      FontMetrics 提供的就是 Paint 根據當前字體和字號,得出的這些值的推薦值。它把這些值以變量的形式存儲;

      ascentdescent 這兩個值還可以通過 Paint.ascent()Paint.descent() 來快捷獲取

    3. getTextBounds(String text, int start, int end, Rect bounds)

      獲取文字的顯示範圍

      text 是要測量的文字,startend 分別是文字的起始和結束位置,bounds 是存儲文字顯示範圍的對象,方法在測算完成之後會把結果寫進 `bounds

      該方法測量的是文字的顯示範圍

      a14be0c0279959d02d8b5c86926a3293

    4. float measureText(String text)

      測量文字的寬度並返回

      該方法測量的是文字繪製時所佔用的寬度,一個文字在界面中,往往需要佔用比他的實際顯示寬度更多一點的寬度。

    5. getTextWidths(String text, float[] widths)

      獲取字符串中每個字符的寬度,並把結果填入widths

    6. int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)

      在給出最大寬度的前提下測量文字寬度,如果文字的寬度超出限制,則在臨近超限的位置截斷文字。

      其返回值是截取的文字個數(沒有超限時,是文字總個數)。

      方法中,text 是要測量的文字;measureForwards 表示文字的測量方向,true 表示由左往右測量;maxWidth 是給出的寬度上限;measuredWidth 是用於接受數據,而不是用於提供數據的:方法測量完成後會把截取的文字寬度(如果寬度沒有超限,則爲文字總寬度)賦值給 measuredWidth[0]

      這個方法可以用於多行文字的折行計算

    7. 光標相關

      • 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)

        檢查指定的字符串中是否是一個單獨的字形。
        img

四、Canvas 對繪製的輔助

  1. 範圍裁切

    • clipRect()

      將畫布裁切成一個矩形範圍,後面的繪製都在這個範圍內,超出範圍的部分將會被裁掉

    • clipPath

      同上,只不過形狀不是矩形了,而是一個Path

  2. 幾何變換

    • 使用Canvas來做常見的二維變幻

      • Canvas.translate(float dx, float dy) 平移

        將畫布分別沿着x軸平移dxy軸平移 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) 錯切

        參數裏的 sxsy 是 x 方向和 y 方向的錯切係數

    • 使用Matrix來做變換

      • 使用Matrix來做常見變換

        一般步驟爲:

        1. 創建 Matrix 對象;
        2. 調用 Matrixpre/postTranslate/Rotate/Scale/Skew() 方法來設置幾何變換;
        3. 使用 Canvas.setMatrix(matrix)Canvas.concat(matrix) 來把幾何變換應用到 Canvas

        Matrix 應用到 Canvas 有兩個方法:

        1. Canvas.setMatrix(matrix):用Matrix直接替換Canvas當前的變換矩陣(hencoder 說不同的手機系統中 setMatrix(matrix) 的行爲可能不一致,建議用下面的)。
        2. Canvas.concat(matrix):用Canavas當前的變換矩陣和Matrix相乘,及將Canvas當前的變換疊加上Matrix的變換
      • 使用Matrix來做自定義變換

        Matrix 的自定義變換使用的是setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount)方法

        setPolyToPoly() 的作用是通過多點映射的方式來直接設置變換。

        多點影射的意思就是把指定的點移動到給出的位置,從而發生形變

        srcdst 是源點集合目標點集;srcIndexdstIndex 是第一個點的偏移;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();
        
  3. 使用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 像素

五、繪製順序

  1. 寫在 super.onDraw()的下面

    由於繪製代碼會在原有內容繪製結束之後才執行,所以繪製內容就會蓋住控件原來的內容。

    常見的情況:爲控件增加點綴性內容;比如,在 Debug 模式下繪製出 ImageView 的圖像尺寸信息

  2. 寫在 super.onDraw() 的上面

    由於繪製代碼會執行在原有內容的繪製之前,所以繪製的內容會被控件的原內容蓋住

    一般可以通過在文字的下層繪製純色矩形來作爲「強調色」

  3. dispatchDraw():繪製子 View 的方法

    當我們繼承一個ViewGroup類型的View時,在onDraw裏繪製了內容會正常顯示(前提是做了setWillNotDraw(false)等會讓ViewGroupOndraw方法的設置)

    但是當我們添加自view後,我們的繪製信息會消失,即使我們的繪製內容寫在super.onDraw()的下面。

    這是因爲在繪製過程中,每一個 ViewGroup會先調用自己的 onDraw() 來繪製完自己的主體之後再去繪製它的子 View。所以在onDraw這個方法裏我們永遠無法讓我們的繪製內容顯示在子View上方

    上面說的繪製子View的方法叫做 dispatchDraw() ,我們只要重寫這個方法,並把內容寫在super.dispatchDraw() 的下面即可

  4. 繪製過程簡述

    一個完整的繪製過程會依次繪製以下幾個內容:

    1. 背景
    2. 主體(onDraw()
    3. 子 View(dispatchDraw()
    4. 滑動邊緣漸變和滑動條
    5. 前景
    img
  5. onDrawForeground()

    這個方法是 API 23 才引入的,所以在重寫這個方法的時候要確認你的 minSdk 達到了 23,不然低版本的手機裝上你的軟件會沒有效果

    如果你把繪製代碼寫在了 super.onDrawForeground() 的下面,繪製代碼會在滑動邊緣漸變、滑動條和前景之後被執行,那麼繪製內容將會蓋住滑動邊緣漸變、滑動條和前景。

    如果你把繪製代碼寫在了 super.onDrawForeground() 的上面,繪製內容就會在 dispatchDraw()super.onDrawForeground() 之間執行,那麼繪製內容會蓋住子 View,但被滑動邊緣漸變、滑動條以及前景蓋住:

    無法在滑動邊緣漸變、滑動條前景之間插入繪製代碼

  6. draw() 總調度方法

    draw() 是繪製過程的總調度方法。一個 View 的整個繪製過程都發生在 draw() 方法裏。背景、主體、子 View 、滑動相關以及前景的繪製,它們其實都是在 draw() 方法裏的。

    // View.java 的 draw() 方法的簡化版大致結構(是大致結構,不是源碼哦):
    
    public void draw(Canvas canvas) {
       ...
    
       drawBackground(Canvas); // 繪製背景(不能重寫)
       onDraw(Canvas); // 繪製主體
       dispatchDraw(Canvas); // 繪製子 View
       onDrawForeground(Canvas); // 繪製滑動相關和前景
    
       ...
    }
    
    img

    如果把繪製代碼寫在 super.draw() 的下面,那麼這段代碼會在其他所有繪製完成之後再執行,也就是說,它的繪製內容會蓋住其他的所有繪製內容。

    如果把繪製代碼寫在 super.draw() 的上面,那麼這段代碼會在其他所有繪製之前被執行,所以這部分繪製內容會被其他所有的內容蓋住,包括背景。是的,背景也會蓋住它。

  1. ViewGroup 的子類中重寫除 dispatchDraw() 以外的繪製方法時,可能需要調用 setWillNotDraw(false)
  2. 在重寫的方法有多個選擇時,優先選擇 onDraw()。因爲 Android 有相關的優化,可以在不需要重繪的時候自動跳過 onDraw() 的重複執行
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章