目錄
1. 進度指示器(LinearProgressIndicator燥狰、CircularProgressIndicator)
2. 日期組件
3. 寬高比組件AspectRatio
4. 卡片組件Card
5. 剪裁
6. 標簽 Chip、ActionChip、FilterChip、ChoiceChip
7. 表格 DataTable、PaginatedDataTable
8. 分割線Divider
9. ListTile
10. ButtonBarTheme
11. Material
Spacer
Visibility
IndexedStack
CircleAvatar
1. 進度指示器
LinearProgressIndicator户辞、CircularProgressIndicator
精確進度指示(可計算/預估疟羹,如:文件下載)碳柱、模糊進度指示(如:下拉刷新构哺、數(shù)據提交)。
沒提供尺寸參數(shù)(以父容器尺寸作為繪制邊界)战坤,可通過ConstrainedBox曙强、SizedBox等尺寸限制類組件來指定尺寸。
內部是通過CustomPainter來實現(xiàn)外觀繪制的途茫。
自定義指示器(通過CustomPainter自定義繪制)
- LinearProgressIndicator(線性/條狀進度條)
LinearProgressIndicator({
Key key,
double value, // 當前進度碟嘴,取值范圍為[0,1],如果為null則會執(zhí)行一個循環(huán)動畫(模糊進度)囊卜。
Color backgroundColor, // 背景色
Animation<Color> valueColor, // 進度條顏色,可以指定動畫,也可以AlwaysStoppedAnimation指定固定顏色
String semanticsLabel,
String semanticsValue,
})
示例
// 模糊進度條(會執(zhí)行一個循環(huán)動畫:藍色條一直在移動)
LinearProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
),
// 進度條顯示50%
LinearProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
value: .5,
)
示例(一個進度條在3秒內從灰色變成藍色的動畫)
import 'package:flutter/material.dart';
class ProgressRoute extends StatefulWidget {
@override
_ProgressRouteState createState() => _ProgressRouteState();
}
class _ProgressRouteState extends State<ProgressRoute>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
@override
void initState() {
// 動畫執(zhí)行時間3秒
_animationController =
new AnimationController(vsync: this, duration: Duration(seconds: 3));
_animationController.forward();
_animationController.addListener(() => setState(() => {}));
super.initState();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.all(16),
child: LinearProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: ColorTween(begin: Colors.grey, end: Colors.blue)
.animate(_animationController), // 從灰色變成藍色
value: _animationController.value,
),
);
],
),
);
}
}
- CircularProgressIndicator(圓形進度條)
CircularProgressIndicator({
Key key,
double value,
Color backgroundColor,
Animation<Color> valueColor,
this.strokeWidth = 4.0, // 圓形進度條的粗細
String semanticsLabel,
String semanticsValue,
})
示例
// 模糊進度條(會執(zhí)行一個旋轉動畫)
CircularProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
),
// 進度條顯示50%娜扇,會顯示一個半圓
CircularProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
value: .5,
),
示例(自定義尺寸)
// 線性進度條高度指定為3
SizedBox(
height: 3,
child: LinearProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
value: .5,
),
),
// 圓形進度條直徑指定為100。如果CircularProgressIndicator顯示空間的寬高不同栅组,則會顯示為橢圓雀瓢。
SizedBox(
height: 100,
width: 100,
child: CircularProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
value: .7,
),
),
- 自定義指示器(通過CustomPainter自定義繪制)
- flutter_spinkit三方庫
示例(風車)
import 'dart:math';
import 'package:flutter/material.dart';
class WindmillIndicator extends StatefulWidget {
final double size; // 大小
final double speed; // 轉速,默認2秒轉一圈玉掸。
final bool isClockwise; // 是否順時針
WindmillIndicator({
Key key,
this.size = 50.0,
this.speed = 0.5,
this.isClockwise = true,
}) : assert(speed > 0),
assert(size > 20),
super(key: key);
@override
_WindmillIndicatorState createState() => _WindmillIndicatorState();
}
class _WindmillIndicatorState extends State<WindmillIndicator>
with SingleTickerProviderStateMixin {
AnimationController controller;
@override
void initState() {
super.initState();
int milliseconds = 1000 ~/ widget.speed;
controller = AnimationController(
duration: Duration(milliseconds: milliseconds), vsync: this);
controller.repeat();
}
@override
Widget build(BuildContext context) {
return AnimatedWindmill(
animation: controller,
size: widget.size,
isClockwise: widget.isClockwise,
);
}
@override
void dispose() {
if (controller.status != AnimationStatus.completed &&
controller.status != AnimationStatus.dismissed) {
controller.stop();
}
controller.dispose();
super.dispose();
}
}
// 組裝4個葉片
class AnimatedWindmill extends AnimatedWidget {
final double size;
final bool isClockwise;
AnimatedWindmill({
Key key,
@required Animation<double> animation,
this.isClockwise = true,
this.size = 50.0,
}) : super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
final rotationAngle = isClockwise
? 2 * pi * animation.value
: -2 * pi * animation.value;
return Stack(
alignment: Alignment.topCenter,
children: [
WindmillWing(
size: size,
color: Colors.blue,
angle: 0 + rotationAngle,
),
WindmillWing(
size: size,
color: Colors.yellow,
angle: pi / 2 + rotationAngle,
),
WindmillWing(
size: size,
color: Colors.green,
angle: pi + rotationAngle,
),
WindmillWing(
size: size,
color: Colors.red,
angle: -pi / 2 + rotationAngle,
),
],
);
}
}
// 單個葉片
class WindmillWing extends StatelessWidget {
final double size; // 葉片所占據的正方形區(qū)域的邊長
final Color color; // 葉片顏色
final double angle; // 葉片旋轉角度
const WindmillWing({
Key key,
@required this.size,
@required this.color,
@required this.angle,
});
@override
Widget build(BuildContext context) {
return Container(
transformAlignment: Alignment.bottomCenter,
// 旋轉后風車會下移刃麸,這里向上補償size / 2
transform: Matrix4.translationValues(0, -size / 2, 0)..rotateZ(angle),
// 將正方形剪裁成葉片
child: ClipPath(
child: Container(
width: size,
height: size,
alignment: Alignment.center,
color: color,
),
clipper: WindwillClipPath(),
),
);
}
}
class WindwillClipPath extends CustomClipper<Path> {
@override
Path getClip(Size size) {
// 2弧線閉合
var path = Path()
..moveTo(size.width / 3, size.height)
..arcToPoint(
Offset(0, size.height * 2 / 3),
radius: Radius.circular(size.width / 2),
)
..arcToPoint(
Offset(size.width, 0),
radius: Radius.circular(size.width),
)
..lineTo(size.width / 3, size.height);
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
示例(在圓環(huán)內滾動的小球)
// 動畫控制設置
controller =
AnimationController(duration: const Duration(seconds: 3), vsync: this);
animation = Tween<double>(begin: 0, end: 1.0).animate(CurvedAnimation(
parent: controller,
curve: Curves.slowMiddle,
))
..addListener(() {
setState(() {});
});
// 繪制和動畫控制方法
_drawLoadingCircle(Canvas canvas, Size size) {
var paint = Paint()..style = PaintingStyle.stroke
..color = Colors.blue[400]!
..strokeWidth = 2.0;
var path = Path();
final radius = 40.0;
var center = Offset(size.width / 2, size.height / 2);
path.addOval(Rect.fromCircle(center: center, radius: radius));
canvas.drawPath(path, paint);
var innerPath = Path();
final ballRadius = 4.0;
innerPath.addOval(Rect.fromCircle(center: center, radius: radius - ballRadius));
var metrics = innerPath.computeMetrics();
paint.color = Colors.red;
paint.style = PaintingStyle.fill;
for (var pathMetric in metrics) {
var tangent = pathMetric.getTangentForOffset(pathMetric.length * animationValue);
canvas.drawCircle(tangent!.position, ballRadius, paint);
}
}
示例(兩橫縱向圓環(huán)上滾動的小球)
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end: 1.0).animate(CurvedAnimation(
parent: controller,
curve: Curves.easeInOutSine,
))
..addListener(() {
setState(() {});
});
_drawTwinsCircle(Canvas canvas, Size size) {
var paint = Paint()
..style = PaintingStyle.stroke
..color = Colors.blue[400]!
..strokeWidth = 2.0;
final radius = 50.0;
final ballRadius = 6.0;
var center = Offset(size.width / 2, size.height / 2);
var circlePath = Path()
..addOval(Rect.fromCircle(center: center, radius: radius));
paint.style = PaintingStyle.stroke;
paint.color = Colors.blue[400]!;
canvas.drawPath(circlePath, paint);
var circleMetrics = circlePath.computeMetrics();
for (var pathMetric in circleMetrics) {
var tangent = pathMetric
.getTangentForOffset(pathMetric.length * animationValue);
paint.style = PaintingStyle.fill;
paint.color = Colors.blue;
canvas.drawCircle(tangent!.position, ballRadius, paint);
}
paint.style = PaintingStyle.stroke;
paint.color = Colors.green[600]!;
var ovalPath = Path()
..addOval(Rect.fromCenter(center: center, width: 3 * radius, height: 40));
canvas.drawPath(ovalPath, paint);
var ovalMetrics = ovalPath.computeMetrics();
for (var pathMetric in ovalMetrics) {
var tangent =
pathMetric.getTangentForOffset(pathMetric.length * animationValue);
paint.style = PaintingStyle.fill;
canvas.drawCircle(tangent!.position, ballRadius, paint);
}
}
示例(鐘擺運動)
1. 繪制頂部的橫線,代表懸掛的頂點司浪;
2. 繪制運動的圓弧路徑泊业,以便讓球沿著圓弧運動;
3. 繪制實心圓代表球啊易,并通過動畫控制沿著一條圓弧運動吁伺;
4. 用一條頂端固定,末端指向球心的直線代表繩子租谈;
5. 當球運動到弧線的終點后篮奄,通過動畫反轉(reverse)控制球 返回;到起點后再正向(forward) 運動就可以實現(xiàn)來回運動的效果了。
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end: 1.0).animate(CurvedAnimation(
parent: controller,
curve: Curves.easeInOutQuart,
))
..addListener(() {
setState(() {});
}
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
_drawPendulum(Canvas canvas, Size size) {
var paint = Paint()
..style = PaintingStyle.stroke
..color = Colors.blue[400]!
..strokeWidth = 2.0;
final ceilWidth = 60.0;
final pendulumHeight = 200.0;
var ceilCenter =
Offset(size.width / 2, size.height / 2 - pendulumHeight / 2);
var ceilPath = Path()
..moveTo(ceilCenter.dx - ceilWidth / 2, ceilCenter.dy)
..lineTo(ceilCenter.dx + ceilWidth / 2, ceilCenter.dy);
canvas.drawPath(ceilPath, paint);
var pendulumArcPath = Path()
..addArc(Rect.fromCircle(center: ceilCenter, radius: pendulumHeight),
3 * pi / 4, -pi / 2);
paint.color = Colors.white70;
var metrics = pendulumArcPath.computeMetrics();
for (var pathMetric in metrics) {
var tangent =
pathMetric.getTangentForOffset(pathMetric.length * animationValue);
canvas.drawLine(ceilCenter, tangent!.position, paint);
paint.style = PaintingStyle.fill;
paint.color = Colors.blue;
paint.maskFilter = MaskFilter.blur(BlurStyle.solid, 4.0);
canvas.drawCircle(tangent.position, 16.0, paint);
}
}
示例(一個有趣的Loading組件)
參數(shù)
1. 前景色:繪制圖形的前景色宦搬;
2. 背景色:繪制圖形的背景色牙瓢;
3. 圖形尺寸:繪制圖形的尺寸;
4. 加載文字:可選间校,有就顯示矾克,沒有就不顯示。
class LoadingAnimations extends StatefulWidget {
final Color bgColor;
final Color foregroundColor;
String? loadingText;
final double size;
LoadingAnimations(
{required this.foregroundColor,
required this.bgColor,
this.loadingText,
this.size = 100.0,
Key? key})
: super(key: key);
@override
_LoadingAnimationsState createState() => _LoadingAnimationsState();
}
圓形Loading
多個沿著大圓運動的實心圓憔足,半徑依次減小胁附,實心圓的間距隨著動畫時間逐步拉大。
_drawCircleLoadingAnimaion(
Canvas canvas, Size size, Offset center, Paint paint) {
final radius = boxSize / 2;
final ballCount = 6;
final ballRadius = boxSize / 15;
var circlePath = Path()
..addOval(Rect.fromCircle(center: center, radius: radius));
var circleMetrics = circlePath.computeMetrics();
for (var pathMetric in circleMetrics) {
for (var i = 0; i < ballCount; ++i) {
var lengthRatio = animationValue * (1 - i / ballCount);
var tangent =
pathMetric.getTangentForOffset(pathMetric.length * lengthRatio);
var ballPosition = tangent!.position;
canvas.drawCircle(ballPosition, ballRadius / (1 + i), paint);
canvas.drawCircle(
Offset(size.width - tangent.position.dx,
size.height - tangent.position.dy),
ballRadius / (1 + i),
paint);
}
}
}
橢圓運動Loading(漸變效果)
final ballCount = 6;
final ballRadius = boxSize / 15;
var ovalPath = Path()
..addOval(Rect.fromCenter(
center: center, width: boxSize, height: boxSize / 1.5));
paint.shader = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [this.foregroundColor, this.bgColor],
).createShader(Offset.zero & size);
var ovalMetrics = ovalPath.computeMetrics();
for (var pathMetric in ovalMetrics) {
for (var i = 0; i < ballCount; ++i) {
var lengthRatio = animationValue * (1 - i / ballCount);
var tangent =
pathMetric.getTangentForOffset(pathMetric.length * lengthRatio);
var ballPosition = tangent!.position;
canvas.drawCircle(ballPosition, ballRadius / (1 + i), paint);
canvas.drawCircle(
Offset(size.width - tangent.position.dx,
size.height - tangent.position.dy),
ballRadius / (1 + i),
paint);
}
}
貝塞爾曲線Loading
首先是構建貝塞爾曲線Path
var bezierPath = Path()
..moveTo(size.width / 2 - boxSize / 2, center.dy)
..quadraticBezierTo(size.width / 2 - boxSize / 4, center.dy - boxSize / 4,
size.width / 2, center.dy)
..quadraticBezierTo(size.width / 2 + boxSize / 4, center.dy + boxSize / 4,
size.width / 2 + boxSize / 2, center.dy)
..quadraticBezierTo(size.width / 2 + boxSize / 4, center.dy - boxSize / 4,
size.width / 2, center.dy)
..quadraticBezierTo(size.width / 2 - boxSize / 4, center.dy + boxSize / 4,
size.width / 2 - boxSize / 2, center.dy);
實心圓
var ovalMetrics = bezierPath.computeMetrics();
for (var pathMetric in ovalMetrics) {
for (var i = 0; i < ballCount; ++i) {
var lengthRatio = animationValue * (1 - i / ballCount);
var tangent =
pathMetric.getTangentForOffset(pathMetric.length * lengthRatio);
var ballPosition = tangent!.position;
canvas.drawCircle(ballPosition, ballRadius / (1 + i), paint);
canvas.drawCircle(
Offset(size.width - tangent.position.dx,
size.height - tangent.position.dy),
ballRadius / (1 + i),
paint);
}
}
/*
改變運動方向
var lengthRatio = animationValue * (1 - i / ballCount);
var tangent =
pathMetric.getTangentForOffset(pathMetric.length * lengthRatio);
var ballPosition = tangent!.position;
canvas.drawCircle(ballPosition, ballRadius / (1 + i), paint);
canvas.drawCircle(Offset(tangent.position.dy, tangent.position.dx),
ballRadius / (1 + i), paint);
*/
使用
class _LoadingDemoState extends State<LoadingDemo> {
var loaded = false;
@override
void initState() {
super.initState();
Future.delayed(Duration(seconds: 5), () {
setState(() {
loaded = true;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Loading 使用'),
),
body: Center(
child: loaded
? Image.asset(
'images/beauty.jpeg',
width: 100.0,
)
: LoadingAnimations(
foregroundColor: Colors.blue,
bgColor: Colors.white,
size: 100.0,
),
),
);
}
2. 日期組件
var now=new DateTime.now(); // 當前時間滓彰。2050-01-01 00:00:01.001
var time=now.millisecondsSinceEpoch; // 13位時間戳控妻,ms。日期轉時間戳
var date=DateTime.fromMillisecondsSinceEpoch(time); // 時間戳轉日期揭绑,2050-01-01 00:00:01.001
intl 國際化包
DateFormat.yMMMd().format(DateTime.now())
date_format 三方庫(日期格式化)
// 2050年01月01
formatDate(DateTime.now(),[yyyy,'年',mm,'月',dd]);
Android
iOS
日歷組件弓候、時間組件
日歷組件
var _datetime=DateTime.now();
_showDatePicker() async{ // 返回Future<void>
var date=await showDatePicker(
context: context,
initialDate: _datetime, // 當前日期(初始日期)
firstDate: DateTime(1900), // 最早日期
lastDate: DateTime(2100), // 最晚日期。_datetime.add(Duration(days: 30),), // 未來30天可選
// locale: Locale('zh'), // 當前環(huán)境不是中文時他匪,強制使用中文菇存。需要首先國家化Material組件。
); // 選擇完后才會繼續(xù)向下執(zhí)行
if(!date)return;
print(date);
setState(() {
_datetime=date;
});
}
時間組件
var _nowTIme=TimeOfDay(hour: 8,minute: 0);
_showTimePicker() async{
var time=await showTimePicker(
context: context,
initialTime: _nowTIme, // 當前日期(初始日期)
);
if(!time)return;
print(time);
setState(() {
_nowTIme=time;
});
}
// _nowTIme.format(context)
iOS風格的日歷選擇器需要使用showCupertinoModalPopup方法和CupertinoDatePicker組件來實現(xiàn):
Future<DateTime> _showDatePicker2() {
var date = DateTime.now();
return showCupertinoModalPopup(
context: context,
builder: (ctx) {
return SizedBox(
height: 200,
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.dateAndTime,
minimumDate: date,
maximumDate: date.add(
Duration(days: 30),
),
maximumYear: date.year + 1,
onDateTimeChanged: (DateTime value) {
print(value);
},
),
);
},
);
}
三方組件(flutter_cupertino_date_picker)
需要先依賴包
var _datetime=DateTime.now();
_showDatePicker(){
DatePicker.showDatePicker(
context,
picherTheme: DateTimePickerTheme(
showTitle: true, // 是否顯示標題
confirm: Text('確定',style:TextStyle(color: Colors.red)),
cancel: Text('取消',style:TextStyle(color: Colors.blue)),
),
minDateTime: DateTime.parse('1900-01-01') , // 最早日期
maxDateTime: DateTime.parse('2100-01-01') , // 最晚日期
initialDateTime: _datetime, // 當前日期(初始日期)
dateFormat:"yyyy-MM-dd", // 年月日
// dateFormat:"yyyy-MM-dd EEE,H時:m分,", // 年月日周時分秒
// pickerMode: DateTimePickerMode.datetime, // 年月日周時分秒
locale:DateTimePickerLocale.zh_cn,
onCancel:(){
},
onChange:(dateTime,List<int> index){
setDate((){
_datetime=dateTime;
});
},
onConfirm:(dateTime,List<int> index){
setDate((){
_datetime=dateTime;
});
},
);
}
3. AspectRatio 寬高比組件
設置子元素寬高比(寬度盡可能擴展邦蜜,高度由寬高比決定)依鸥。
AspectRatio(
aspectRatio: 2.0/1.0, // 寬高比
child: Container( // 子組件
color: Colors.blue,
),
)
4. Card 卡片組件(Meterial組件庫)
內容不能滾動,需要在MeterialApp內使用悼沈。
Card({
Key? key,
this.color, // 背景色
this.shadowColor, // 陰影色
this.elevation, // 陰影
this.shape, // 形狀
this.borderOnForeground = true,
this.margin, // 外邊距
this.clipBehavior,
this.child,
this.semanticContainer = true,
})
示例
Card(
margin: EdgeInsets.all(10.0), // 外邊距
child: Column( // 子組件
children: <Widget>[
ListTile(
title: Text("張三"),
subtitle: Text("男"),
)
],
),
// shape: , // 默認圓角陰影
)
5. 剪裁
用于對子組件進行剪裁
1. ClipOval
圓形(正方形時)贱迟,橢圓(矩形時)
2. ClipRRect
圓角矩形
3. ClipRect
溢出部分剪裁(剪裁子組件到實際占用的矩形大小)
4.ClipPath
按照自定義路徑剪裁
繼承CustomClipper<Path>自定義剪裁類絮供,重寫getClip方法返回自定義Path
ClipOval({Key? key, this.clipper, this.clipBehavior = Clip.antiAlias, Widget? child})
ClipRRect({
Key? key,
this.borderRadius = BorderRadius.zero,
this.clipper,
this.clipBehavior = Clip.antiAlias,
Widget? child,
})
ClipRect({ Key? key, this.clipper, this.clipBehavior = Clip.hardEdge, Widget? child })
示例
import 'package:flutter/material.dart';
class ClipTestRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 頭像
Widget avatar = Image.asset("imgs/avatar.png", width: 60.0);
return Center(
child: Column(
children: <Widget>[
avatar, // 不剪裁
ClipOval(child: avatar), // 剪裁為圓形
ClipRRect( // 剪裁為圓角矩形
borderRadius: BorderRadius.circular(5.0),
child: avatar,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Align(
alignment: Alignment.topLeft,
widthFactor: .5,// 寬度設為原來寬度一半衣吠,另一半會溢出
child: avatar,
),
Text("你好世界", style: TextStyle(color: Colors.green),)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ClipRect( // 將溢出部分剪裁
child: Align(
alignment: Alignment.topLeft,
widthFactor: .5, // 寬度設為原來寬度一半
child: avatar,
),
),
Text("你好世界",style: TextStyle(color: Colors.green))
],
),
],
),
);
}
}
運行結果
示例(自定義剪裁區(qū)域)
截取圖片中部40×30像素的范圍
1. 首先,自定義一個繼承自CustomClipper的子類:
class MyClipper extends CustomClipper<Rect> {
@override
// getClip()是用于獲取剪裁區(qū)域的接口壤靶,由于圖片大小是60×60蒸播,計算之后即圖片中部40×30像素的范圍。
Rect getClip(Size size) => Rect.fromLTWH(10.0, 15.0, 40.0, 30.0);
@override
// shouldReclip() 接口決定是否重新剪裁萍肆。
// 如果在應用中袍榆,剪裁區(qū)域始終不會發(fā)生變化時應該返回false,這樣就不會觸發(fā)重新剪裁塘揣,避免不必要的性能開銷包雀。如果剪裁區(qū)域會發(fā)生變化(比如在對剪裁區(qū)域執(zhí)行一個動畫),那么變化后應該返回true來重新執(zhí)行剪裁亲铡。
bool shouldReclip(CustomClipper<Rect> oldClipper) => false;
}
2. 然后才写,通過ClipRect來執(zhí)行剪裁葡兑,為了看清圖片實際所占用的位置,設置一個紅色背景:
DecoratedBox(
decoration: BoxDecoration(
color: Colors.red
),
child: ClipRect(
clipper: MyClipper(), // 使用自定義的clipper
child: avatar
),
)
剪裁成功了赞草,但是圖片所占用的空間大小仍然是60×60(紅色區(qū)域)讹堤,這是因為剪裁是在layout完成后的繪制階段進行的,所以不會影響組件的大小厨疙,這和Transform原理是相似的洲守。
運行結果
6. 標簽 Chip、ActionChip沾凄、FilterChip梗醇、ChoiceChip
標簽
Chip({
Key? key,
this.avatar, // 在左側顯示
required this.label, // 文本
this.labelStyle, //
this.labelPadding, //
this.deleteIcon, // 右側刪除圖標
this.onDeleted, // 點擊右側刪除圖標后回調
this.deleteIconColor, // 右側刪除圖標顏色
this.useDeleteButtonTooltip = true, // 長按右側刪除圖標是否提示
this.deleteButtonTooltipMessage, // 長按右側刪除圖標的提示文本
this.side,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.backgroundColor, // 背景色
this.padding,
this.visualDensity,
this.materialTapTargetSize,
this.elevation,
this.shadowColor,
})
可點擊的標簽
ActionChip({
Key? key,
this.avatar,
required this.label,
this.labelStyle,
this.labelPadding,
required this.onPressed, // 點擊后回調
this.pressElevation,
this.tooltip,
this.side,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.backgroundColor,
this.padding,
this.visualDensity,
this.materialTapTargetSize,
this.elevation,
this.shadowColor,
})
選中后左側出現(xiàn)對勾
FilterChip({
Key? key,
this.avatar,
required this.label, // 文本
this.labelStyle, //
this.labelPadding, //
this.selected = false, // 是否選中
required this.onSelected, // 選中狀態(tài)改變后回調
this.pressElevation,
this.disabledColor,
this.selectedColor, // 選中背景色
this.tooltip,
this.side,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.backgroundColor,
this.padding,
this.visualDensity,
this.materialTapTargetSize,
this.elevation,
this.shadowColor, // 陰影色
this.selectedShadowColor,// 選中后的陰影色
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
})
單選標簽
const ChoiceChip({
Key? key,
this.avatar,
required this.label, // 文本
this.labelStyle, //
this.labelPadding, //
this.onSelected, // 選中狀態(tài)改變后回調
this.pressElevation,
required this.selected, // 是否選中
this.selectedColor, // 選中背景色
this.disabledColor,
this.tooltip,
this.side,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.backgroundColor,
this.padding,
this.visualDensity,
this.materialTapTargetSize,
this.elevation,
this.shadowColor,
this.selectedShadowColor,
this.avatarBorder = const CircleBorder(),
})
7. 表格 DataTable、PaginatedDataTable
DataTable({
Key? key,
required this.columns, // 頂部的欄目內容
this.sortColumnIndex, // 排序欄的索引號(右側出現(xiàn)箭頭)
this.sortAscending = true, // 排序方式撒蟀,true升序
this.onSelectAll,
this.decoration,
this.dataRowColor,
this.dataRowHeight,
this.dataTextStyle,
this.headingRowColor,
this.headingRowHeight,
this.headingTextStyle,
this.horizontalMargin,
this.columnSpacing,
this.showCheckboxColumn = true,
this.showBottomBorder = false,
this.dividerThickness,
required this.rows, // 每一行內容
})
欄目
DataColumn({
required this.label, // 文本
this.tooltip,
this.numeric = false,
this.onSort, // 點擊后回調叙谨,進行排序,參數(shù)(index,isAscending)
})
一行
DataRow({
this.key,
this.selected = false, // 是否選中保屯,左側會有選擇框
this.onSelectChanged, // 選中狀態(tài)改變后回調
this.color, // 背景色
required this.cells, //
})
DataRow.byIndex({
int? index,
this.selected = false,
this.onSelectChanged,
this.color,
required this.cells,
})
單元格
DataCell(
this.child, {
this.placeholder = false,
this.showEditIcon = false,
this.onTap,
})
分頁表格
PaginatedDataTable({
Key? key,
this.header, // 表格標題
this.actions,
required this.columns, // 頂部欄目
this.sortColumnIndex, //
this.sortAscending = true, //
this.onSelectAll,
this.dataRowHeight = kMinInteractiveDimension,
this.headingRowHeight = 56.0,
this.horizontalMargin = 24.0,
this.columnSpacing = 56.0,
this.showCheckboxColumn = true,
this.initialFirstRowIndex = 0,
this.onPageChanged, // 頁面改變后回調
this.rowsPerPage = defaultRowsPerPage,
this.availableRowsPerPage = const <int>[defaultRowsPerPage, defaultRowsPerPage * 2, defaultRowsPerPage * 5, defaultRowsPerPage * 10],
this.onRowsPerPageChanged,
this.dragStartBehavior = DragStartBehavior.start,
required this.source, // 創(chuàng)建類手负,繼承DataTableSource,實現(xiàn)相關方法姑尺。
})
8. 分割線Divider
Divider({
Key? key,
this.height, // 高度
this.thickness,
this.indent, // 縮進
this.endIndent,
this.color, // 背景色
})
9. ListTile
1. leading
頭部widget
2. trailing
尾部widget
3. minLeadingWidth
頭部最小寬(默認40.0)
4. title
標題
5. subtitle
副標題
6. minVerticalPadding
最小的縱向間距(默認4.0)
7. horizontalTitleGap
標題距離頭部竟终、尾部的距離(默認16.0)
8. isThreeLine
9. tileColor
未選中的背景色
10. selectedTileColor
選中時的背景色
11. selected
是否選中(默認false)
12. hoverColor
指針懸停時的背景色
13. focusColor
獲取焦點時的背景色
14. autofocus
是否自動獲取焦點(默認false)
15. focusNode
焦點
16. mouseCursor
在內部或懸停時的鼠標樣式
17. shape
形狀
18. visualDensity
緊湊程度
19. dense
20. contentPadding
內部邊距
21. onTap
點擊回調
22. onLongPress
長按回調
23. enableFeedback
是否提供聽覺/觸覺反饋
24. enabled
是否可交互(默認true)
示例
ListTile(
leading: const Icon(Icons.add),
title: const Text('Add account'),
),
10. ButtonBarTheme
繼承自InheritedWidget
ButtonBarTheme({
Key? key,
required this.data,
required Widget child,
})
const ButtonBar({
Key? key,
this.alignment,
this.mainAxisSize,
this.buttonTextTheme,
this.buttonMinWidth,
this.buttonHeight,
this.buttonPadding,
this.buttonAlignedDropdown,
this.layoutBehavior,
this.overflowDirection,
this.overflowButtonSpacing,
this.children = const <Widget>[],
})
11. Material
Material({
Key? key,
this.type = MaterialType.canvas,
this.elevation = 0.0, // 陰影
this.color, //
this.shadowColor, // 陰影色
this.textStyle, // 文本樣式
this.borderRadius, // 圓角
this.shape, // 形狀
this.borderOnForeground = true,
this.clipBehavior = Clip.none,
this.animationDuration = kThemeChangeDuration,
this.child,
})
Spacer
Visibility
IndexedStack
CircleAvatar(圓形頭像)
CircleAvatar({
Key? key,
this.child,
this.backgroundColor, // 背景色
this.backgroundImage, // 背景圖片
this.onBackgroundImageError,
this.foregroundColor,
this.radius,
this.minRadius,
this.maxRadius,
})