1. 程式人生 > >flutter自定義View(CustomPainter) 之 canvas的方法總結

flutter自定義View(CustomPainter) 之 canvas的方法總結

前有大佬分享了用CustomPaint畫一個自定義的CircleProgressBar的文章, 今天我分享一波自定義View(CustomPaint)的一些基礎知識

畫布canvas

畫布是一個矩形區域,我們可以控制其每一畫素來繪製我們想要的內容

canvas 擁有多種繪製點、線、路徑、矩形、圓形、以及新增影象的方法,結合這些方法我們可以繪製出千變萬化的畫面。

雖然,畫布可以畫這些東西,但是決定這些圖形顏色、粗細表現的還是畫筆。

畫筆Paint

Paint非常好理解,就是我們用來畫圖形的工具,我們可以設定畫筆的顏色、粗細、是否抗鋸齒、筆觸形狀以及作畫風格。

通過這些屬性我們可以很方便的來定製自己的UI效果,當然我們在“作畫”的過程中可以定義多個畫筆,這樣更方便我們對圖形的繪製

畫筆Paint的屬性

canvas中有多個與繪製相關的方法,如drawLine()、drawRect()、drawOval()、drawOval()、等方法。

但是,僅僅使用canvas這個畫布還不夠,我們還需要一個畫筆paint,我們可以使用如下程式碼來構建paint

Paint _paint = Paint()
    ..color = Colors.blueAccent //畫筆顏色
    ..strokeCap = StrokeCap.round //畫筆筆觸型別
    ..isAntiAlias = true //是否啟動抗鋸齒
    ..blendMode = BlendMode.exclusion //顏色混合模式
    ..style = PaintingStyle.fill //繪畫風格,預設為填充
    ..colorFilter = ColorFilter.mode(Colors.blueAccent,
        BlendMode.exclusion) //顏色渲染模式,一般是矩陣效果來改變的,但是flutter中只能使用顏色混合模式
    ..maskFilter = MaskFilter.blur(BlurStyle.inner, 3.0) //模糊遮罩效果,flutter中只有這個
    ..filterQuality = FilterQuality.high //顏色渲染模式的質量
    ..strokeWidth = 15.0; //畫筆的寬度
複製程式碼

當然,在正常的開發中一般不會使用這麼多的屬性,大家可以根據需要去具體的瞭解和使用。

畫布canvas的方法

以下內容基於此畫筆繪製:

Paint _paint = new Paint()
    ..color = Colors.blueAccent
    ..strokeCap = StrokeCap.round
    ..isAntiAlias = true
    ..strokeWidth = 5.0
    ..style = PaintingStyle.stroke;
複製程式碼

繪製直線

void drawLine(Offset p1, Offset p2, Paint paint)

使用給定的塗料在給定點之間繪製一條線。 該行被描邊,此呼叫忽略[Paint.style]的值。 p1p2引數為兩個點的座標 , 在這兩點之間繪製一條直線。

eg : canvas.drawLine(Offset(20.0, 20.0), Offset(100.0, 100.0), _paint)
複製程式碼

在這裡插入圖片描述

繪製點drawPoints

void drawPoints(PointMode pointMode, List points, Paint paint)

繪製點也是非常的簡單,3個引數分別為: PointMode列舉,座標 list 和 paint PointMode的列舉型別有三個,points(點),lines(線,隔點連線),polygon(線,相鄰連線)

canvas.drawPoints(
        ///PointMode的列舉型別有三個,points(點),lines(線,隔點連線),polygon(線,相鄰連線)
        PointMode.points,
        [
          Offset(20.0, 130.0),
          Offset(100.0, 210.0),
          Offset(100.0, 310.0),
          Offset(200.0, 310.0),
          Offset(200.0, 210.0),
          Offset(280.0, 130.0),
          Offset(20.0, 130.0),
        ],
        _paint..color = Colors.redAccent);
複製程式碼

為了方便演示,我們在上面定義了7個點,第一個和最後一個點重合。

然後我們設定PointMode為points看下效果。

在這裡插入圖片描述

然後我們把PointMode改為lines

PointMode.lines

PointMode為lines時,兩個點相互連線,也就是說第一個和第二個點連線,第三個跟第四個連線,如果最後只有一個點就捨棄不連線了,在我們的例子中有7個點,所以圖中只有三條連線。

然後我們把PointMode改為lines

在這裡插入圖片描述

對,你看的沒有錯跟上面繪製線段的效果是一樣的,相鄰點互相連線。

繪製圓rawCircle

void drawCircle(Offset c, double radius, Paint paint)

引數分別為:圓心的座標、半徑和paint即可。 圓形是否填充或描邊(或兩者)由Paint.style控制。

//繪製圓 引數(圓心,半徑,畫筆)
     canvas.drawCircle(
        Offset(100.0, 350.0),
        30.0,
        _paint
          ..color = Colors.greenAccent
          ..style = PaintingStyle.stroke //繪畫風格改為stroke
        );
複製程式碼

在這裡插入圖片描述

在這裡我將畫筆Paint的style改成了stroke 然後我們將畫筆style改成fill (填充) ,fill也是畫筆的style的預設值.

在這裡插入圖片描述
填充之後,這個圓就變成實心的了.

繪製橢圓drawOval

void drawOval(Rect rect, Paint paint)

繪製一個軸對稱的橢圓形 引數為一個矩形和畫筆paint.

//使用左上和右下角座標來確定矩形的大小和位置,橢圓是在這個矩形之中內切的
    Rect rect1 = Rect.fromPoints(Offset(150.0, 200.0), Offset(300.0, 250.0));
    canvas.drawOval(rect1, _paint);
複製程式碼

在這裡插入圖片描述

在前面我們已經講過了使用Rect便可確認這個矩形的大小和位置。

其實,Rect也有多種構建方式:

fromPoints(Offset a, Offset b)
使用左上和右下角座標來確定矩形的大小和位置

fromCircle({ Offset center, double radius })
使用圓的圓心點座標和半徑和確定外切矩形的大小和位置

fromLTRB(double left, double top, double right, double bottom)
使用矩形左邊的X座標、矩形頂部的Y座標、矩形右邊的X座標、矩形底部的Y座標來確定矩形的大小和位置

fromLTWH(double left, double top, double width, double height)
使用矩形左邊的X座標、矩形頂部的Y座標矩形的寬高來確定矩形的大小和位置
複製程式碼

繪製圓弧drawArc

void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)

首先還是需要Rect來確認圓弧的位置,還需要開始的弧度、結束的弧度、是否使用中心點繪製(圓弧是否向中心閉合)、以及paint.

弧度

根據定義,一週的弧度數為2πr/r=2π,360°角=2π弧度,因此,1弧度約為57.3°,即57°17’44.806’’,1°為π/180弧度,近似值為0.01745弧度,周角為2π弧度,平角(即180°角)為π弧度,直角為π/2弧度。

特殊的弧度:
弧度
0
30° π/6
45° π/4
60° π/3
90° π/2
120° 2π/3
180° π
270° 3π/2
360°
 //繪製圓弧
    // Rect來確認圓弧的位置,還需要開始的弧度、結束的弧度、是否使用中心點繪製、以及paint弧度
    Rect rect2 = Rect.fromCircle(center: Offset(200.0, 50.0), radius: 80.0);
    canvas.drawArc(rect2, 0.0, 0.8, false, _paint);

複製程式碼

在這裡插入圖片描述
繪製個90度的弧度

const PI = 3.1415926;
    Rect rect2 = Rect.fromCircle(center: Offset(200.0, 50.0), radius: 80.0);
    canvas.drawArc(rect2, 0.0, PI / 2, false, _paint);
複製程式碼

定義π為3.1415926,定義開始的角度為0°掃過的角度為PI / 2(90°),設定userCenter為false

在這裡插入圖片描述
將useCenter改成true 試試:
在這裡插入圖片描述
發現圓弧向中心點閉合了.

繪製圓角矩形drawDRRect

void drawRRect(RRect rrect, Paint paint)

使用RRect確定矩形大小及弧度,使用paint來完成繪製。

RRect構建起來也非常的方便,直接使用fromRectAndRadius即可

RRect.fromRectAndRadius(rect, radius)

rect依然用來表示矩形的位置和大小,radius用來表示圓角的大小。

    //用Rect構建一個邊長50,中心點座標為100,100的矩形
    Rect rect = Rect.fromCircle(center: Offset(100.0, 150.0), radius: 50.0);
    //根據上面的矩形,構建一個圓角矩形
    RRect rrect = RRect.fromRectAndRadius(rect, Radius.circular(20.0));
    canvas.drawRRect(rrect, _paint);
複製程式碼

在這裡插入圖片描述

將圓角的半徑設定為邊長(從20改成50)試一下:

在這裡插入圖片描述
就變成了圓.

繪製雙圓角矩形drawRRect

void drawDRRect(RRect outer, RRect inner, Paint paint)

和drawRRect類似,使用RRect確定內部、外部矩形大小及弧度,使用paint來完成繪製。

    //繪製兩個矩形
    Rect rect1 = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 60.0);
    Rect rect2 = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 40.0);

    //分別繪製外部圓角矩形和內部的圓角矩形
    RRect outer = RRect.fromRectAndRadius(rect1, Radius.circular(10.0));
    RRect inner = RRect.fromRectAndRadius(rect2, Radius.circular(10.0));
    canvas.drawDRRect(outer, inner, _paint);
複製程式碼

使用Rect.fromCircle來建立Rect,使用RRect.fromRectAndRadius來建立RRect

在這裡插入圖片描述

可以看到兩個圓角矩形,當然我們也可以嘗試調整角度的度數大小。

   //繪製兩個矩形
    Rect rect1 = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 60.0);
    Rect rect2 = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 40.0);

    //分別繪製外部圓角矩形和內部的圓角矩形
    RRect outer = RRect.fromRectAndRadius(rect1, Radius.circular(30.0));
    RRect inner = RRect.fromRectAndRadius(rect2, Radius.circular(5.0));
    canvas.drawDRRect(outer, inner, _paint);
複製程式碼

在這裡插入圖片描述

你甚至可以調整角度的大小使兩個矩形都變成圓來形成一個圓環.

繪製路徑drawPath

void drawPath(Path path, Paint paint)

繪製路徑,首先需要一個要繪製的路徑path,然後就是這個paint了。

Path的常用方法:

方法名 作用
moveTo 將路徑起始點移動到指定的位置
relativeMoveTo 相對於當前位置移動到
lineTo 從當前位置連線指定點
relativeLineTo 相對當前位置連線到
arcTo 二階貝塞爾曲線
conicTo 三階貝塞爾曲線
add** 新增其他圖形,如addArc,在路徑是新增圓弧
contains 路徑上是否包括某點
transfor 給路徑做matrix4變換
combine 結合兩個路徑
close 關閉路徑,連線路徑的起始點
reset 重置路徑,恢復到預設狀態

eg:

//新建了一個path,然後將路徑起始點移動到座標(100,100)的位置
    Path path = new Path()..moveTo(100.0, 100.0);

    path.lineTo(200.0, 200.0);

    canvas.drawPath(path, _paint);
複製程式碼

首先新建了一個path,然後將路徑起始點移動到座標(100,100)的位置, 然後從這個位置連線(200,200)的點.

在這裡插入圖片描述

我們也可以繪製多個路徑:

    Path path = new Path()..moveTo(100.0, 100.0);

    path.lineTo(200.0, 200.0);
    path.lineTo(100.0, 300.0);
    path.lineTo(150.0, 350.0);
    path.lineTo(150.0, 500.0);
    
    canvas.drawPath(path, _paint);
複製程式碼

在這裡插入圖片描述

使用二階貝塞爾曲線繪製弧線:

void arcTo(Rect rect, double startAngle, double sweepAngle, bool forceMoveTo)

rect我們都知道了,是一個矩形,startAngle是開始的弧度,sweepAngle是結束的弧度 重點介紹一下forceMoveTo. forceMoveTo:

  • 如果“forceMoveTo”引數為false,則新增一條直線段和一條弧段。
  • 如果“forceMoveTo”引數為true,則啟動一個新的子路徑,其中包含一個弧段。

例如:

Path path = new Path()..moveTo(100.0, 100.0);
    Rect rect = Rect.fromCircle(center: Offset(200.0, 200.0), radius: 60.0);
    path.arcTo(rect, 0.0, 3.14, false);
    canvas.drawPath(path, _paint);
複製程式碼

在這裡插入圖片描述
這裡,我們利用貝塞爾曲線繪製了一個半圓,因為起始點的座標是(100,100),而我們繪製貝塞爾曲線的時候,曲線的原點是(200,200)半徑,60,所以我們移動到(200,260)的位置再畫這個曲線.

因為forceMoveTo此時為false,所以從起始點到曲線的起始點畫出了直線路徑, 改為true可以看到,因為啟動了一個新的子路徑,所以那條線段沒有了:

在這裡插入圖片描述

當然,你甚至可以用貝塞爾曲線直接畫一個圓:

    Rect rect = Rect.fromCircle(center: Offset(200.0, 200.0), radius: 60.0);

    path.arcTo(rect, 0.0, 3.14*2, false);

    canvas.drawPath(path, _paint);
複製程式碼

在這裡插入圖片描述

使用三階貝塞爾曲線繪製❤:

void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3)

    var width = 200;
    var height = 300;
    path.moveTo(width / 2, height / 4);
    path.cubicTo((width * 6) / 7, height / 9, (width * 13) / 13,
        (height * 2) / 5, width / 2, (height * 7) / 12);
    canvas.drawPath(path, _paint);

    Path path2 = new Path();
    path2.moveTo(width / 2, height / 4);
    path2.cubicTo(width / 7, height / 9, width / 21, (height * 2) / 5,
        width / 2, (height * 7) / 12);
    canvas.drawPath(path2, _paint);
複製程式碼

看一下效果:

在這裡插入圖片描述

然後我們改變paint的樣式:

 canvas.drawPath(path, _paint); 
 替換為:
 canvas.drawPath(
        path,
        _paint
          ..style = PaintingStyle.fill
          ..color = Colors.red);
複製程式碼

我們將畫筆的顏色改成紅色,樣式改為填充:

)

繪製顏色drawColor

void drawColor(Color color, BlendMode blendMode)

我們先繪製一個圓:

canvas.drawCircle(Offset(100.0, 100.0), 50.0, _paint);
複製程式碼

在這裡插入圖片描述
然後我們新增一行程式碼:

    canvas.drawCircle(Offset(100.0, 100.0), 50.0, _paint);
    canvas.drawColor(Colors.red, BlendMode.color);  // 新增這行
複製程式碼

在這裡插入圖片描述

可以看到,圓的顏色變成了紅色, 我們還可以改變BlendMode, 例如:BlendMode.colorDodge

在這裡插入圖片描述
更多效果可以查詢BlendMode原始碼.

繪製圖片drawImage

void drawImage(Image image, Offset p, Paint paint)

將給定的[image]以其左上角的[偏移量]繪製到畫布中 首先我們需要獲取本地圖片檔案,然後繪製圖片即可

全文相關程式碼已提交到 github