在一年的Android自學中,Canvas一直是我能避且避的類,甚至不惜封裝本身的繪圖庫來替代它。
現在回首,虐我千萬次的Canvas也不過如此,靜下心看看,其實也沒有想象中的那麼糟糕。
就像曾經等級30的我去打點等級40的副本(Canvas)很是吃力,如今等級50的我回來吊打它同樣。
因此朋友,遇到承受不了的困擾,不要太沮喪,去別的地方刷怪升級,一旦境界提高了,遲早能夠"報仇雪恨"
Android技術棧C模塊
,第一篇正式開講:git
那麼Canvas是一個黑匣子裏的白紙,它的特性是能夠添加圖層和平移,旋轉、縮放、斜切等,最重要的就是它的n種drawXXX...
Paint是繪製用的畫筆,它的特性是提供繪製工具與制定畫筆的特殊效果(如筆頭Cap,線接方式Join,六種Effect)
View則是讓黑匣子變成透明的視口,也是咱們最熟悉。那Coder就是在操縱畫筆的在白紙上繪製的人,是最核心的github
提及Canvas對象,貌似不多去new它,更多的是在自定義控件時的Ondraw方法裏回調有canvas對象編程
public class CanvasView extends View {
public CanvasView(Context context) {
this(context, null);
}
public CanvasView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
//TODO init 初始化
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//TODO drawGrid 繪製網格
//TODO drawCoo 繪製座標系
}
}
複製代碼
這裏值得提一下:onDarw中儘可能不要建立對象,由於視圖更新都會走onDarw(),而不斷開闢空間canvas
若是要演示繪製,這二者必不可少,放在analyze包裏
實現效果:給出座標原點後會自動繪製座標系以及網格和數字數組
//成員變量
private Paint mGridPaint;//網格畫筆
private Point mWinSize;//屏幕尺寸
private Point mCoo;//座標系原點
//TODO init 初始化:release:
//準備屏幕尺寸
mWinSize = new Point();
mCoo = new Point(500, 500);
Utils.loadWinSize(getContext(), mWinSize);
mGridPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//TODO drawGrid 繪製網格:release:
HelpDraw.drawGrid(canvas, mWinSize, mGridPaint);
//TODO drawCoo 繪製座標系:release:
HelpDraw.drawCoo(canvas, mCoo, mWinSize, mGridPaint);
複製代碼
/**
* 繪製網格:注意只有用path才能繪製虛線
*
* @param step 小正方形邊長
* @param winSize 屏幕尺寸
*/
public static Path gridPath(int step, Point winSize) {
Path path = new Path();
for (int i = 0; i < winSize.y / step + 1; i++) {
path.moveTo(0, step * i);
path.lineTo(winSize.x, step * i);
}
for (int i = 0; i < winSize.x / step + 1; i++) {
path.moveTo(step * i, 0);
path.lineTo(step * i, winSize.y);
}
return path;
}
複製代碼
/**
* 繪製網格
* @param canvas 畫布
* @param winSize 屏幕尺寸
* @param paint 畫筆
*/
public static void drawGrid(Canvas canvas, Point winSize, Paint paint) {
//初始化網格畫筆
paint.setStrokeWidth(2);
paint.setColor(Color.GRAY);
paint.setStyle(Paint.Style.STROKE);
//設置虛線效果new float[]{可見長度, 不可見長度},偏移值
paint.setPathEffect(new DashPathEffect(new float[]{10, 5}, 0));
canvas.drawPath(HelpPath.gridPath(50, winSize), paint);
}
複製代碼
/**
* 得到屏幕高度
*
* @param ctx 上下文
* @param winSize 屏幕尺寸
*/
public static void loadWinSize(Context ctx, Point winSize) {
WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
if (wm != null) {
wm.getDefaultDisplay().getMetrics(outMetrics);
}
winSize.x = outMetrics.widthPixels;
winSize.y = outMetrics.heightPixels;
}
複製代碼
/**
* 座標系路徑
*
* @param coo 座標點
* @param winSize 屏幕尺寸
* @return 座標系路徑
*/
public static Path cooPath(Point coo, Point winSize) {
Path path = new Path();
//x正半軸線
path.moveTo(coo.x, coo.y);
path.lineTo(winSize.x, coo.y);
//x負半軸線
path.moveTo(coo.x, coo.y);
path.lineTo(coo.x - winSize.x, coo.y);
//y負半軸線
path.moveTo(coo.x, coo.y);
path.lineTo(coo.x, coo.y - winSize.y);
//y負半軸線
path.moveTo(coo.x, coo.y);
path.lineTo(coo.x, winSize.y);
return path;
}
複製代碼
/**
* 繪製座標系
* @param canvas 畫布
* @param coo 座標系原點
* @param winSize 屏幕尺寸
* @param paint 畫筆
*/
public static void drawCoo(Canvas canvas, Point coo, Point winSize, Paint paint) {
//初始化網格畫筆
paint.setStrokeWidth(4);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
//設置虛線效果new float[]{可見長度, 不可見長度},偏移值
paint.setPathEffect(null);
//繪製直線
canvas.drawPath(HelpPath.cooPath(coo, winSize), paint);
//左箭頭
canvas.drawLine(winSize.x, coo.y, winSize.x - 40, coo.y - 20, paint);
canvas.drawLine(winSize.x, coo.y, winSize.x - 40, coo.y + 20, paint);
//下箭頭
canvas.drawLine(coo.x, winSize.y, coo.x - 20, winSize.y - 40, paint);
canvas.drawLine(coo.x, winSize.y, coo.x + 20, winSize.y - 40, paint);
//爲座標系繪製文字
drawText4Coo(canvas, coo, winSize, paint);
}
/**
* 爲座標系繪製文字
*
* @param canvas 畫布
* @param coo 座標系原點
* @param winSize 屏幕尺寸
* @param paint 畫筆
*/
private static void drawText4Coo(Canvas canvas, Point coo, Point winSize, Paint paint) {
//繪製文字
paint.setTextSize(50);
canvas.drawText("x", winSize.x - 60, coo.y - 40, paint);
canvas.drawText("y", coo.x - 40, winSize.y - 60, paint);
paint.setTextSize(25);
//X正軸文字
for (int i = 1; i < (winSize.x - coo.x) / 50; i++) {
paint.setStrokeWidth(2);
canvas.drawText(100 * i + "", coo.x - 20 + 100 * i, coo.y + 40, paint);
paint.setStrokeWidth(5);
canvas.drawLine(coo.x + 100 * i, coo.y, coo.x + 100 * i, coo.y - 10, paint);
}
//X負軸文字
for (int i = 1; i < coo.x / 50; i++) {
paint.setStrokeWidth(2);
canvas.drawText(-100 * i + "", coo.x - 20 - 100 * i, coo.y + 40, paint);
paint.setStrokeWidth(5);
canvas.drawLine(coo.x - 100 * i, coo.y, coo.x - 100 * i, coo.y - 10, paint);
}
//y正軸文字
for (int i = 1; i < (winSize.y - coo.y) / 50; i++) {
paint.setStrokeWidth(2);
canvas.drawText(100 * i + "", coo.x + 20, coo.y + 10 + 100 * i, paint);
paint.setStrokeWidth(5);
canvas.drawLine(coo.x, coo.y + 100 * i, coo.x + 10, coo.y + 100 * i, paint);
}
//y負軸文字
for (int i = 1; i < coo.y / 50; i++) {
paint.setStrokeWidth(2);
canvas.drawText(-100 * i + "", coo.x + 20, coo.y + 10 - 100 * i, paint);
paint.setStrokeWidth(5);
canvas.drawLine(coo.x, coo.y - 100 * i, coo.x + 10, coo.y - 100 * i, paint);
}
}
複製代碼
之前看到一個類有不少方法都有些不耐煩,這麼多,怎麼記得住。
如今看到一個類有不少方法,--哇,太好了,哈哈,居然連這方法都有,做者真給力省的我實現了。
Canvas圖形繪製的API,全部的我分了一下類:以下(顏色、點、線、矩形、類圓、文字、圖片、其餘)
下面一一介紹:bash
/**
* 繪製顏色(注意在畫座標系前繪製,不然後者覆蓋)
* @param canvas
*/
private void drawColor(Canvas canvas) {
// canvas.drawColor(Color.parseColor("#E0F7F5"));
// canvas.drawARGB(255, 224, 247, 245);
// 三者等價
canvas.drawRGB(224, 247, 245);
}
複製代碼
/**
* 繪製點
* @param canvas
*/
private void drawPoint(Canvas canvas) {
//繪製點
canvas.drawPoint(100, 100, mRedPaint);
////繪製一組點,座標位置由float數組指定(必須是2的倍數個)
canvas.drawPoints(new float[]{
400, 400, 500, 500,
600, 400, 700, 350,
800, 300, 900, 300
}, mRedPaint);
}
複製代碼
/**
* 繪製線
* @param canvas
*/
private void drawLine(Canvas canvas) {
canvas.drawLine(500, 200, 900, 400, mRedPaint);
//繪製一組點,座標位置由float數組指定(必須是4的倍數個)
canvas.drawLines(new float[]{
200, 200, 400, 200,
400, 200, 200, 400,
200, 400, 400, 400
}, mRedPaint);
}
複製代碼
/**
* 繪製矩形
*
* @param canvas
*/
private void drawRect(Canvas canvas) {
canvas.drawRect(100, 100, 500, 300, mRedPaint);
//等價上行
// Rect rect = new Rect(100, 100, 500, 300);
// canvas.drawRect(rect,mRedPaint);
//(左上右下X圓角,Y圓角)
canvas.drawRoundRect(100 + 500, 100, 500 + 500, 300, 50, 50, mRedPaint);
}
複製代碼
/**
* 繪製類圓
*
* @param canvas
*/
private void drawLikeCircle(Canvas canvas) {
//繪製圓(矩形邊界,畫筆)
canvas.drawCircle(650, 200, 100, mRedPaint);
// canvas.drawOval(100, 100, 500, 300, mRedPaint);
//等價上行
//繪製橢圓(矩形邊界,畫筆)
RectF rect = new RectF(100, 100, 500, 300);
canvas.drawOval(rect, mRedPaint);
RectF rectArc = new RectF(100 + 500, 100, 500 + 500, 300);
//繪製圓弧(矩形邊界,開始角度,掃過角度,使用中心?邊緣兩點與中心連線區域:邊緣兩點連線區域)
canvas.drawArc(rectArc, 0, 90, true, mRedPaint);
RectF rectArc2 = new RectF(100 + 500 + 300, 100, 500 + 500 + 300, 300);
//繪製圓弧(矩形邊界,開始角度,掃過角度,使用中心?邊緣兩點與中心連線區域:邊緣兩點連線區域)
canvas.drawArc(rectArc2, 0, 90, false, mRedPaint);
}
複製代碼
/**
* 繪製圖片
* @param canvas
*/
private void drawBitmap(Canvas canvas) {
//1.定點繪製圖片
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.menu_bg);
canvas.drawBitmap(bitmap, 100, 100, mRedPaint);
//2.適用變換矩陣繪製圖片
Matrix matrix = new Matrix();
//設置變換矩陣:縮小3倍,斜切0.5,右移150,下移100
matrix.setValues(new float[]{
1, 0.5f, 1500 * 3,
0, 1, 100 * 3,
0, 0, 3
});
canvas.drawBitmap(bitmap, matrix, mRedPaint);
//3.圖片適用矩形區域不剪裁
RectF rectf1 = new RectF(100 + 900, 100, 600 + 900, 400);
canvas.drawBitmap(bitmap, null, rectf1, mRedPaint);
//4.圖片裁剪出的矩形區域
Rect rect = new Rect(300, 300, 400, 400);
//圖片適用矩形區域
RectF rectf2 = new RectF(100 + 900, 100 + 400, 600 + 900, 400 + 400);
canvas.drawBitmap(bitmap, rect, rectf2, mRedPaint);
}
複製代碼
1).一開始挺納悶Picture不就是圖片嗎?而後翻了一下API:微信
/**
* A Picture records drawing calls (via the canvas returned by beginRecording)
* and can then play them back into Canvas (via {@link Picture#draw(Canvas)} or
* {@link Canvas#drawPicture(Picture)}).For most content (e.g. text, lines, rectangles),
* drawing a sequence from a picture can be faster than the equivalent API
* calls, since the picture performs its playback without incurring any
* method-call overhead.
*
* <p class="note"><strong>Note:</strong> Prior to API level 23 a picture cannot
* be replayed on a hardware accelerated canvas.</p>
一個Picture類記錄繪製(經過beginRecording方法返回的Canvas),能夠展現這個Canvas
到其餘Canvas上(經過Picture#draw(Canvas)或者Canvas#drawPicture(Picture)),
對於大多數的內容,從picture繪製都要比相應的API要快速,由於picture的展示不會招致方法調用開銷
在API級別23以前,沒法在硬件加速畫布上展現Picture(自譯,僅供參考)
複製代碼
2).經過一段代碼你就能很清楚它是幹嗎用的ide
若是繪製一個品字,須要這樣:工具
private void drawPicture(Canvas canvas) {
canvas.drawRect(100, 0, 200, 100, mRedPaint);
canvas.drawRect(0, 100, 100, 200, mRedPaint);
canvas.drawRect(200, 100, 300, 200, mRedPaint);
}
複製代碼
若是想要複用這個品字形,大多數人知道,平移畫布,複製粘貼,
對於少許的代碼,這還能夠接收,若是是很是複雜的圖形,每次繪製重複的內容,會浪費性能性能
private void drawPicture(Canvas canvas) {
//建立Picture對象
Picture picture = new Picture();
//肯定picture產生的Canvas元件的大小,並生成Canvas元件
Canvas recodingCanvas = picture.beginRecording(canvas.getWidth(), canvas.getHeight());
//Canvas元件的操做
recodingCanvas.drawRect(100, 0, 200, 100, mRedPaint);
recodingCanvas.drawRect(0, 100, 100, 200, mRedPaint);
recodingCanvas.drawRect(200, 100, 300, 200, mRedPaint);
//Canvas元件繪製結束
picture.endRecording();
canvas.save();
canvas.drawPicture(picture);//使用picture的Canvas元件
canvas.translate(0, 300);
picture.draw(canvas);//同上:使用picture的Canvas元件
canvas.drawPicture(picture);
canvas.translate(350, 0);
canvas.drawPicture(picture);
canvas.restore();
複製代碼
Picture至關於先拍一張照片,而且是在別的Canvas上,在別的Canvas上,在別的Canvas上!
重要的話說三遍:當須要的時候在貼在當前的canvas上,picture繪製的優點就是節能減排
當有大量複雜內容須要複用,Picture這個的canvas元件是不二的選擇:
/**
* 繪製文字
*
* @param canvas
*/
private void drawText(Canvas canvas) {
mRedPaint.setTextSize(100);
canvas.drawText("張風捷特烈--Toly", 200, 300, mRedPaint);
}
複製代碼
無聊的代碼終於敲完了,進入正題。
之前對Canvas的變換很厭倦,如今看了鍵值是神技
做爲一代PS大神的我,理解Canvas狀態保存與恢復本應易如反掌,爲什麼最近才豁然開朗
private void stateTest(Canvas canvas) {
canvas.drawLine(mCoo.x + 500, mCoo.y + 200, mCoo.x + 900, mCoo.y + 400, mRedPaint);
// canvas.rotate(45);
canvas.drawRect(mCoo.x + 100, mCoo.x + 100, mCoo.y + 300, mCoo.y + 200, mRedPaint);
}
複製代碼
問題來了,想畫一個斜45度的矩形怎麼辦?
貌似沒有斜矩形的API,一個一個點找,貌似太麻煩了,我把紙轉一下不就好了嗎!
紙就是Canvas,看一下API,果真有rotate()方法,懷着忐忑的心情:
private void stateTest(Canvas canvas) {
canvas.drawLine(mCoo.x + 500, mCoo.y + 200, mCoo.x + 900, mCoo.y + 400, mRedPaint);
canvas.rotate(45);
canvas.drawRect(mCoo.x + 100, mCoo.x + 100, mCoo.y + 300, mCoo.y + 200, mRedPaint);
}
複製代碼
果真轉得天翻地覆
PS中的圖層可謂PS的精華,它保證了在一個圖層中繪製而不會影響到其餘的圖層
在Canvas中每次的save()都存將先前的狀態保存下來,產生一個新的繪圖層,
咱們能夠爲所欲爲地地畫而不會影響其餘已畫好的圖,最後用restore()將這個圖層合併到原圖層
這像是棧的概念,每次save(),新圖層入棧(注意能夠save屢次),只有棧頂的層能夠進行操做,restore()彈棧
private void stateTest(Canvas canvas) {
canvas.drawLine(mCoo.x + 500, mCoo.y + 200, mCoo.x + 900, mCoo.y + 400, mRedPaint);
canvas.drawRect(mCoo.x + 100, mCoo.x + 100, mCoo.y + 300, mCoo.y + 200, mRedPaint);
canvas.save();//保存canvas狀態
//(角度,中心點x,中心點y)
canvas.rotate(45, mCoo.x + 100, mCoo.y + 100);
mRedPaint.setColor(Color.parseColor("#880FB5FD"));
canvas.drawRect(mCoo.x + 100, mCoo.x + 100, mCoo.y + 300, mCoo.y + 200, mRedPaint);
canvas.restore();//圖層向下合併
}
複製代碼
效果必變,是否是清爽許多
private void stateTest(Canvas canvas) {
canvas.save();
canvas.translate(mCoo.x, mCoo.y);//將原點平移到座標系原點
canvas.drawLine(500, 200, 900, 400, mRedPaint);
canvas.drawRect(100, 100, 300, 200, mRedPaint);
canvas.save();//保存canvas狀態
//(角度,中心點x,中心點y)
canvas.rotate(45, 100, 100);
mRedPaint.setColor(Color.parseColor("#880FB5FD"));
canvas.drawRect(100, 100, 300, 200, mRedPaint);
canvas.restore();//圖層向下合併
canvas.restore();
}
複製代碼
private void stateTest(Canvas canvas) {
canvas.save();
canvas.translate(mCoo.x, mCoo.y);//將原點平移到座標系原點
canvas.drawLine(500, 200, 900, 400, mRedPaint);
canvas.drawRect(100, 100, 300, 200, mRedPaint);
canvas.save();//保存canvas狀態
//(角度,中心點x,中心點y)
canvas.scale(2, 2, 100, 100);
mRedPaint.setColor(Color.parseColor("#880FB5FD"));
canvas.drawRect(100, 100, 300, 200, mRedPaint);
canvas.restore();//圖層向下合併
canvas.restore();
}
複製代碼
private void stateTest(Canvas canvas) {
canvas.save();
canvas.translate(mCoo.x, mCoo.y);//將原點平移到座標系原點
canvas.drawLine(500, 200, 900, 400, mRedPaint);
canvas.drawRect(100, 100, 300, 200, mRedPaint);
canvas.save();//保存canvas狀態
canvas.skew(1f,0f);
mRedPaint.setColor(Color.parseColor("#880FB5FD"));
canvas.drawRect(100, 100, 300, 200, mRedPaint);
canvas.restore();//圖層向下合併
canvas.restore();
}
複製代碼
public int save (int saveFlags)
默認:MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG
ALL_SAVE_FLAG 保存所有狀態
CLIP_SAVE_FLAG 過時---僅保存剪輯區(不做爲圖層)
CLIP_TO_LAYER_SAVE_FLAG 過時---僅剪裁區做爲圖層保存
FULL_COLOR_LAYER_SAVE_FLAG 過時---僅保存圖層的所有色彩通道
HAS_ALPHA_LAYER_SAVE_FLAG 過時---僅保存圖層的alpha(不透明度)通道
MATRIX_SAVE_FLAG 過時---僅保存Matrix信息( translate, rotate, scale, skew)
複製代碼
int count = canvas.getSaveCount();//獲取圖層的個數3
canvas.restoreToCount(1);//直接恢復到第幾個圖層
複製代碼
private void clip(Canvas canvas) {
//剪裁區域
Rect rect = new Rect(20, 100, 250, 300);
canvas.clipRect(rect);
canvas.drawRect(0, 0, 200, 300, mRedPaint);
}
複製代碼
private void clip(Canvas canvas) {
//剪裁區域
Rect rect = new Rect(20, 100, 250, 300);
canvas.clipOutRect(rect);
canvas.drawRect(0, 0, 200, 300, mRedPaint);
}
複製代碼
Canvas的相關內容就到這裏(注:路徑的繪製會在Path篇精講),下一節將帶來畫筆Paint的知識
項目源碼 | 日期 | 備註 |
---|---|---|
V0.1--無 | 2018-11-5 | Android關於Canvas你所知道的和不知道的一切 |
V0.2--無 | 2018-11-6 | 增長繪製Picture的內容 |
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
個人github | 個人簡書 | 個人CSDN | 我的網站 |
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持