0x00 前言
在Android開發中,有時產品需求會要我們實現一個復雜的控件,如果控件不是常用的類型,這時候我們不得不寫一個自定義的控件來實現。
自定義一個控件,目前一般通過繼承View或者SurfaceView來實現。View和SurfaceView的區別在于,View必須在UI的主線程中更新畫面,而SurfaceView是在一個新起的單獨線程中可以重新繪制畫面。View使用CPU來處理,用戶需要主動調用invalidate()
來改變控件的狀態。SurfaceView單獨開啟了一個線程,理論上效率更高,但是實際使用時在魅族和小米的手機上經常會變得很卡頓,紅明哥說是因為SurfaceView谷歌設計的是雙緩沖,但是國內部分手機廠商將它修改為三緩沖,所以圖形緩存變得未知。不管是使用哪種方式,實現方式都得通過Android Canvas來繪制。
0x01 Paint
我們知道,有畫布和畫筆才能畫畫。畫筆決定了畫的東西的粗細和顏色屬性,這就是Paint的作用。Paint的常用方法有:
setARGB(int a, int r, int g, int b) // 設置 Paint對象顏色,參數一為alpha透明值
setAlpha(int a) // 設置alpha不透明度,范圍為0~255
setAntiAlias(boolean alpha) // 是否抗鋸齒
setColor(int color) // 設置顏色,這里Android內部定義的有Color類包含了一些常見顏色定義
setTextScaleX(float scaleX) // 設置文本縮放倍數,1.0f為原始
setTextSize(float textSize) // 設置字體大小
setUnderlineText(booleanunderlineText) // 設置下劃線
Paint還可以設置Shader類用于渲染圖像,Shader有幾個直接子類:
BitmapShader : 主要用來渲染圖像
LinearGradient : 用來進行線性渲染
RadialGradient : 用來進行環形渲染
SweepGradient : 掃描漸變 圍繞一個中心點掃描漸變就像電影里那種雷達掃描,用來梯度渲染
ComposeShader : 組合渲染 可以和其他幾個子類組合起來使用
Shader構造有時要指定TileMode(一共有三種)
exempli gratia: public BitmapShader (Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY);
CLAMP :如果渲染器超出原始邊界范圍,會復制范圍內邊緣染色。
REPEAT :橫向和縱向的重復渲染器圖片,平鋪。
MIRROR :橫向和縱向的重復渲染器圖片,這個和REPEAT重復方式不一樣,他是以鏡像方式平鋪。
ComposeShader構造時要指定PorterDuff.Mode,表示兩種渲染疊加的效果
android.graphics.PorterDuff.Mode.SRC : 只繪制源圖像
android.graphics.PorterDuff.Mode.DST : 只繪制目標圖像
android.graphics.PorterDuff.Mode.DST_OVER : 在源圖像的頂部繪制目標圖像
android.graphics.PorterDuff.Mode.DST_IN : 只在源圖像和目標圖像相交的地方繪制目標圖像
android.graphics.PorterDuff.Mode.DST_OUT : 只在源圖像和目標圖像不相交的地方繪制目標圖像
android.graphics.PorterDuff.Mode.DST_ATOP : 在源圖像和目標圖像相交的地方繪制目標圖像,在不相交的地方繪制源圖像
android.graphics.PorterDuff.Mode.SRC_OVER : 在目標圖像的頂部繪制源圖像
android.graphics.PorterDuff.Mode.SRC_IN : 只在源圖像和目標圖像相交的地方繪制源圖像
android.graphics.PorterDuff.Mode.SRC_OUT : 只在源圖像和目標圖像不相交的地方繪制源圖像
android.graphics.PorterDuff.Mode.SRC_ATOP : 在源圖像和目標圖像相交的地方繪制源圖像,在不相交的地方繪制目標圖像
android.graphics.PorterDuff.Mode.XOR : 在源圖像和目標圖像重疊之外的任何地方繪制他們,而在不重疊的地方不繪制任何內容
android.graphics.PorterDuff.Mode.LIGHTEN : 獲得每個位置上兩幅圖像中最亮的像素并顯示
android.graphics.PorterDuff.Mode.DARKEN : 獲得每個位置上兩幅圖像中最暗的像素并顯示
android.graphics.PorterDuff.Mode.MULTIPLY : 將每個位置的兩個像素相乘,除以255,然后使用該值創建一個新的像素進行顯示。結果顏色=頂部顏色*底部顏色/255
android.graphics.PorterDuff.Mode.SCREEN : 反轉每個顏色,執行相同的操作(將他們相乘并除以255),然后再次反轉。結果顏色=255-(((255-頂部顏色)*(255-底部顏色))/255)
0x02 Path
在View中我們可以先定義好幾個點的位置,然后使用Path類將它們連成路徑。連接點有lineTo
和quadTo
兩種方式。lineTo(float x, float y)
是將兩點連成一條直線,quadTo(float x1, float y1, float x2, float y2)
則是使用貝塞爾曲線原理連接,使得線成為平滑的曲線。
Android還為路徑繪制提供了PathEffect來定義繪制效果,PathEffect包含如下子類:
- CornerPathEffect:將Path的各個連接線段之間的夾角用一種更平滑的方式連接,類似于圓弧與切線的效果。
CornerPathEffect(float radius)
- DashPathEffect:將Path的線段虛線化。
DashPathEffect(float[] intervals, float offset)
intervals為虛線的ON和OFF數組 - DiscretePathEffect:打散Path的線段,使得在原來路徑的基礎上發生打散效果。
DiscretePathEffect(float segmentLength,float deviation)
segmentLength指定最大的段長,deviation指定偏離量。 - PathDashPathEffect:使用Path圖形來填充當前的路徑。
PathDashPathEffect (Path shape, float advance, float phase,PathDashPathEffect.Stylestyle)
shape則是指填充圖形,advance指每個圖形間的間距,phase為繪制時的偏移量,style為該類自由的枚舉值,有三種情況:Style.ROTATE、Style.MORPH和Style.TRANSLATE。其中ROTATE的情況下,線段連接處的圖形轉換以旋轉到與下一段移動方向相一致的角度進行轉轉,MORPH時圖形會以發生拉伸或壓縮等變形的情況與下一段相連接,TRANSLATE時,圖形會以位置平移的方式與下一段相連接 - ComposePathEffect:組合效果,這個類需要兩個PathEffect參數來構造一個實例。
ComposePathEffect (PathEffect outerpe,PathEffect innerpe)
,先將innerpe表現出來,然后再在innerpe的基礎上去增加outerpe的效果 - SumPathEffect:疊加效果。
SumPathEffect(PathEffect first,PathEffect second)
,與ComposePathEffect不同的是,在表現時,會分別對兩個參數的效果各自獨立進行表現,然后將兩個效果簡單的重疊在一起顯示出來
0x03 裁剪
Canvas提供了ClipPath, ClipRect, ClipRegion 等方法來裁剪,通過Path, Rect ,Region 的不同組合,Android幾乎可以支持任意現狀的裁剪區域。
Path可以為開放或是閉合曲線。Rect提供了定義矩形的簡潔方法。Region則支持通過區域的“加”,“減”,“并”,“異或”等邏輯運算由多個Region組合而成。Region.Op定義了Region支持的區域間運算種類。
這部分我自己用的少,但是在整理Canvas部分時還是加入進來,放一個比較詳細的博文地址:Canvas裁剪
0x04 Canvas
有了前面的知識,我們可以真正開始畫畫了!
Canvas 可以畫點、線、圖形、文字等,詳細的方法如下: