目錄
1、前言
2、如何畫圖
一、繪圖座標系
二、視圖座標系
三、小結
3、Canvas的剪刀手API
一、clipPath
二、clipOutPath
三、clipPath
4、實戰
5、寫在最後
java
從今天開始咱們聊一聊 Canvas 的API,由於Canvas的API較多,因此咱們分爲幾回分享,首先分享的是裁剪類型的API使用。話很少說,先上實戰圖。git
老夫的少女心 github
源碼地址文末會給出,瞭解原理才能更好地駕馭。canvas
分享前,咱們先來聊聊,在咱們生活中如何繪製一張以下的圖。 微信
咱們須要兩樣東西來繪製:有了這兩樣,咱們就能在現實的場景中開始繪製了。post
但在 Android 的體系中,咱們所謂的 「筆Paint」 和 「紙Canvas」 都是由App持有的,因此咱們在繪製時就出現一個問題:咱們怎麼「告訴」App,肯定咱們想要繪製圖形的落筆點?固然須要一個座標系來進行交流。動畫
而這個 座標系 即是咱們常常所說的 繪圖座標系。初始狀態下,Canvas的左上角爲原點,以下圖的藍色點所示。此時咱們想畫圖中的紅點,就很是的容易,只須要「告訴」 App 在座標(200,500)處畫一個紅點,這就達到了畫圖的效果了。 因此咱們能夠明確的一點是 咱們全部的畫圖座標都是根據原點進行肯定。 編碼
因此咱們能夠移動原點,達到總體座標點的移動,例如仍是畫剛纔的紅點,咱們能夠先將原點水平移動100,垂直移動400。而後在進行繪製,這時紅點的座標就變爲(100,100),具體以下圖所示。 通過上面的簡單講述,咱們能夠知道,繪圖過程當中, 咱們的繪圖座標永遠是跟隨當前的原點,而畫布的原點能夠進行移動。理論上 Canvas 這張紙是沒有邊界的,可是咱們的手機屏幕是有界的。咱們能夠理解爲咱們透過一個方形的洞(手機屏幕)看一張巨畫(Canvas)。spa
而這裏咱們就又存在一個問題了,由於剛纔的移動,咱們是移動的原點,也就是說咱們的畫布是靜止不動的,只是落筆點一直在變更,這就致使咱們繪製的圖對於用戶來講是看不全的,因此咱們須要進行移動 方形的洞 來查看這幅畫。3d
舉個例子,咱們要查看最開始所說的畫,能夠經過移動 Screen框來查看這幅畫,而這裏又出現了一個座標系,這一座標系則爲 視圖座標系,經過 scrollerTo
和 scrollerBy
進行移動該Screen框,正數則往正半軸,負數則往負半軸。
自定義控件中存在兩個座標系須要明確,用一句話總結以下:
Canvas 中以 clip開頭 的公有方法,用於裁剪畫布的內容。 咱們抽取比較好玩的參數類型爲Path的方法來分享,其他的均可以一一映射進來。
public boolean clipPath(@NonNull Path path) 複製代碼
描述: 只留下 path內 的畫布區域,而處於path範圍以外的則不顯示。
舉個例子: 咱們先準備好一個心形的路徑Path,而後調用 clipPath
從畫布中將此路徑內的區域 「裁剪」 下來,最後爲了咱們觀察,使用drawColor
「染」上酒紅色。
// 第一步:建立 心形路徑 mPath
....省略,具體請移步github
// 第二步:從畫布 canvas 裁剪下心形路徑以內的區域
canvas.clipPath(mPath);
// 第三步:塗酒紅色
canvas.drawColor(mBgColor);
複製代碼
若是想了解如何繪製心形軌跡,請移步小盆友的另外一篇博文:自帶美感的貝塞爾曲線原理與實戰
效果圖
此類型的方法還有如下這幾個,但他們的 裁剪範圍均爲矩形public boolean clipRect(float left, float top, float right, float bottom) public boolean clipRect(int left, int top, int right, int bottom) public boolean clipRect(@NonNull Rect rect) public boolean clipRect(@NonNull RectF rect) 複製代碼
public boolean clipOutPath(@NonNull Path path) 複製代碼
描述: 只留下 path外 的畫布區域,而處於path範圍以內的則不顯示。(與clipPath的做用範圍正好相反)
值得注意的是,該方法只能在API26版本以上調用。 低版本咱們使用下一小節介紹的方法
舉個例子:
咱們先準備好一個心形的路徑Path,而後調用 clipOutPath
從畫布中將此路徑以外的區域 「裁剪」 下來,最後爲了咱們觀察,使用 drawColor
「染」上酒紅色。
// 第一步:建立 心形路徑 mPath
....省略,具體請移步github
// 第二步:從畫布 canvas 裁剪下心形路徑以外的區域
canvas.clipOutPath(mPath);
// 第三步:塗酒紅色
canvas.drawColor(mBgColor);
複製代碼
效果圖
此類型的方法還有如下這幾個,但他們的裁剪範圍均爲矩形
public boolean clipOutRect(float left, float top, float right, float bottom) public boolean clipOutRect(int left, int top, int right, int bottom) public boolean clipOutRect(@NonNull Rect rect) public boolean clipOutRect(@NonNull RectF rect) 複製代碼
public boolean clipPath(@NonNull Path path, @NonNull Region.Op op) 複製代碼
描述: 在畫布上進行使用 path 路徑進行操做,至於其做用由 op 決定。
描述比較抽象,咱們經過例子來體會。但在上例子前,咱們須要先了解下 Region.Op
這個枚舉類型,具體內容代碼以下
public enum Op {
// A: 爲咱們先裁剪的路徑
// B: 爲咱們後裁剪的路徑
// A形狀中不一樣於B的部分顯示出來
DIFFERENCE(0),
// A和B交集的形狀
INTERSECT(1),
// A和B的全集
UNION(2),
// A和B的全集形狀,去除交集形狀以後的部分
XOR(3),
// B形狀中不一樣於A的部分顯示出來
REVERSE_DIFFERENCE(4),
// 只顯示B的形狀
REPLACE(5);
// ...省略不相關代碼
}
複製代碼
經過源碼能夠知道共有六種類型。值得一提的有如下兩點:
1)clipOutPath
方法中使用的類型就是 DIFFERENCE
,換而言之,咱們可使用如下代碼代替,解決在API26 如下沒法使用的問題clipOutPath
方法的問題
clipPath(mPath, Region.Op.DIFFERENCE)
複製代碼
2)clipPath
方法中使用的類型就是 INTERSECT
,換而言之,咱們可使用如下代碼代替
clipPath(mPath, Region.Op.INTERSECT)
複製代碼
舉些例子:
接下來咱們一個個講解這六種類型,兩次裁剪比較能體現出 Region.Op
參數的做用,因此咱們接下來的例子須要使用兩個路徑:
一、心形路徑 (下列例子中的 A)
二、圓路徑(下列例子中的 B)(1)DIFFERENCE
描述: A形狀中不一樣於B的部分顯示出來
效果圖: 紅色即爲最終裁剪留下區域
(2)INTERSECT
描述: A和B交集的形狀
效果圖: 紅色即爲最終裁剪留下區域
(3)UNION
描述: A和B的全集
效果圖: 紅色即爲最終裁剪留下區域
(4)XOR
描述: A和B的全集形狀,去除交集形狀以後的部分
效果圖: 紅色即爲最終裁剪留下區域
(5)REVERSE_DIFFERENCE
描述: B形狀中不一樣於A的部分顯示出來
效果圖: 紅色即爲最終裁剪留下區域
(6)REPLACE
描述: 只顯示B的形狀
效果圖: 紅色即爲最終裁剪留下區域
此類型的方法還有如下這幾個,但他們的 裁剪範圍均爲矩形public boolean clipRect(float left, float top, float right, float bottom, @NonNull Region.Op op) public boolean clipRect(@NonNull Rect rect, @NonNull Region.Op op) public boolean clipRect(@NonNull RectF rect, @NonNull Region.Op op) 複製代碼
上一小節咱們已經瞭解了這幾些API的做用就是裁剪,這小節咱們就把它使用起來。
效果圖
Github入口:傳送門
編碼思路
咱們藉助下面這張小盆友手繪的思路圖(看看能不能達到一圖勝千言😄)
這裏爲了視覺效果易於講解,紅色即爲咱們demo中的粉色,藍色即爲咱們demo中青色,橘色就是最終的漸變色
第一步(綠色心形部分): 咱們先在畫布裁剪下心形區域,這就奠基了最後呈現給用戶所看到的畫布區域爲一個「心」。
第二步(紅色部分): 咱們用將畫布染成紅色,而後在畫布的中心用藍色寫上 「猛猛的小盆友」 ,最後使用圖中紅色框(即上邊是橫線,下邊是用貝塞爾曲線繪製的Path紅色區域)將畫布的上半部分裁剪下來,放置最終呈現的畫布中。
第三步(藍色部分): 與第二步正好相反,咱們用將畫布染成藍色,而後在畫布的中心用紅色寫上 「猛猛的小盆友」 ,最後使用圖中藍色框(即上邊是用貝塞爾曲線繪製,下邊是橫線的Path懶色區域)將畫布的下半部分裁剪下來,放置最終呈現的畫布中。
第四步: 通過前三步,咱們的圖案已經造成了右邊的圖像。咱們開啓動畫,其實就是控制中間貝塞爾曲線的y軸座標,令其從底部上升至頂部,則呈現出了灌滿心形的動畫效果,因此咱們能夠經過讓畫布偏移必定的值達到該效果,同時讓貝塞爾曲線作水平的運動,有一種波動感。
核心代碼
// 第一步
canvas.clipPath(mHeartPath);
// ======== 第二步start ==============
canvas.save();
// 第四步
canvas.translate(-mCurOffset, mCurPos);
canvas.clipPath(mTopPath);
mPaint.setColor(mTopBgColor);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawPath(mTopPath, mPaint);
canvas.translate(mCurOffset, -mCurPos);
drawText(canvas, mBottomBgColor);
canvas.restore();
// ======== 第二步end ==============
// ======== 第三步start ==============
canvas.save();
// 第四步
canvas.translate(-mCurOffset, mCurPos);
canvas.clipPath(mBottomPath);
mPaint.setColor(ContextCompat.getColor(getContext(), R.color.canvas_light_blue_color));
mPaint.setStyle(Paint.Style.FILL);
canvas.drawPath(mBottomPath, mPaint);
canvas.translate(mCurOffset, -mCurPos);
drawText(canvas, mTopBgColor);
canvas.restore();
// ======== 第三步end ==============
複製代碼
Canvas 中的API挺多,涉及的小知識也比較零碎,原本想在一篇文章中分享完全部的API,但寫的過於寬泛,糾結再三,小盆友最終仍是選擇迴歸初心,按照本身的理解分享好每一個知識點,將canvas的分享拆分爲幾回。若是以爲文章對你有所啓發,請給我個贊吧,若是發現有那些欠妥的地方,請留言區與我討論,咱們共同進步。
高級UI系列的Github地址:請進入傳送門,若是喜歡的話給我一個star吧😄
歡迎加我微信,咱們能夠進行更多更有趣的交流