HenCoder Android 開發進階:自定義 View 1-4 Canvas 對繪製的輔助

這期是 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

1 範圍裁切

範圍裁切有兩個方法: clipRect()clipPath()。裁切方法以後的繪製代碼,都會被限制在裁切範圍內。bash

1.1 clipRect()

使用很簡單,直接應用:微信

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();複製代碼

1.2 clipPath()

其實和 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();複製代碼

2 幾何變換

幾何變換的使用大概分爲三類:oop

  1. 使用 Canvas 來作常見的二維變換;
  2. 使用 Matrix 來作常見和不常見的二維變換;
  3. 使用 Camera 來作三維變換。

2.1 使用 Canvas 來作常見的二維變換:

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

參數裏的 dxdy 表示橫向和縱向的位移。

canvas.save();
canvas.translate(200, 0);
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();複製代碼

好吧這個從截圖並不能看出什麼,那你就用心去感覺吧

2.1.2 Canvas.rotate(float degrees, float px, float py) 旋轉

參數裏的 degrees 是旋轉角度,單位是度(也就是一週有 360° 的那個單位),方向是順時針爲正向; pxpy 是軸心的位置。

canvas.save();
canvas.rotate(45, centerX, centerY);
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();複製代碼

2.1.3 Canvas.scale(float sx, float sy, float px, float py) 放縮

參數裏的 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();複製代碼

2.1.4 skew(float sx, float sy) 錯切

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

canvas.save();
canvas.skew(0, 0.5f);
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();複製代碼

2.2 使用 Matrix 來作變換

2.2.1 使用 Matrix 來作常見變換

Matrix 作常見變換的方式:

  1. 建立 Matrix 對象;
  2. 調用 Matrixpre/postTranslate/Rotate/Scale/Skew() 方法來設置幾何變換;
  3. 使用 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)

  1. Canvas.setMatrix(matrix):用 Matrix 直接替換 Canvas 當前的變換矩陣,即拋棄 Canvas 當前的變換,改用 Matrix 的變換(注:根據我在一些評論以及微信公衆號中收到的反饋,不一樣的手機系統中 setMatrix(matrix) 的行爲可能不一致,因此仍是儘可能用 concat(matrix) 吧);
  2. Canvas.concat(matrix):用 Canvas 當前的變換矩陣和 Matrix 相乘,即基於 Canvas 當前的變換,疊加上 Matrix 中的變換。

2.2.2 使用 Matrix 來作自定義變換

Matrix 的自定義變換使用的是 setPolyToPoly() 方法。

2.2.2.1 Matrix.setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount) 用點對點映射的方式設置變換

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();複製代碼

參數裏,srcdst 是源點集合目標點集;srcIndexdstIndex 是第一個點的偏移;pointCount 是採集的點的個數(個數不能大於 4,由於大於 4 個點就沒法計算變換了)。

2.3 使用 Camera 來作三維變換

Camera 的三維變換有三類:旋轉、平移、移動相機。

2.3.1 Camera.rotate*() 三維旋轉

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();複製代碼

另外,CameraCanvas 同樣也須要保存和恢復狀態才能正常繪製,否則在界面刷新以後繪製就會出現問題。因此上面這張圖完整的代碼應該是這樣的:

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 的幾何變換順序是反的,因此要把移動到中心的代碼寫在下面,把從中心移動回來的代碼寫在上面。

2.3.2 Camera.translate(float x, float y, float z) 移動

它的使用方式和 Camera.rotate*() 相同,並且我在項目中沒有用過它,因此就不貼代碼和效果圖了。

2.3.3 Camera.setLocation(x, y, z) 設置虛擬相機的位置

注意!這個方法有點奇葩,它的參數的單位不是像素,而是 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)xy 參數通常不會改變,直接填 0 就好。

好了,上面這些就是本期的內容:範圍裁切和幾何變換。

練習項目

爲了不轉頭就忘,強烈建議你趁熱打鐵,作一下這個練習項目:HenCoderPracticeDraw4

下期預告

到這期爲止,全部繪製的「術」就講完了,下期講的是「道」:繪製順序。

特別感謝

此次因爲某些緣由,沒有依慣例邀請內測讀者,但我須要特別感謝幾我的:

  • 特別感謝 GcsSloopLeeThree 在我疲憊不堪的時候和我探討問題、幫我作實驗和尋找答案。
  • 特別感謝 脈脈不得語 在我忙得不可開交的時候幫我解決個人各類大小問題,以及幫我聯繫廣告贊助商(別找了,這期沒上廣告,之後再上)。

以爲贊?

若是你看完以爲有收穫,把文章轉發到你的微博、微信羣、朋友圈、公衆號,讓其餘須要的人也看到吧。

相關文章
相關標籤/搜索