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 也是一样,通过学习是可以快速提高的。一起努力加油吧!