Flutter 之 自定義控制元件
通過前面的學習,瞭解了常用的佈局方式和常用的子widget,可以完成大部分UI頁面的編寫,但有時候看到的UI控制元件不是這些基礎控制元件就能實現的,這時候怎麼辦呢?
和Android 和iOS 原生開發一樣,Flutter 也提供了兩種方式來實現:組合和自繪。
組合控制元件
有時候,雖然基本控制元件不能完成UI需求,但是可以通過一些基礎widget 組合成一個新的widget,來實現UI需求。
在開發中拿到一個UI頁面,一般按照從上到下、從左到右對UI進行分析,然後使用合適的widget 去編寫頁面。下面就以一個華為應用市場,應用列表的item 為例進行簡單的分析,通過組合的方式組合成一個新的widget。
以 今日頭條 為例,進行分析: 首先確定item裡面的資料有哪些,定義item 的資料結構
class UpdateItemModel {
String appIcon; //App圖示
String appName; //App名稱
String appType; //App類別
String appDecs; //App更新日期
//建構函式語法糖,為屬性賦值
UpdateItemModel({this.appIcon, this.appName, this.appType, this.appDecs});
}
複製程式碼
首先:分為左右兩部分,可以使用Row,左邊是一張圖片使用Image,但圖片是圓角的,但普通的 Image 並不支援圓角。這時,我們可以使用 ClipRRect 控制元件來解決這個問題;右邊就比較複雜了,這裡先定義為widget1,
widget1: 可以分上、下兩個部分,可以使用Column,下面是一條分隔線,可以使用 Divider,但看到有邊距,需要再包裹一層Padding;上面部分比較複雜,定義為widget2;
widget2: 又可以分為左右兩部分,可以使用Row,右邊是一個 FlatButton,左邊部分比較複習,定義為widget3;
widget3: 豎直方向上放置的幾個Text,可以使用Column;到這來就分析完成了。下面就看看程式碼和實際的執行效果吧。
分為 上下兩個部分,下面部分是一個分隔線,上面又可以繼續劃分
水平排放,可以使用Row完成,左邊是Column裡面放置幾個Text,右邊是一個 FlatButton
下面看看具體程式碼:
class CustomDemo1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("自定義控制元件"),
),
body: UpdateWidget(
model: UpdateItemModel(
appName: "今日頭條",
appIcon:
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Foss.huangye88.net%2Flive%2Fuser%2F0%2F1502268284008709600-0.png&refer=http%3A%2F%2Foss.huangye88.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618804524&t=3b2eff4563a5421ed62be433277abe06",
appType: "新聞",
appDecs: "海量視訊熱點資訊高效搜尋"),
onPressed: () {
print("安裝今日頭條");
},
),
);
}
}
class UpdateWidget extends StatelessWidget {
final UpdateItemModel model; //資料模型
final VoidCallback onPressed;
UpdateWidget({Key key, this.model, this.onPressed}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(10),
child: ClipRect(
child: Image.network(
model.appIcon,
width: 80,
height: 80,
),
),
),
Padding(
padding: EdgeInsets.fromLTRB(0, 10, 10, 0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
model.appName,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: Colors.black),
),
Text(
model.appType,
style: TextStyle(fontSize: 14, color: Colors.black26),
),
Text(
model.appDecs,
style: TextStyle(fontSize: 14, color: Colors.black26),
),
],
),
Container(
padding: EdgeInsets.fromLTRB(30, 0, 0, 0),
alignment: Alignment.topRight,
child: MaterialButton(
onPressed: this.onPressed,
textColor: Colors.blue,
color: Colors.grey,
minWidth: 30,
height: 25,
child: Text(
"安裝",
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
)),
)
],
),
],
),
)
],
);
}
}
複製程式碼
自繪控制元件
在原生 iOS 和 Android 開發中,我們可以繼承 UIView/View,在 drawRect/onDraw 方法裡進行繪製操作。其實,在 Flutter 中也有類似的方案,那就是 CustomPaint。
我們都知道在繪製的過程中有兩個重要的東西--畫布和畫筆,畫筆 Paint,我們可以配置它的各種屬性,比如顏色、樣式、粗細等;Canvas,則提供了各種常見的繪製方法,比如畫線 drawLine、畫矩形 drawRect、畫點 DrawPoint、畫路徑 drawPath、畫圓 drawCircle、畫圓弧 drawArc 等。
一般繪製的流程分為三個部分:
- 自定義class 繼承 CustomPainter
- 通過 CustomPaint,把自定義的控制元件放到widget中
- 像使用正常的widget 一樣使用 新的widget
下面看看一個餅狀圖的例子:
// 1.繼承 CustomPainter,在裡面編寫繪製邏輯
class CustomWidget extends CustomPainter {
// 生成畫筆
Paint getPaintByColor(Color color) {
Paint paint = Paint();
paint.color = color;
return paint;
}
@override
void paint(Canvas canvas, Size size) {
// 這裡面是繪製的邏輯
double wheelSize = min(size.width, size.height)/2;
double nbElem = 6; // 分為6份
// 繪製的圓弧
double radius = (2 * pi) / nbElem;
// 建立一個矩形
Rect boundingRect = Rect.fromCircle(center: Offset(wheelSize, wheelSize),radius: wheelSize);
// 繪製扇形
canvas.drawArc(
boundingRect, 0, radius, true, getPaintByColor(Colors.blueGrey));
canvas.drawArc(
boundingRect, radius * 1, radius, true, getPaintByColor(Colors.red));
canvas.drawArc(
boundingRect, radius * 2, radius, true, getPaintByColor(Colors.green));
canvas.drawArc(
boundingRect, radius * 3, radius, true, getPaintByColor(Colors.blue));
canvas.drawArc(
boundingRect, radius * 4, radius, true, getPaintByColor(Colors.brown));
canvas.drawArc(
boundingRect, radius * 5, radius, true, getPaintByColor(Colors.amber));
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return oldDelegate != this;
}
}
//2. 將餅圖包裝成一個新的控制元件,通過 CustomPaint
class Cake extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomPaint(
size: Size(200, 200),
painter: CustomWidget(),
);
}
}
// 就可以像使用普通控制元件一樣使用新定義的控制元件
class CustomDemo2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("繪製控制元件"),
),
body: Center(
child: Cake(),
),
);
}
}
複製程式碼
總結
在Flutter 中,自定義控制元件的方式有兩種,組合和自繪。組合的方式是通過一些基本widget 元素的堆積,組合成一個新的控制元件;自繪則是會比較麻煩一點,通過 繼承 CustomPainter
在 paint 方法中完成繪製邏輯;最後把 CustomPainter
放入到 CustomPaint
成為一個新控制元件。
還記得剛開始學習 Android 的自定義控制元件,總是很排斥,但發現認真去學習之後,還是很簡單的。Flutter 也是一樣,通過學習是可以快速提高的。一起努力加油吧!