Android绘图(一)基础篇
目錄
- 一、繪圖入門
- 二、繪制位圖
- 三、繪制點(diǎn)
- 四、繪制直線
- 五、繪制矩形
- 六、繪制圓
- 七、繪制路徑
-
- 7.1 往 Path 中添加線條
- 7.2 往 Path 中添加矩形、橢圓、弧
- 7.3 往 Path 中添加曲線和貝塞爾曲線
- 7.4 將 Path 中的圖形進(jìn)行運(yùn)算
- 7.5 繪制文字
-
- 7.5.1 在指定位置繪制文本
- 八、Paint的FontMetrics使用
-
- 8.1 行距
- 8.2 計(jì)算fontPadding
- 8.3文本在控件水平方向居中顯示
- 8.4 文本在控件垂直方向居中顯示
- 8.5 Paint的setTextAlign使用
- 九、繪制驗(yàn)證碼
一、繪圖入門
Canvas 翻譯為“畫布”,可以理解成畫家作畫時(shí)的宣紙。Canvas 供了若干方法用于繪制各種圖形圖案——點(diǎn)、線、圓等等。Paint 翻譯為“畫筆”,為繪圖定義各種參數(shù)——顏色、線條樣式、圖案樣式等等。通常的繪圖思路是先定義 Paint 對(duì)象,指定繪圖參數(shù),再通過 Canvas 對(duì)象進(jìn)行圖 形繪制,繪圖的結(jié)果因 Paint 的不同而不同。Paint 類用于定義繪圖時(shí)的參數(shù),主要包含顏色、文本、圖形樣式、位圖模式、濾鏡等幾個(gè)方面。
顏色是指繪圖時(shí)使用的顏色,在 Android 中顏色可以指定透明度,使用 16 進(jìn)制來表示顏色時(shí),格式通常為#AARRGGBB,其中,AA 表示透明度、RR 表示紅色、GG 表示綠色、BB 表示藍(lán)色,Color 類定義了顏色信息,內(nèi)置了常用顏色的 int 型常量,比如 Color.RED 是紅色,Color.BLUE 是藍(lán)色……如果您習(xí)慣了 16 進(jìn)制的顏色,Color 類的靜態(tài)方法 parseColor(String colorString)可以將 16進(jìn)制顏色轉(zhuǎn)換成 Color 類型。
先來看看簡(jiǎn)單的使用
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ImageView iv = findViewById(R.id.iv1);// 創(chuàng)建空白的bitmapBitmap bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888);// 新建畫布,關(guān)聯(lián)bitmapCanvas canvas = new Canvas(bitmap);// 繪制黑色背景canvas.drawColor(Color.BLACK);// 定義畫筆Paint paint = new Paint();// 設(shè)置畫筆顏色paint.setColor(Color.WHITE);paint.setAntiAlias(true);// 畫筆實(shí)心paint.setStyle(Paint.Style.FILL);// 文本水平居左對(duì)齊paint.setTextAlign(Paint.Align.LEFT);// 字體大小設(shè)置paint.setTextSize(32f);paint.setTextSkewX(0.5f); // 設(shè)置文本傾斜度,取值0~1,正負(fù)表示傾斜方向paint.setUnderlineText(true);// 設(shè)置下劃線paint.setFakeBoldText(true); // 設(shè)置粗體// 繪制文字canvas.drawText("hello 你好啊!", 10, 100, paint);// 繪制圖形// 設(shè)置畫筆空心樣式paint.setStyle(Paint.Style.STROKE);paint.setColor(Color.RED);// 設(shè)置畫筆描邊的邊框?qū)挾萷aint.setStrokeWidth(20);// 設(shè)置畫筆連接處的形狀paint.setStrokeJoin(Paint.Join.BEVEL);// 繪制矩形canvas.drawRect(new Rect(20, 200, 350, 350), paint);//顯示bitmap到ImageView中iv.setImageBitmap(bitmap);
}
效果圖:
二、繪制位圖
常用方法
// bitmap 繪制在畫布上,同時(shí)指定位圖左上角相對(duì)于畫布的坐標(biāo),大小與原位置相同,不進(jìn)行任何縮放。
public void drawBitmap( Bitmap bitmap, float left, float top, Paint paint)
// 下面兩個(gè)方法從源bitmap中摳出一塊大小區(qū)域?yàn)閟rc的圖片并繪制到canvas的dst處。src和ds 的大小與比例關(guān)系影響到最終的繪制效果,這個(gè)過程是自動(dòng)縮放以適應(yīng)dest區(qū)域的
public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
ps:繪制位圖時(shí),除非需要進(jìn)行位圖運(yùn)算,否則,并不需要指定 paint 對(duì)象,直接傳遞null 即可。
案例-使用drawBitmap方法在ImageView上繪制2個(gè)位圖,一個(gè)按原始大小繪制,另一個(gè)則按2倍大小繪制
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ImageView iv = findViewById(R.id.iv);// 創(chuàng)建空白位圖Bitmap bitmap = Bitmap.createBitmap(500, 800, Bitmap.Config.ARGB_8888);// 創(chuàng)建畫布并關(guān)聯(lián)位圖Canvas canvas = new Canvas(bitmap);// 繪制背景色,便以區(qū)分canvas.drawColor(Color.BLACK);// 獲取資源目錄下的原圖Bitmap sourceBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);// 繪制原圖canvas.drawBitmap(sourceBitmap, 0, 0, null);// 獲取原圖寬高int bmpWidth = sourceBitmap.getWidth();int bmpHeight = sourceBitmap.getHeight();// 創(chuàng)建原圖的矩形Rect src = new Rect(0, 0, bmpWidth, bmpHeight);// 創(chuàng)建放大后的矩形,位于原圖下方Rect dest = new Rect(0, bmpHeight, bmpWidth * 2, bmpHeight + bmpHeight * 2);// 繪制放大后的位圖canvas.drawBitmap(sourceBitmap, src, dest, null);// 顯示到ImageView上iv.setImageBitmap(bitmap);
}
效果圖如下:
三、繪制點(diǎn)
點(diǎn)的大小取決于 setStrokeWidth()方法的參數(shù),參數(shù)值越大,點(diǎn)也就越大。所以,不要以為一個(gè)點(diǎn)就是屏幕上的一個(gè)像素。如果將 stroke 的寬度設(shè)置為足夠大,我們發(fā)現(xiàn)最終繪制出來的點(diǎn)其實(shí)是一個(gè)正方形。繪制點(diǎn)的方法一共有三個(gè):
// 該方法在(x,y)處繪制一個(gè)點(diǎn)。
public void drawPoint(float x, float y, Paint paint)
// 該方法的參數(shù) pts 是一個(gè)數(shù)組,從下標(biāo) 0 開始每 2 個(gè)數(shù)確定一個(gè)點(diǎn),連續(xù)繪制多個(gè)點(diǎn)。多余的元素會(huì)忽略。
public void drawPoints(float[] pts, Paint paint)
// 從 pts 數(shù)組中的第 offset 處開始取出 count 個(gè)數(shù)字,以 2 個(gè)數(shù)為一組確實(shí)一個(gè)點(diǎn),連 續(xù)繪制若干個(gè)點(diǎn)。忽略多余的元素
public void drawPoints(float[] pts, int offset, int count, Paint paint)
示例如下:
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ImageView iv = findViewById(R.id.iv);Bitmap bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);// 繪制背景canvas.drawColor(Color.BLACK);//畫一個(gè)紅色的點(diǎn)Paint paint = new Paint();paint.setColor(Color.RED);paint.setStrokeWidth(10);canvas.drawPoint(120, 20, paint);// 畫一組藍(lán)色的點(diǎn)paint.setColor(Color.BLUE);float[] points = new float[]{10, 10, 50, 50, 50, 100, 50, 150};canvas.drawPoints(points, paint);// 畫一組綠色的點(diǎn)paint.setColor(Color.GREEN);points = new float[]{20, 20, 60, 60, 60, 80, 60, 180};canvas.drawPoints(points, 3, 4, paint);// 顯示在ImageView上iv.setImageBitmap(bitmap);
}
效果圖:
四、繪制直線
兩個(gè)點(diǎn)確定一條直線,所以,繪制線條時(shí),需要指定兩個(gè)點(diǎn)的坐標(biāo)。同畫點(diǎn)一樣,繪制線條也有 3 個(gè)重載的方法:
// 在(startX,startY)和(stopX,stopY)兩個(gè)點(diǎn)之間繪制一條直線。
public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
// pts 數(shù)組中每4個(gè)數(shù)一組繪制一條直線,多余的元素會(huì)忽略。
public void drawLines(float[] pts, Paint paint)
// 從 pts 數(shù)組中的 offset 索引處開始,取出 count 個(gè)元素,并以 4 個(gè)數(shù)一組繪制直線,忽略多余的元素。
public void drawLines(float[] pts, int offset,int count, Paint paint)
五、繪制矩形
繪制矩形時(shí),參數(shù)分為兩種:一種是指定 left、top、right、bottom 等 4 個(gè)參數(shù),另一種直接指定一個(gè) Rect 對(duì)象或 RectF 對(duì)象。繪制直角矩形的三個(gè)重載方法如下:
public void drawRect(float left,float top, float right,float bottom,Paint paint)public void drawRect(Rect r,Paint paint)public void drawRect(RectF r,Paint paint)
圓角矩形的幾何形狀比直角矩形相對(duì)復(fù)雜一些,我們需要指定 4 個(gè)拐角的弧度,4 個(gè)角的弧度不能單獨(dú)設(shè)置,而是統(tǒng)一設(shè)置為相同的值。拐角弧度實(shí)際上是圓或橢圓的一段弧線,如圖所示:
繪制圓角矩形一共有 2 個(gè)重載的方法:
// 該方法用于繪制一個(gè)圓角矩形,left、top、right、bottom 構(gòu)建一個(gè)矩形,rx、ry 分別是圓角處的水平半徑和垂直半徑。rx 和 ry 不一定相同,如果不同,則是橢圓上的一段弧線。
public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,Paint paint)
// 該方法是上面方法的另一種重載形式。
public void drawRoundRect(RectF rect,float rx,float ry,Paint paint)
下面的代碼繪制了三個(gè)矩形,一個(gè)直角矩形,一個(gè)為空心圓角矩形,另一個(gè)是有填充顏色的實(shí)心圓角矩形:
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main5);ImageView iv = findViewById(R.id.iv);Bitmap bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);canvas.drawColor(Color.BLACK);Paint paint = new Paint();paint.setAntiAlias(true);// 該方法用于設(shè)置落筆時(shí)的樣式,控制我們的畫筆在離開畫板時(shí)留下的最后一點(diǎn)圖形paint.setStrokeCap(Paint.Cap.ROUND);// 當(dāng)繪圖樣式為 STROKE 時(shí),該方法用于指定線條連接處的拐角樣式,能使繪制的圖形更加平滑paint.setStrokeJoin(Paint.Join.ROUND);paint.setColor(Color.RED);// 繪制紅色的直角矩形canvas.drawRect(new Rect(0, 0, 100, 100), paint);// 繪制綠色的圓角矩形paint.setColor(Color.GREEN);canvas.drawRoundRect(new RectF(100, 100, 200, 200), 20, 20, paint);// 繪制藍(lán)色的圓角矩形(空心)paint.setColor(Color.BLUE);paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(5);// 圓角的半徑可以不一樣canvas.drawRoundRect(new RectF(200, 200, 400, 400), 10, 20, paint);iv.setImageBitmap(bitmap);
}
效果圖:
注意: 繪制圓角矩形邊框的時(shí)候,需要考慮邊框的寬度,也就是paint的setStrokeWidth設(shè)置的大小,如果canvas從(0,0)左上角開始繪制圓角矩形邊框, 那么canvas.drawRoundRect的4個(gè)矩形頂點(diǎn)坐標(biāo)需要往里面縮進(jìn)邊框?qū)挾鹊囊话?否則繪制的區(qū)域在canvas之外是看不到的,導(dǎo)致繪制的邊框?qū)挾瓤雌饋硪阮A(yù)想的要小一半,并且4個(gè)圓角看起來會(huì)比非圓角部分的線條要粗一點(diǎn).
六、繪制圓
在對(duì)圖形進(jìn)行分類時(shí),我將圓、橢圓、扇形、弧線統(tǒng)一歸類到“圓”這一類中,扇形和弧線可以認(rèn)為是圓或橢圓的一部分,橢圓的大小是由他的外切矩形來決定的,這實(shí)際上和幾何學(xué)中的定義完全一致,如圖所示:
繪制橢圓的方法如下:
public void drawOval(float left, float top, float right, float bottom, Paint paint)public void drawOval(RectF oval, Paint paint)
繪制橢圓時(shí),如果外切矩形的長(zhǎng)和寬相等,即為正方形,繪制出來的圖形就是一個(gè)正圓,但 是 Cavnas 類供了另一個(gè)更加簡(jiǎn)單實(shí)用的方法,供圓點(diǎn)的坐標(biāo)和半徑即可。
//(cx、cy)為圓心坐標(biāo),radius 為圓的半徑
public void drawCircle(float cx, float cy, float radius, Paint paint)
弧線和扇形本質(zhì)上更是相似,弧線是橢圓上的一段,而扇形則是將弧線的兩個(gè)端點(diǎn)和橢圓中心點(diǎn)使用線條連接形成的閉合區(qū)域。理解弧線和扇形的演變過程便很容易明白方法中的參數(shù)意義,如下圖所示:
繪制弧線和扇形的方法如下:
// 參數(shù)oval是規(guī)定橢圓的范圍, startAngle 表示起始角度,sweepAngle 表示扇形或弧線所占的角度,
// 正數(shù)表示順時(shí)針,負(fù)數(shù)表示逆時(shí)針,useCenter 參數(shù)詢問是否要使用中心點(diǎn),為true 表示扇形,為 false 表示弧線
public void drawArc(RectF oval,float startAngle,float sweepAngle,boolean useCenter,Paint paint)public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle,boolean useCenter,Paint paint)
下面的代碼演示了弧線和扇形的繪制方法,采用了 Style.STROKE 的圖形模式,如果將 Style設(shè)置為 Style.FILL,不管是弧線還是扇形,都可以使用顏色進(jìn)行填充
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main6);ImageView iv = findViewById(R.id.iv);Bitmap bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);canvas.drawColor(Color.BLACK);Paint paint = new Paint();paint.setAntiAlias(true);paint.setStrokeWidth(5);paint.setStrokeJoin(Paint.Join.ROUND);paint.setStrokeCap(Paint.Cap.ROUND);// 空心模式paint.setStyle(Paint.Style.STROKE);RectF oval = new RectF(100, 100, 400, 200);// 繪制橢圓paint.setColor(Color.GRAY);canvas.drawOval(oval, paint);// 繪制圓弧,起始角度90度(也就是y軸原點(diǎn)),掃過45度(也就是8點(diǎn)中方向)paint.setColor(Color.RED);canvas.drawArc(oval,90,45,false,paint);// 繪制扇形,起始角度是0度(也就是在x軸原點(diǎn)),掃過-45度(也就是3點(diǎn)鐘方向,因?yàn)锳ndroid坐標(biāo)系是向下的)paint.setColor(Color.GREEN);canvas.drawArc(oval,0,-45,true,paint);iv.setImageBitmap(bitmap);
}
效果圖:
七、繪制路徑
Path 是 Graphics2D 中一個(gè)非常重要的概念,表示“路徑”,理解該概念時(shí)保持“路徑”的本色就好。路徑可以是直的、也可以是彎的,可以是閉合的、也可以是非閉合的,可以是圓形的、也可以是方形的,可以是單個(gè)的、也可以是多個(gè)的,可以是簡(jiǎn)單的、也可以是復(fù)雜的……總的來說,路徑是基于普通圖形但是功能比普通圖形更強(qiáng)的一種復(fù)雜圖形。
Path 是一個(gè)類,用于繪制復(fù)雜圖形,創(chuàng)建之初什么也沒有,只有往 Path 中添加了具體的形狀,Path 才會(huì)清晰可見。繪制 Path 時(shí),所有信息都存儲(chǔ)在 Path 對(duì)象中,Canvas 根據(jù) Path 對(duì)象來繪制相應(yīng)的圖形。
我們將 Path 的功能歸納成以下幾類:
7.1 往 Path 中添加線條
通過 Path 可以繪制出奇形怪狀的線條,并能將線條組合在一起變成折線,閉合后就是一個(gè)多邊形了。這就是 Path 的厲害之處。為此,Path 類中定義了 5 個(gè)方法
// 將畫筆移動(dòng)到點(diǎn)(x,y)的位置,使用的是絕對(duì)定位
public void moveTo(float x,float y)
// 將畫筆移動(dòng)到一個(gè)新點(diǎn),新點(diǎn)在上一個(gè)點(diǎn)的基礎(chǔ)上偏移(dx,dy),也就是說,新點(diǎn)的坐標(biāo)為(x+dx,y+dy)。這里使用的是相對(duì)定位。首字母“r”就是“relative(相對(duì))”的意思
public void rMoveTo(float dx,float dy)
// 將畫筆連接到點(diǎn)(x,y)的位置,并在上一個(gè)點(diǎn)與當(dāng)前點(diǎn)之前畫一條直線。使用的是絕對(duì)定位。
public void lineTo(float x,float y)
// 將畫筆移動(dòng)到一個(gè)新點(diǎn),新點(diǎn)在上一個(gè)點(diǎn)的基礎(chǔ)上偏移(dx,dy),新點(diǎn)的坐標(biāo)為(x+dx,y+dy),同時(shí),在新點(diǎn)與上一個(gè)點(diǎn)之間畫一條直線。這里使用的是相對(duì)定位。
public void rLineTo(float dx,float dy)
// 在第一個(gè)點(diǎn)和最后一個(gè)點(diǎn)之前畫一條直線,形成閉合區(qū)域。
public void close()
下面的案例使用 Path 繪制了一個(gè)五角星,這不是一個(gè)完美的五角星幾何圖形,因?yàn)槲鍌€(gè)點(diǎn)的坐標(biāo)并沒有正確計(jì)算出來,只是算了個(gè)大概。首先調(diào)用了 moveTo(0, 150)定義好這次繪圖的起 點(diǎn),接下來調(diào)用 rLineTo()方法通過相對(duì)定位計(jì)算出下一個(gè)點(diǎn)的坐標(biāo),并使用直線連接,最后,調(diào)用 close()方法連接最后一點(diǎn)和第一個(gè)點(diǎn)以形成閉合區(qū)域。
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main7);ImageView iv = findViewById(R.id.iv);Bitmap bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);canvas.drawColor(Color.BLACK);Paint paint = new Paint();paint.setAntiAlias(true);paint.setColor(Color.WHITE);paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(5);paint.setStrokeCap(Paint.Cap.ROUND);paint.setStrokeJoin(Paint.Join.ROUND);// 創(chuàng)建路徑Path path = new Path();path.moveTo(0, 150);path.rLineTo(300, 0);path.rLineTo(-300, 150);path.rLineTo(150, -300);path.rLineTo(150, 300);path.close();// 連接最后一點(diǎn)和第一個(gè)點(diǎn)以形成閉合區(qū)域// 繪制路徑canvas.drawPath(path, paint);iv.setImageBitmap(bitmap);
}
效果圖:
7.2 往 Path 中添加矩形、橢圓、弧
如果要往 Path 對(duì)象中添加矩形、橢圓、圓和弧,需要調(diào)用 Path 類中定義的一組以“add”開 頭的方法,這組方法有些需要傳遞一個(gè)類型為 Path.Direction 的參數(shù),這是一個(gè)枚舉類型,枚舉值 CW 表示順時(shí)針,CCW 表示逆時(shí)針,下一節(jié)內(nèi)容沿著圖形繪制文字時(shí),我們可以清晰地感受到方向?qū)L圖帶來的影響。
// 往 Path 對(duì)象中添加一個(gè)矩形
public void addRect(RectF rect,Path.Direction dir)public void addRect(float left,float top,float right,float bottom, Path.Direction dir)
// Path 對(duì)象中添加一個(gè)圓角矩形。該方法和前面繪制圓角矩形相比在定義四個(gè)角的弧線大小時(shí)功能更強(qiáng),能對(duì)四個(gè)角分別定義不同的弧線弧度
public void addRoundRect(RectF rect,float[] radii, Path.Direction dir)public void addRoundRect(RectF rect,float rx,float ry,Path.Direction dir)public void addRoundRect(float left, float top, float right, float bottom, float[] radii,Path.Direction dir)
// Path 對(duì)象中添加一個(gè)橢圓。
public void addOval(RectF oval, Path.Direction dir)public void addOval(float left, float top, float right, float bottom, Path.Direction dir)
// Path 對(duì)象中添加一個(gè)圓。
public void addCircle(float x, float y, float radius, Path.Direction dir)
// Path 對(duì)象中添加一段弧。本方法并沒有指定方向,因?yàn)榻嵌鹊恼?fù)已經(jīng)代表了方向,正數(shù)為順時(shí)針,負(fù)數(shù)為逆時(shí)針。
public void addArc(RectF oval, float startAngle, float sweepAngle)
public void addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle)
我們?cè)谙旅娴拇a中繪制了一個(gè) Path 對(duì)象,對(duì)象中同時(shí)包含了矩形、圓角矩形、橢圓、圓、弧線等圖形,顯然,Path 對(duì)象繪制出來的圖形更加復(fù)雜了。
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main8);ImageView iv = findViewById(R.id.iv);Bitmap bitmap = Bitmap.createBitmap(500, 620, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);canvas.drawColor(Color.BLACK);Paint paint = new Paint();paint.setStyle(Paint.Style.STROKE);paint.setAntiAlias(true);paint.setStrokeWidth(5);paint.setColor(Color.RED);Path path = new Path();// 繪制矩形path.addRect(new RectF(10, 10, 300, 100), Path.Direction.CCW);// 繪制圓角矩形,4個(gè)角的弧度都不一樣,2個(gè)數(shù)確定一個(gè)弧度path.addRoundRect(new RectF(10, 120, 300, 220),new float[]{10, 20, 20, 10, 30, 40, 40, 30},Path.Direction.CCW);// 橢圓path.addOval(new RectF(10, 240, 300, 340), Path.Direction.CCW);// 圓path.addCircle(60, 390, 50, Path.Direction.CCW);// 弧線path.addArc(new RectF(10, 500, 300, 600), -30, -60);// 繪制路徑canvas.drawPath(path,paint);iv.setImageBitmap(bitmap);
}
效果圖:
7.3 往 Path 中添加曲線和貝塞爾曲線
曲線包括弧線和貝塞爾曲線,與前面講的矩形、圓或弧線不同,繪制曲線時(shí)需要確定一個(gè)起點(diǎn),繪制的曲線會(huì)與該起點(diǎn)進(jìn)行連接,形成一個(gè)更加復(fù)雜的圖形。
貝塞爾曲線(Bézier curve)是圖形開發(fā)中的一個(gè)重要工具,通過三個(gè)點(diǎn)的,確定一條平滑的曲線,又稱貝茲曲線或貝濟(jì)埃曲線,是應(yīng)用于二維圖形應(yīng)用程序的數(shù)學(xué)曲線。一般的矢量圖形軟件通過它來精確畫出曲線,貝茲曲線由線段與節(jié)點(diǎn)組成,節(jié)點(diǎn)是可拖動(dòng)的支點(diǎn),線段像可伸縮的皮筋,我們?cè)诶L圖工具上看到的鋼筆工具就是來做這種矢量曲線的。貝塞爾曲線是計(jì)算機(jī)圖形學(xué)中相當(dāng)重要的參數(shù)曲線,在一些比較成熟的位圖軟件中也有貝塞爾曲線工具。貝塞爾曲線又分為一階貝塞爾曲線、二階貝塞爾曲線、三階貝塞爾曲線和高階貝塞爾曲線,一階貝塞爾曲線就是一條線段,Path 類支持二階貝塞爾曲線和三階貝塞爾曲線。如下圖所示分別是: 一階、二階、三階
前面提到,貝塞爾曲線通過 3 個(gè)點(diǎn)來繪制一條平滑的曲線,這 3 個(gè)點(diǎn)分別是起點(diǎn)、控制點(diǎn)和終點(diǎn)。比如,如果要繪制一條二階貝塞爾曲線,必須調(diào)用 moveTo()方法定義起點(diǎn),再調(diào)用如下方法繪制貝塞爾曲線
// (x1,y1)是控制點(diǎn),(x2,y2)是終點(diǎn)。
public void quadTo(float x1,float y1,float x2,float y2)
我們通過一段代碼演示如何繪制貝塞爾曲線
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main9);ImageView iv = findViewById(R.id.iv);Bitmap bitmap = Bitmap.createBitmap(500, 400, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);canvas.drawColor(Color.BLACK);Paint paint = new Paint();paint.setStyle(Paint.Style.STROKE);paint.setAntiAlias(true);paint.setStrokeWidth(5);paint.setColor(Color.RED);Path path = new Path();path.moveTo(100, 100);path.quadTo(200, 50, 300, 300);canvas.drawPath(path, paint);// 畫點(diǎn)(起點(diǎn)100,100,控制點(diǎn)200,50,終點(diǎn)300,300)paint.setColor(Color.GREEN);canvas.drawPoints(new float[]{100, 100, 200, 50, 300, 300}, paint);// 添加文案paint.setStrokeWidth(2);paint.setTextSize(22);canvas.drawText("起點(diǎn)",90,140,paint);canvas.drawText("控制點(diǎn)",220,55,paint);canvas.drawText("終點(diǎn)",280,340,paint);iv.setImageBitmap(bitmap);
}
效果圖:
三階貝塞爾曲線有 1 個(gè)起點(diǎn),2 個(gè)控制點(diǎn),1 個(gè)終點(diǎn),Path 類中通過下面方法進(jìn)行繪制
// (x1、y1)、(x2、y2)是控制點(diǎn),(x3、y3)是終點(diǎn)
public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
quadTo()和 cubicTo()的控制點(diǎn)和終點(diǎn)利用絕對(duì)定位來進(jìn)行確定,其實(shí)還有另外兩個(gè)方法,通過相對(duì)定位對(duì)各點(diǎn)進(jìn)行定義:
public void rQuadTo(float dx1, float dy1, float dx2, float dy2)
public void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
添加曲線可以使用arcTo()方法,arcTo()方法可以和 moveTo()配合使用,通過 moveTo()確定一個(gè)起點(diǎn),再通過 arcTo()繪制弧線。弧線是基于矩形的內(nèi)切圓上的一段,該弧線的起始點(diǎn)會(huì)和 moveTo()方法定義的點(diǎn)進(jìn)行連接。
public void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
public void arcTo(RectF oval, float startAngle, float sweepAngle)
public void arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)
上面三個(gè)方法的參數(shù)在前面都有說明,不再贅述,參數(shù) forceMoveTo 為 true 時(shí),表示開始一個(gè)新的圖形,不和上一個(gè)點(diǎn)進(jìn)行連接,為 false 時(shí)才和上一個(gè)點(diǎn)連接
我們通過一小段代碼來演示 artTo()方法的使用技巧:
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main10);ImageView iv = findViewById(R.id.iv);Bitmap bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);canvas.drawColor(Color.BLACK);Paint paint = new Paint();paint.setStyle(Paint.Style.STROKE);paint.setAntiAlias(true);paint.setStrokeWidth(5);// 繪制橢圓RectF oval1 = new RectF(150, 100, 350, 250);RectF oval2 = new RectF(150, 250, 350, 400);paint.setColor(Color.GRAY);canvas.drawOval(oval1, paint);canvas.drawOval(oval2, paint);Path path = new Path();paint.setColor(Color.GREEN);path.moveTo(100, 100);// true表示不會(huì)連接到上一個(gè)點(diǎn)(這里上一個(gè)點(diǎn)剛好是起始點(diǎn))path.arcTo(oval1, -30, 60, true);//forceMoveTo不傳默認(rèn)是false,表示會(huì)和上一個(gè)點(diǎn)進(jìn)行連接path.arcTo(oval2, 90, -45);canvas.drawPath(path, paint);iv.setImageBitmap(bitmap);}
效果圖:
從圖中可以看出下面的弧線和上一個(gè)點(diǎn)是連接起來的,它的上一個(gè)點(diǎn)剛好就是上一個(gè)圓弧的終點(diǎn)
7.4 將 Path 中的圖形進(jìn)行運(yùn)算
我們還可以將多個(gè) Path 進(jìn)行圖形運(yùn)算,得到更加復(fù)雜和不規(guī)則的圖形。Path 有一個(gè)靜態(tài)內(nèi)部類 Op,定義了 5 種運(yùn)算規(guī)則:
Path.Op. DIFFERENCE:差集,圖形 A 減去與圖形 B 重疊的區(qū)域后 A 余下的區(qū)域。
Path.Op. INTERSECT:交集,圖形 A 和圖形 B 的重疊區(qū)域。
Path.Op. REVERSE_DIFFERENCE:反差集,圖形 B 減去與圖形 A 重疊的區(qū)域后 B 余下的區(qū)域。
Path.Op. UNION:并集,包含了圖形 A 和圖形 B 的所有區(qū)域。
Path.Op.XOR:補(bǔ)集,即圖形 A 和圖形 B 的所有區(qū)域減去他們的重疊區(qū)域后余下的區(qū)域。
我們通過以下的表格來比較這 5 種圖形運(yùn)算的不同效果:
圖形A代表黑色正方形,圖形B代表紅色圓形
下面通過代碼演示,先繪制原圖查看效果:
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main11);ImageView iv = findViewById(R.id.iv);Bitmap bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);canvas.drawColor(Color.BLACK);Paint paint = new Paint();paint.setAntiAlias(true);paint.setStrokeWidth(5);// 繪制白色矩形paint.setStyle(Paint.Style.FILL); // 修改此參數(shù)可以查看填充模式還是邊框模式paint.setColor(Color.WHITE);Path path1 = new Path();path1.addRect(new RectF(10, 10, 110, 110), Path.Direction.CCW);canvas.drawPath(path1, paint);// 繪制紅色圓paint.setColor(Color.RED);Path path2 = new Path();path2.addCircle(100, 100, 50, Path.Direction.CCW);canvas.drawPath(path2, paint);iv.setImageBitmap(bitmap);}
效果圖如下:
然后對(duì)path進(jìn)行5種效果運(yùn)算演示
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main11);ImageView iv = findViewById(R.id.iv);Bitmap bitmap = Bitmap.createBitmap(800, 500, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);canvas.drawColor(Color.BLACK);Paint paint = new Paint();paint.setAntiAlias(true);paint.setStrokeWidth(5);//============原圖==========================// 繪制白色矩形paint.setStyle(Paint.Style.FILL); // 修改此參數(shù)可以查看填充模式還是邊框模式paint.setColor(Color.WHITE);Path path1 = new Path();path1.addRect(new RectF(10, 10, 110, 110), Path.Direction.CCW);canvas.drawPath(path1, paint);// 繪制紅色圓paint.setColor(Color.RED);Path path2 = new Path();path2.addCircle(100, 100, 50, Path.Direction.CCW);canvas.drawPath(path2, paint);//============原圖==========================// =============差集計(jì)算==============// path1 差 path2 ,也就是:path1-path2paint.setColor(Color.WHITE);path1.reset();path2.reset();path1.addRect(new RectF(10, 170, 110, 270), Path.Direction.CCW);path2.addCircle(100, 260, 50, Path.Direction.CCW);// 差集運(yùn)算path1.op(path2, Path.Op.DIFFERENCE);canvas.drawPath(path1,paint);// =============差集計(jì)算==============// =============交集==============// path1 交 path2 ,也就是:path1&path2paint.setColor(Color.WHITE);path1.reset();path2.reset();path1.addRect(new RectF(160, 170, 260, 270), Path.Direction.CCW);path2.addCircle(250, 260, 50, Path.Direction.CCW);// 交集運(yùn)算path1.op(path2, Path.Op.INTERSECT);canvas.drawPath(path1,paint);// =============差集計(jì)算==============// =============反差集==============// path1 反差集 path2 ,也就是:path2-path1paint.setColor(Color.WHITE);path1.reset();path2.reset();path1.addRect(new RectF(260, 170, 360, 270), Path.Direction.CCW);path2.addCircle(350, 260, 50, Path.Direction.CCW);// 反差集運(yùn)算path1.op(path2, Path.Op.REVERSE_DIFFERENCE);canvas.drawPath(path1,paint);// =============差集計(jì)算==============// =============并集==============// path1 并集 path2 ,也就是:path1|path2paint.setColor(Color.WHITE);path1.reset();path2.reset();path1.addRect(new RectF(450, 170, 550, 270), Path.Direction.CCW);path2.addCircle(540, 260, 50, Path.Direction.CCW);// 并集運(yùn)算path1.op(path2, Path.Op.UNION);canvas.drawPath(path1,paint);// =============差集計(jì)算==============// =============補(bǔ)集==============// path1 補(bǔ)集 path2 ,也就是:path1|path2 - path1&path2paint.setColor(Color.WHITE);path1.reset();path2.reset();path1.addRect(new RectF(650, 170, 750, 270), Path.Direction.CCW);path2.addCircle(740, 260, 50, Path.Direction.CCW);// 補(bǔ)集運(yùn)算path1.op(path2, Path.Op.XOR);canvas.drawPath(path1,paint);// =============差集計(jì)算==============iv.setImageBitmap(bitmap);}
效果圖:
7.5 繪制文字
Canvas為我們供了兩組方法,一組直接從指定的位置開始繪制文字,另一組沿著 Path 繪制文字:
public void drawText(char[] text, int index, int count, float x, float y, Paint paint)
public void drawText(String text, float x, float y, Paint paint)
public void drawText(String text, int start, int end, float x, float y, Paint paint)
public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint)
上面這一組方法是從指定的位置(坐標(biāo))開始繪制文字,雖然都是字符串,但是供了三種形式:char[]、String 和 CharSequence,本質(zhì)上并沒有什么不同,參數(shù) index 和count、start 和 end 可以從字符串中取出子串,而參數(shù) x、y 就是文字繪制的坐標(biāo)位置,其中 y 是文字的 baseline 的值
public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)
public void drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)
上面這兩個(gè)重載的 drawTextOnPath()方法用于沿著 Path 定義好的路徑繪制文字,這是一個(gè)很在趣的功能,文字在 Path 的帶領(lǐng)下龍飛鳳舞,靈活多變。參數(shù) hOffset 和 vOffset 用于定義文字離 Path 的水平偏移量和垂直偏移量,正數(shù)和負(fù)數(shù)影響文字與路徑的相對(duì)位 置。同樣的,也支持繪制從字符數(shù)組中截取的子串,index 表示起始索引,count 表示要截取的長(zhǎng)度。
下面的案例中繪制了 4 個(gè)字符串,一個(gè)繪制所有的字符串,中間兩個(gè)截取子串進(jìn)行繪制,最后一個(gè)沿著 Path 繪制出所有的文字,為了更好的理解文字與路徑的關(guān)系,所以把對(duì)應(yīng)的路徑也繪制出來了
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main12);ImageView iv = findViewById(R.id.iv);Bitmap bitmap = Bitmap.createBitmap(800, 450, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);canvas.drawColor(Color.BLACK);Paint paint = new Paint();paint.setColor(Color.WHITE);paint.setStyle(Paint.Style.FILL);paint.setAntiAlias(true);paint.setTextSize(30);String text = "天王蓋地虎,寶塔鎮(zhèn)河妖;野雞悶頭鉆,哪能上天王山";// 1.直接繪制canvas.drawText(text, 10, 50, paint);// 2.截取數(shù)量來繪制paint.setColor(Color.RED);// start從6開始,end=11,表示取[6,11)范圍的數(shù)量canvas.drawText(text, 6, 11, 10, 100, paint);// 截取方式二paint.setColor(Color.BLUE);// index從12開始,count=5,表示從索引12開始取5個(gè)字符canvas.drawText(text.toCharArray(), 12, 5, 10, 150, paint);// 3.通過路徑繪制// 先創(chuàng)建路徑Path path = new Path();path.moveTo(10, 300);path.quadTo(100, 100, 700, 400);paint.setColor(Color.GREEN);// 將文字繪制到路徑上canvas.drawTextOnPath(text, path, 10, 30, paint);// 繪制路徑,方便查看paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(2);paint.setColor(Color.RED);canvas.drawPath(path, paint);// 繪制到ImageView上iv.setImageBitmap(bitmap);
}
效果圖:
7.5.1 在指定位置繪制文本
下面2個(gè)方法都是標(biāo)記了過時(shí)的方法
public void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos,@NonNull Paint paint);
public void drawPosText(@NonNull char[] text, int index, int count,@NonNull @Size(multiple = 2) float[] pos,@NonNull Paint paint);
通過這2個(gè)方法可以按自定的坐標(biāo)集繪制字符串或者字符數(shù)組,例如要實(shí)現(xiàn)一個(gè)垂直方向的文本繪制可以這樣實(shí)現(xiàn)
class MyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, def: Int = 0) : View(context, attrs, def) {private val paint = Paint().apply {isAntiAlias = truetextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 22f, resources.displayMetrics)color = Color.RED}private val text = "月黑風(fēng)高"// 每個(gè)字的x和y坐標(biāo)集private val array = FloatArray(text.length * 2).apply {for (i in 0 until size step 2) {set(i, 100f) // x,水平方向每個(gè)字的x相同,都是100pxset(i + 1, (i + 1) * paint.textSize) // y,垂直方向每個(gè)字間隔一個(gè)字的大小}}override fun onDraw(canvas: Canvas?) {canvas?.drawColor(Color.BLACK)// 繪制文本canvas?.drawPosText(text, array, paint)}
}
效果圖:
八、Paint的FontMetrics使用
粉紅色就是TextView的背景色, 可以看到在Ascent和Descent之外分別還有一點(diǎn)距離才到TextView的邊緣, 也就是右側(cè)使用橙色方塊標(biāo)出的fontPadding
FontMetrics提供了如下屬性:
top: 即上邊界, 因?yàn)樵贏ndroid中, y軸正方向是向下的, 而基準(zhǔn)線(base line)是y=0, 所以這個(gè)值是一個(gè)負(fù)數(shù).它的值等于它到base line距離的負(fù)數(shù)
ascent: 字體文件中設(shè)置的Ascent值也是負(fù)數(shù), 理由同上,它的值是ascent到base line的距離的負(fù)數(shù)
descent: 字體文件中設(shè)置的Descent值正數(shù),因?yàn)樵诨鶞?zhǔn)線下面,它的值等于它到base line的距離
bottom: 下邊界, 正數(shù),理由同上,它的值等于它到base line的距離
leading: 兩行之間, 上一行的bottom和下一行的top的間距, 然而這個(gè)值總是0, 可以忽略,用下圖來描述leading
8.1 行距
行距就是相鄰兩行的基線之間的距離.默認(rèn)行距的實(shí)際值等于字體設(shè)置中的|Descent| + |Aescent|,在Android的TextView中, 可以通過android:lineSpacingExtra和android:lineSpacingMultiplier修改行距. 其中l(wèi)ineSpacingExtra默認(rèn)值為0, lineSpacingMultiplier默認(rèn)值為1, 有以下公式
行距=默認(rèn)行距 * lineSpacingMultiplier + lineSpacingExtra
8.2 計(jì)算fontPadding
頂部的fontPadding= |top - ascent |,底部的font padding= bottom - descent,通過android:includeFontPadding可以決定字體的高度是否包含fontPadding
Android中的字體高度是|bottom| + |top|, 而普通軟件(例如word, Sketch或者其他設(shè)計(jì)軟件)中, 字體高度使用的是|descent| + |ascent|, 所以Android中的字體在垂直方向上總是比設(shè)計(jì)稿的多占一點(diǎn)空間.
對(duì)于普通的字體, 要完美復(fù)刻設(shè)計(jì)稿的字體高度, 應(yīng)該把a(bǔ)ndroid:includeFontPadding設(shè)置為false,默認(rèn)是true
8.3文本在控件水平方向居中顯示
原理很簡(jiǎn)單,只需要計(jì)算繪制文字的x坐標(biāo) = 控件寬度的一半 - 文字寬度的一半即可, 代碼如下:
private val mPaint: Paint = Paint().apply {isAntiAlias = truecolor = Color.REDtextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 23f, resources.displayMetrics)
}override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)// 繪制的文本val txt = "hello go yes!!!"// 控件的寬度和高度的一半val x = measuredWidth / 2fval y = measuredHeight / 2f// 繪制中間水平線canvas?.drawLine(0f, y, measuredWidth.toFloat(), y, mPaint)// 繪制中間垂直線canvas?.drawLine(x, 0f, x, measuredHeight.toFloat(), mPaint)// 測(cè)量文字大小val rect = Rect()mPaint.getTextBounds(txt, 0, txt.length, rect)// 文字水平居中的x坐標(biāo)計(jì)算val finalX = x - rect.width() / 2f// 繪制文本canvas?.drawText(txt, finalX, y, mPaint)
}
效果圖:
由上圖可見文本在水平方向是居中了的,但是垂直方向并沒有居中(看感嘆號(hào)的位置就能對(duì)比出來了),這是因?yàn)槲覀儗⒗L制文本的y坐標(biāo)(baseline)定為了控件高度的一半,這是錯(cuò)誤的,我們需要將文本處于控件居中位置, 而不應(yīng)該是y坐標(biāo)(baseline), 這就需要向下移動(dòng)一個(gè)baseline的距離,這個(gè)距離的計(jì)算可以看下面介紹.
8.4 文本在控件垂直方向居中顯示
- 計(jì)算文字的高度一半到baseLine的距離
Baseline在文本的垂直方向很重要,只有先確定了Baseline的位置,換句話說就是y坐標(biāo)的值,我們才能準(zhǔn)確的將文字繪制在我們想要的位置上。Baseline的概念在我們使用TextView等系統(tǒng)控件直接設(shè)置文字內(nèi)容時(shí)是用不到的,但是如果我們想要在Canvas畫布上面繪制文字時(shí),Baseline的概念就必不可少了。以4個(gè)參數(shù)的drawText方法為例:
/*** canvas的drawText方法* @param text:待繪制的文本* @param x:從畫布上開始繪制的x坐標(biāo)(Canvas是一個(gè)原點(diǎn)在左上角的平面坐標(biāo)系)* @param y:baseLine所在的y坐標(biāo),不少人一開始以為y是繪制文字區(qū)域的底部坐標(biāo),其實(shí)是不正確的,這是兩個(gè)概念* @param paint: 畫筆*/
public void drawText(String text, float x, float y, Paint paint)
計(jì)算原理:
/*
如下所示:
---------------------top---------------------負(fù)數(shù)2 (相對(duì)原點(diǎn)baseline的距離)
---------------------ascent------------------ 負(fù)數(shù)1 (相對(duì)原點(diǎn)baseline的距離)
--------------------文本正中----------------- 距離top和bottom都等于(fontMetrics.bottom - fontMetrics.top)/2
---------------------baseline---------------- 0 (原點(diǎn))
---------------------descent-----------------正數(shù)1 (相對(duì)原點(diǎn)baseline的距離)
---------------------bottom------------------正數(shù)2 (相對(duì)原點(diǎn)baseline的距離)*/
// 文字的高度 = (fontMetrics.bottom - fontMetrics.top)
// 文字正中 = 文字高度的一半 = (fontMetrics.bottom - fontMetrics.top)/2
// 那么文字的正中到baseline的距離 = 文字高度的一半 - fontMetrics.bottom,即:
int distanceY = (int) (((fontMetrics.bottom - fontMetrics.top) / 2) - fontMetrics.bottom);
注意: 文本的正中的"值"并不是控件的getHeight()/2,也就是說getHeight() != (fontMetrics.bottom - fontMetrics.top)因?yàn)閒ontMetrics的值是相對(duì)于baseline的.而控件是相對(duì)左上角top來計(jì)算的.所以如果要在自定義View的正中央繪制文本,那么可以這么做
private val mPaint: Paint = Paint().apply {isAntiAlias = truecolor = Color.REDtextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 23f, resources.displayMetrics)
}override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)val txt = "hello go yes!!!"// 控件的寬度和高度的一半val x = measuredWidth / 2fval y = measuredHeight / 2f// 繪制中間水平線canvas?.drawLine(0f, y, measuredWidth.toFloat(), y, mPaint)// 繪制中間垂直線canvas?.drawLine(x, 0f, x, measuredHeight.toFloat(), mPaint)//=======================水平居中處理===============================// 測(cè)量文字大小val rect = Rect()mPaint.getTextBounds(txt, 0, txt.length, rect)// 文字水平居中的x坐標(biāo)計(jì)算val finalX = x - rect.width() / 2f//=======================垂直居中處理===============================// 計(jì)算文本高度一半到baseline的距離val metrics = mPaint.fontMetricsval distance = (metrics.bottom - metrics.top) / 2f - metrics.bottom// 矯正y坐標(biāo)(我們預(yù)想的就是讓文本正中的位置處于控件居中的位置,而文本正中距離baseline的距離就是我們計(jì)算y坐標(biāo)的偏移量)val finalY = y + distance// 繪制文本canvas?.drawText(txt, finalX, finalY, mPaint)
}
效果圖:
注意看感嘆號(hào)的位置是居中的, 換個(gè)中文就很明顯了
8.5 Paint的setTextAlign使用
該方法用于控制文本在水平方向的對(duì)齊方式, 可以理解成該字符串與起點(diǎn)的相對(duì)位置, 常用有3種:
1)Align.LEFT: 居左繪制,即通過drawText函數(shù)指定的起點(diǎn)在最左側(cè),文字從起點(diǎn)位置開始繪制
2)Align.CENTER:居中繪制,即通過drawText函數(shù)指定的起點(diǎn)在文字中間位置
3)Align.Right: 居右繪制,即通過drawText函數(shù)指定的起點(diǎn)在文字右側(cè)位置.
如下圖所示:
因此如果需要將文本繪制在控件的水平居中的位置,可以這么做
private val mPaint: Paint = Paint().apply {isAntiAlias = truecolor = Color.REDtextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 23f, resources.displayMetrics)textAlign = Paint.Align.CENTER // 文字水平居中對(duì)齊
}override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)val txt = "我是勝哥"// 控件的寬度的一半val x = measuredWidth / 2f// 繪制中間垂直線canvas?.drawLine(x, 0f, x, measuredHeight.toFloat(), mPaint)// 水平居中繪制文本, 可以看到此時(shí)計(jì)算x坐標(biāo)的時(shí)候就不需要計(jì)算文本的寬度了canvas?.drawText(txt, x, 100f, mPaint)
}
效果如下:
九、繪制驗(yàn)證碼
在 ImageView 繪制一個(gè)空心矩形,隨機(jī)產(chǎn)生 100 條干擾線,并隨機(jī)生成 4 個(gè)數(shù),字顯示在矩形框內(nèi)。
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ImageView iv = findViewById(R.id.iv);Bitmap bitmap = Bitmap.createBitmap(500, 300, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);canvas.drawColor(Color.BLACK);Paint paint = new Paint();paint.setAntiAlias(true);paint.setColor(Color.WHITE);paint.setStyle(Paint.Style.FILL);// 先繪制長(zhǎng)方型canvas.drawRect(new Rect(100, 100, 370, 200), paint);// 繪制隨機(jī)文字char[] text = new char[]{'我', '你', '他', '它', '東', '西', '南', '北', '中'};Random random = new Random();// 生成隨機(jī)數(shù)char[] randomText = new char[4];int index = 0;while (index < 4 && randomText[index] == 0) {char getChar = text[random.nextInt(text.length)];boolean contains = false;for (int i = 0; i < randomText.length; i++) {if (getChar == randomText[i]) {contains = true;break;}}if (!contains) {randomText[index] = getChar;index++;}}paint.setColor(Color.BLACK);paint.setTextSize(30);paint.setFakeBoldText(true);// 設(shè)置字間距為一個(gè)字的寬度paint.setLetterSpacing(1);// 繪制文本canvas.drawText(randomText, 0, 4, 110, 160, paint);// 繪制線條paint.setStyle(Paint.Style.STROKE);for (int i = 0; i < 100; i++) {// 隨機(jī)顏色int color = Color.argb(150,55 + random.nextInt(200),55 + random.nextInt(200),55 + random.nextInt(200));paint.setColor(color);// 繪制隨機(jī)直線條/*int startY = 100 + random.nextInt(100);int endX = 100 + random.nextInt(270);canvas.drawLine(100, startY, endX, startY, paint);*/// 繪制隨機(jī)貝塞爾曲線Path path = new Path();// 控制點(diǎn)int controlX = 100 + random.nextInt(100);int controlY = 100 + random.nextInt(100);// 起點(diǎn)path.moveTo(100, 100 + random.nextInt(100));// 終點(diǎn)int endX = 370;int endY = 100 + random.nextInt(100);path.quadTo(controlX, controlY, endX, endY);canvas.drawPath(path, paint);}iv.setImageBitmap(bitmap);
}
效果圖:
總結(jié)
以上是生活随笔為你收集整理的Android绘图(一)基础篇的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: B-Suffix Array
- 下一篇: 媒体评论苹果 Vision Pro 头显