前言
在前面的章節中我們基本完成了所有對Flutter的基礎知識講解,到目前為止通讀該專欄的讀者應已經具備Flutter常見開發場景以及各種基礎UI元件的繪製能力,但是在日常開發中業務邏輯千差萬別各種場景交替存在,這時候官方提供的各種元件就很難完全滿足複雜業務需求了,好在Flutter跟Native平臺一樣,給開發者保留了自定義VIEW的可能,開發者可以基於不同的場景,利用Flutter平臺提供的API來完成高可定製化的VIEW檢視來完成各種複雜的UI介面繪製。
課程目標
- 瞭解並掌握Flutter自定義view的原理
- 掌握Flutter自定義view的方式與具體操作流程
- 利用本章節所學知識完成一個自動變化顏色的圓形自定義view
自動變化顏色的圓形自定義view效果圖
1.Flutter自定義view原理
自定義View顧名思義就是按照自己的意圖跟想法來自己實現UI檢視
,熟悉Android開發的小夥伴都知道在Android平臺中如果要自定義view的話,需要繼承View或者ViewGroup,然後重寫onDraw方法,在onDraw中用畫筆(paint)在畫布(canvas)上繪製相應的內容,如果要觸發重繪的話則每次需呼叫invalidate通知重新整理檢視來完成。類比到Flutter平臺上,在Flutter中開發者如果想完成自定義view的操作則需要繼承CustomPainter,然後在它的paint方法中來繪製相應的內容,通過shouldRepaint的返回值來判斷是否需要重繪。
類比分析
其實關於自定義view我們完全可以類比到現實生活中的繪畫操作,我們在一張畫布或者宣紙上用畫筆畫出我們預期的圖形或者內容,只不過這裡操作的物件做了一下形式上的改變,現實生活中的畫筆等同於Flutter中的panit,畫布等同於Flutter中的canvas,具體繪畫內容等同於我們在Flutter檢視層上想要繪製出的內容,繪製五彩繽紛的內容,我們又可以通過給paint設定不同的屬性比如色值,粗細,抗鋸齒等屬性來完成。
1.1 畫筆:Paint
Paint就是我們用來繪製自定義view的基礎,我們可以通過給paint設定配置paint的屬性來繪製不同樣式的內容。 常用的屬性有
- color(設定畫筆顏色)
- strokeCap(畫筆筆觸型別)
- colorFilter(顏色渲染模式,一般是矩陣效果來改變的,但是flutter中只能使用顏色混合模式)
- style(描邊還是填充對應於PaintingStyle.stroke及PaintingStyle.fill)
- strokeWidth(畫筆的寬度)
- isAntiAlias(是否抗鋸齒)
- shader(繪製漸變色常用的LinearGradient線性漸變,SweepGradient掃描漸變,RadialGradient輻射式漸變)
其他屬性我就不逐一羅列了,感興趣的讀者可以結合程式碼自己寫程式碼驗證一下效果
1.2 畫布:Canvas
有了第一步的畫筆,那麼下邊我們就可以利用在第一步我們已經配置好的畫筆在canvas上繪製具體的圖形了,在Flutter中官方已經給我們提供好了一些列的繪製方法,如drawCircle繪製圓,drawRect繪製矩形,drawPath繪製任意路徑。在canvas裡給我們提供了大部分常用的繪製方法,方法前面均為canvas.drawxxx(xxx,paint)所示)這裡的xxx其實就是下圖中的內容:
canvas不僅僅提供大量繪製相關的方法,還提供了我們常用的平移canvas.translate,旋轉canvas.rotate,裁剪canvas.clipRect等方法。
注:在整個canvas繪製的過程中系統座標系是在左上角,向右及向下分別為x及y軸正向
1.3 繪製具體內容
在準備好了畫筆(paint)跟畫布(canvas)之後,下面我們只需在自定義View的
paint
方法中實現具體的邏輯來完成相關繪製;如下程式碼利用path繪製三角形跟利用drawCircle繪製圓的示例程式碼如下所示:
@override
void paint(Canvas canvas, Size size) {
//利用path繪製三角形
Path path = Path();
path.lineTo(100, 0);
path.lineTo(0, 100);
path.close();
canvas.drawPath(path, _paint);
//利用drawCircle圓心座標點為(100, 100),半徑為50的實心圓
// canvas.drawCircle(Offset(100, 100), 50, _paint);
}
複製程式碼
三角形
圓心座標點為(100,100),半徑為100的實心圓
1.4 處理檢視是否需要重新整理
在自定義view時,如果我們繪製的view不需要改變,或者說影像繪製成功之後在它存在的整個生命週期中都不再需要改變,這個時候我們只需要在shouldRepaint
方法中直接返回false
即可,表示當前檢視不需要重新整理處理。
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
複製程式碼
反之則需要根據業務場景,具體去改變shouldRepaint
的返回值,來通知檢視是否需要重新整理。
完整程式碼如下;
import 'package:flutter/material.dart';
/**
* desc:自定義view
* author: xiedong
* date: 2021/9/2
**/
void main() {
runApp(MaterialApp(
home: CustomTriangleView(),
));
}
class CustomTriangleView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("自定義VIEW"),
),
body: CustomPaint(
painter: TriangleView(),
),
);
}
}
class TriangleView extends CustomPainter {
var _paint;
TriangleView() {
_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; //畫筆的寬度
}
@override
void paint(Canvas canvas, Size size) {
//利用path繪製三角形
// Path path = Path();
// path.lineTo(100, 0);
// path.lineTo(0, 100);
// path.close();
// canvas.drawPath(path, _paint);
//利用drawCircle圓心座標點為(100, 100),半徑為50的實心圓
canvas.drawCircle(Offset(100, 100), 50, _paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
複製程式碼
2.繪製顏色自動變化的圓
在開篇的時候我們給本章節提出的課程要求裡提到利用所學的知識點繪製一個顏色可以自動變化的圓。那麼基於此,我們首先確定自定義VIEW的狀態需要動態改變,所以shouldRepaint
返回值應該為true
2.1. 設定shouldRepaint返回值為true
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
複製程式碼
繪製函式不變,在繪製函式中,我們還是利用上述第一部分中繪製圓的邏輯程式碼來完成,需要改變的是,我們需要藉助state來動態改變paint的顏色值。
2.2 繪製函式如下:
@override
void paint(Canvas canvas, Size size) {
//利用path繪製三角形
// Path path = Path();
// path.lineTo(100, 0);
// path.lineTo(0, 100);
// path.close();
// canvas.drawPath(path, _paint);
//利用drawCircle圓心座標點為(100, 100),半徑為50的實心圓
print('----------影像被重繪製');
canvas.drawCircle(Offset(100, 100), 50, _paint);
}
複製程式碼
2.3 利用Flutter周期函式動態修改傳入自定義View中的顏色值
@override
void initState() {
super.initState();
int count = 0;
var period = Duration(seconds: 1);
// print('currentTime='+DateTime.now().toString());
Timer.periodic(period, (timer) {
print('----顏色值改變---');
this.setState(() {
_color = _colorArr[Random().nextInt(4)];
});
});
}
複製程式碼
2.4 在自定義View的構造方法中接收通過state傳遞過來的顏色值
在自定義view的構造方法中初始化paint顏色值由第一部分的固定值,改完通過從state傳遞回來的可變值,進而通過state的變化來重新整理整個view的檢視。
AutoChangeColorCircle(_color) {
_paint = Paint()
..color = _color //畫筆顏色
..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; //畫筆的寬度
}
複製程式碼
效果如下
完整程式碼如下:
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
/**
* desc:自定義view
* author: xiedong
* date: 2021/9/2
**/
void main() {
runApp(MaterialApp(
// home: CustomTriangleView(),
home: AutoChangeColorCircleView(),
));
}
class AutoChangeColorCircleView extends StatefulWidget {
@override
State<StatefulWidget> createState() => ViewState();
}
class ViewState extends State<AutoChangeColorCircleView> {
var _colorArr = [
Colors.amberAccent,
Colors.blue,
Colors.deepOrange,
Colors.cyan,
Colors.black,
Colors.deepPurple,
];
var _color;
@override
void initState() {
super.initState();
int count = 0;
var period = Duration(seconds: 1);
// print('currentTime='+DateTime.now().toString());
Timer.periodic(period, (timer) {
print('----顏色值改變---');
this.setState(() {
_color = _colorArr[Random().nextInt(4)];
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("自定義VIEW"),
),
body: CustomPaint(
painter: AutoChangeColorCircle(_color),
),
);
}
}
class AutoChangeColorCircle extends CustomPainter {
var _paint;
AutoChangeColorCircle(_color) {
_paint = Paint()
..color = _color //畫筆顏色
..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; //畫筆的寬度
}
@override
void paint(Canvas canvas, Size size) {
//利用path繪製三角形
// Path path = Path();
// path.lineTo(100, 0);
// path.lineTo(0, 100);
// path.close();
// canvas.drawPath(path, _paint);
//利用drawCircle圓心座標點為(100, 100),半徑為50的實心圓
print('----------影像被重繪製');
canvas.drawCircle(Offset(100, 100), 50, _paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
複製程式碼
完整程式碼詳見Github Flutter入門進階之旅專欄程式碼
在Flutter中除了繼承CustomPainter重新繪製圖形來完成自定義view,在某些簡單的場景下,我們也可以利用組合Flutter現有的元件來完成自定義view,比如在上一篇博文中我們講到的 Flutter入門進階之旅 - Flutter課程表View就是通過組合Flutter中現有的元件來完成自定義view,感興趣的讀者可以從我的專欄中找到該文章,對比一下通過兩種不同的方式來完成自定義Flutter的優缺點。