這期是 HenCoder 自定義繪製的第 1-4 期:Canvas 對繪製的輔助——範圍裁切和幾何變換。java
以前的內容在這裏:
HenCoder Android 開發進階 自定義 View 1-1 繪製基礎
HenCoder Android 開發進階 自定義 View 1-2 Paint 詳解
HenCoder Android 開發進階 自定義 View 1-3 文字的繪製android
若是你沒據說過 HenCoder,能夠先看看這個:
HenCoder:給高級 Android 工程師的進階手冊git
一圖勝千言,一視頻勝千圖,走你:github
若是你是在手機上看的,能夠點這裏去 B 站看原視頻。canvas
範圍裁切有兩個方法: clipRect()
和 clipPath()
。裁切方法以後的繪製代碼,都會被限制在裁切範圍內。bash
使用很簡單,直接應用:微信
canvas.clipRect(left, top, right, bottom);
canvas.drawBitmap(bitmap, x, y, paint);複製代碼
記得要加上 Canvas.save()
和 Canvas.restore()
來及時恢復繪製範圍,因此完整代碼是這樣的:app
canvas.save();
canvas.clipRect(left, top, right, bottom);
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();複製代碼
其實和 clipRect() 用法徹底同樣,只是把參數換成了 Path
,因此能裁切的形狀更多一些:ide
canvas.save();
canvas.clipPath(path1);
canvas.drawBitmap(bitmap, point1.x, point1.y, paint);
canvas.restore();
canvas.save();
canvas.clipPath(path2);
canvas.drawBitmap(bitmap, point2.x, point2.y, paint);
canvas.restore();複製代碼
幾何變換的使用大概分爲三類:oop
Canvas
來作常見的二維變換;Matrix
來作常見和不常見的二維變換;Camera
來作三維變換。參數裏的 dx
和 dy
表示橫向和縱向的位移。
canvas.save();
canvas.translate(200, 0);
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();複製代碼
好吧這個從截圖並不能看出什麼,那你就用心去感覺吧
參數裏的 degrees
是旋轉角度,單位是度(也就是一週有 360° 的那個單位),方向是順時針爲正向; px
和 py
是軸心的位置。
canvas.save();
canvas.rotate(45, centerX, centerY);
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();複製代碼
參數裏的 sx
sy
是橫向和縱向的放縮倍數; px
py
是放縮的軸心。
canvas.save();
canvas.scale(1.3f, 1.3f, x + bitmapWidth / 2, y + bitmapHeight / 2);
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();複製代碼
參數裏的 sx
和 sy
是 x 方向和 y 方向的錯切係數。
canvas.save();
canvas.skew(0, 0.5f);
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();複製代碼
Matrix
作常見變換的方式:
Matrix
對象;Matrix
的 pre/postTranslate/Rotate/Scale/Skew()
方法來設置幾何變換;Canvas.setMatrix(matrix)
或 Canvas.concat(matrix)
來把幾何變換應用到 Canvas
。Matrix matrix = new Matrix();
...
matrix.reset();
matrix.postTranslate();
matrix.postRotate();
canvas.save();
canvas.concat(matrix);
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();複製代碼
效果就不放圖了,和 Canvas
是同樣的。
把 Matrix
應用到 Canvas
有兩個方法: Canvas.setMatrix(matrix)
和 Canvas.concat(matrix)
。
Canvas.setMatrix(matrix)
:用 Matrix
直接替換 Canvas
當前的變換矩陣,即拋棄 Canvas
當前的變換,改用 Matrix
的變換(注:根據我在一些評論以及微信公衆號中收到的反饋,不一樣的手機系統中 setMatrix(matrix) 的行爲可能不一致,因此仍是儘可能用 concat(matrix) 吧);Canvas.concat(matrix)
:用 Canvas
當前的變換矩陣和 Matrix
相乘,即基於 Canvas
當前的變換,疊加上 Matrix
中的變換。Matrix
的自定義變換使用的是 setPolyToPoly()
方法。
poly
就是「多」的意思。setPolyToPoly()
的做用是經過多點的映射的方式來直接設置變換。「多點映射」的意思就是把指定的點移動到給出的位置,從而發生形變。例如:(0, 0) -> (100, 100) 表示把 (0, 0) 位置的像素移動到 (100, 100) 的位置,這個是單點的映射,單點映射能夠實現平移。而多點的映射,就可讓繪製內容任意地扭曲。
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();複製代碼
參數裏,src
和 dst
是源點集合目標點集;srcIndex
和 dstIndex
是第一個點的偏移;pointCount
是採集的點的個數(個數不能大於 4,由於大於 4 個點就沒法計算變換了)。
Camera
的三維變換有三類:旋轉、平移、移動相機。
Camera.rotate*()
一共有四個方法: rotateX(deg)
rotateY(deg)
rotateZ(deg)
rotate(x, y, z)
。這四個方法的區別不用我說了吧?
canvas.save();
camera.rotateX(30); // 旋轉 Camera 的三維空間
camera.applyToCanvas(canvas); // 把旋轉投影到 Canvas
canvas.drawBitmap(bitmap, point1.x, point1.y, paint);
canvas.restore();複製代碼
另外,Camera
和 Canvas
同樣也須要保存和恢復狀態才能正常繪製,否則在界面刷新以後繪製就會出現問題。因此上面這張圖完整的代碼應該是這樣的:
canvas.save();
camera.save(); // 保存 Camera 的狀態
camera.rotateX(30); // 旋轉 Camera 的三維空間
camera.applyToCanvas(canvas); // 把旋轉投影到 Canvas
camera.restore(); // 恢復 Camera 的狀態
canvas.drawBitmap(bitmap, point1.x, point1.y, paint);
canvas.restore();複製代碼
若是你須要圖形左右對稱,須要配合上 Canvas.translate()
,在三維旋轉以前把繪製內容的中心點移動到原點,即旋轉的軸心,而後在三維旋轉後再把投影移動回來:
canvas.save();
camera.save(); // 保存 Camera 的狀態
camera.rotateX(30); // 旋轉 Camera 的三維空間
canvas.translate(centerX, centerY); // 旋轉以後把投影移動回來
camera.applyToCanvas(canvas); // 把旋轉投影到 Canvas
canvas.translate(-centerX, -centerY); // 旋轉以前把繪製內容移動到軸心(原點)
camera.restore(); // 恢復 Camera 的狀態
canvas.drawBitmap(bitmap, point1.x, point1.y, paint);
canvas.restore();複製代碼
Canvas
的幾何變換順序是反的,因此要把移動到中心的代碼寫在下面,把從中心移動回來的代碼寫在上面。
它的使用方式和 Camera.rotate*()
相同,並且我在項目中沒有用過它,因此就不貼代碼和效果圖了。
注意!這個方法有點奇葩,它的參數的單位不是像素,而是 inch,英寸。
我 TM 的真沒逗你,我也沒有胡說八道,它的單位就。是。英。寸。
這種設計源自 Android 底層的圖像引擎 Skia 。在 Skia 中,Camera 的位置單位是英寸,英寸和像素的換算單位在 Skia 中被寫死爲了 72 像素,而 Android 中把這個換算單位照搬了過來。是的,它。寫。死。了。
吐槽到此爲止,俗話說看透不說透,仍是好朋友。
在 Camera
中,相機的默認位置是 (0, 0, -8)(英寸)。8 x 72 = 576,因此它的默認位置是 (0, 0, -576)(像素)。
若是繪製的內容過大,當它翻轉起來的時候,就有可能出現圖像投影過大的「糊臉」效果。並且因爲換算單位被寫死成了 72 像素,而不是和設備 dpi 相關的,因此在像素越大的手機上,這種「糊臉」效果會越明顯。
而使用 setLocation()
方法來把相機日後移動,就能夠修復這種問題。
camera.setLocation(0, 0, newZ);複製代碼
Camera.setLocation(x, y, z)
的 x
和 y
參數通常不會改變,直接填 0 就好。
好了,上面這些就是本期的內容:範圍裁切和幾何變換。
爲了不轉頭就忘,強烈建議你趁熱打鐵,作一下這個練習項目:HenCoderPracticeDraw4
到這期爲止,全部繪製的「術」就講完了,下期講的是「道」:繪製順序。
此次因爲某些緣由,沒有依慣例邀請內測讀者,但我須要特別感謝幾我的:
若是你看完以爲有收穫,把文章轉發到你的微博、微信羣、朋友圈、公衆號,讓其餘須要的人也看到吧。