flutter widget組件之-----------繪製和視覺效果組件
widget分爲兩類:widgets library中的標準widget和Material Components library中的專用widget;任何應用程序都可以使用widgets library中的widget,但只有Material應用程序可以使用Material Components庫。其中Card,ListTitle就是Material Components庫中的組件。
opacity
使其子widget透明的widget。
- 構造函數
Opacity({
Key key,
@required this.opacity,// 透明度
this.alwaysIncludeSemantics = false,//是否始終包含子widget的語義信息
Widget child,// 子組件
})
- 應用示例
import 'package:flutter/material.dart';
import 'dart:ui';
import 'package:flutter/widgets.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.green,
primaryColor: Colors.pink
),
home:MyStateFulWidget(),
);
}
}
class MyStateFulWidget extends StatefulWidget {
@override
_MyStateFulWidget createState() => new _MyStateFulWidget();
}
class _MyStateFulWidget extends State<MyStateFulWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("mediaquery")),
body: Container(
child: Opacity(
opacity: 0.9,// 透明度
alwaysIncludeSemantics: true,
// 子組件
child: Column(
children: <Widget>[
RaisedButton(
onPressed: (){},
child: Text("點擊"),
),
Text("文本text",style: TextStyle(color: Colors.lightGreen),)
],
)
),
)
);
}
}
transform
在繪製子widget之前應用轉換的widget
- 構造函數
Transform({
Key key,
@required this.transform,//必須,一個4x4的矩陣。不難發現,其他平臺的變換矩陣也都是四階的。一些複合操作,僅靠三維是不夠的,必須採用額外的一維來補充,感興趣的同學可以自行搜索瞭解
this.origin,//旋轉參考點,默認旋轉點事左上角頂點
this.alignment,//對齊方式
this.transformHitTests = true,// 點擊區域是否要做響應的改變
Widget child,// 子組件
})
Transform.rotate({
Key key,
@required double angle,// 弧度
this.origin,
this.alignment = Alignment.center,
this.transformHitTests = true,
Widget child,
})
Transform.translate({
Key key,
@required Offset offset,//偏移量
this.transformHitTests = true,
Widget child,
})
Transform.scale({
Key key,
@required double scale,//縮放級別
this.origin,
this.alignment = Alignment.center,
this.transformHitTests = true,
Widget child,
})
- 應用示例
class _MyStateFulWidget extends State<MyStateFulWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("mediaquery")),
body: Center(
child: Column(
children: <Widget>[
Container(
color: Colors.black,
child: Transform(
alignment: Alignment.topRight,// 對齊方式
origin:Offset(0,5) ,//旋轉參考點
transform: Matrix4.skewY(0.3)..rotateZ(30),//轉換矩陣
//要轉換的組件
child: Container(
padding: const EdgeInsets.all(8.0),
color: const Color(0xFFE8581C),
child: const Text('Apartment for rent!'),
),
),
),
],
),
)
);
}
}
fractionalTranslation
繪製盒子之前給其添加一個偏移轉換
- 構造函數
FractionalTranslation({
Key key,
@required this.translation,//應用於子對象的轉換, Offset對象
this.transformHitTests = true,// 點擊區域是否要做響應的改變
Widget child, // 子組件
})
- 應用示例
class _MyStateFulWidget extends State<MyStateFulWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("mediaquery")),
body: Center(
child: Column(
children: <Widget>[
Container(
// width: 100,
// height: 100,
// color: Colors.black,
child: FractionalTranslation(
// 子對象
child: RaisedButton(onPressed: (){},child: Text("data"),),
//將子對象,向右移動組件寬度的1倍,向下移動組件高度的1倍
translation: Offset(1, 1),
transformHitTests: true,
),
)
],
),
)
);
}
}
DecoratedBox
在孩子繪製之前或之後繪製裝飾的widget
- 構造函數
DecoratedBox({
Key key,
@required this.decoration,// 裝飾器,通常用boxdecoration
this.position = DecorationPosition.background,//在子組件的前面還是後面繪製此裝飾
Widget child// 子組件
})
- 應用示例
class _MyStateFulWidget extends State<MyStateFulWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("mediaquery")),
body: Center(
child: Column(
children: <Widget>[
//裝飾容器DecoratedBox
DecoratedBox(
// 裝飾器,通常用boxdecoration
decoration: BoxDecoration(
//線性背景顏色
gradient: LinearGradient(colors: [Colors.blue,Colors.yellow]),
//邊框角度
borderRadius: BorderRadius.circular(30.0),
//陰影
boxShadow:[
BoxShadow(
//顏色
color: Colors.black,
//偏移量
offset: Offset(4.0, 4.0),
//模糊半徑
blurRadius: 10.0
)
]
),
//在子組件的前面還是後面繪製此裝飾
position: DecorationPosition.background,// 設置到後面
// 子組件
child: Padding(
padding: EdgeInsets.all(20.0),
child: Text("DecoratedBox",style: TextStyle(color: Colors.white),),
),
),
],
),
)
);
}
}
RotatedBox
可以延順時針以90度的倍數旋轉其子widget
- 構造函數
RotatedBox({
Key key,
@required this.quarterTurns,//子組件應該順時針旋轉的的次數,每次1/4圈
Widget child,// 子組件
})
- 應用示例
class _MyStateFulWidget extends State<MyStateFulWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("mediaquery")),
body: Center(
child: Column(
children: <Widget>[
RotatedBox(
// 子組件
child: RaisedButton(onPressed: (){},child: Text("旋轉"),),
//旋轉1/4圈的次數
quarterTurns: 3,
)
],
),
)
);
}
}
ClipOval ClipPath ClipRect ClipRRect
ClipOval: 圓形裁剪,用橢圓剪輯其孩子的widget
ClipPath: 路徑裁剪,用path剪輯其孩子的widget
ClipRect:矩形裁剪,用矩形剪輯其孩子的widget
ClipRRect:圓角矩形裁剪
- 構造函數
ClipOval({
Key key,
this.clipper,// 裁剪路徑
this.clipBehavior = Clip.antiAlias, //裁剪行爲
Widget child // 子組件
})
ClipPath({
Key key,
this.clipper,
this.clipBehavior = Clip.antiAlias,
Widget child
})
ClipRect({
Key key,
this.clipper,
this.clipBehavior = Clip.hardEdge,
Widget child
})
const ClipRRect({
Key key,
this.borderRadius,
this.clipper,
this.clipBehavior = Clip.antiAlias,
Widget child,
})
- 應用示例
class _MyStateFulWidget extends State<MyStateFulWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("mediaquery")),
body: Center(
child: Column(
children: <Widget>[
//圓形裁剪
ClipOval(
// clipper: ,
child:new SizedBox(
width: 100.0,
height:100.0,
child: new Image.asset("assets/images/3.jpg",fit: BoxFit.fill,),
),
),
Divider(color: Colors.red,height: 2,),
// 按路徑裁剪,自定義了裁剪路徑
ClipPath(
child:new SizedBox(
width: 100.0,
height:100.0,
child: new Image.asset("assets/images/4.jpg",fit: BoxFit.fill,),
),
// 自定義五角星裁剪路徑
clipper:new _StarCliper(radius: 50.0) ,
),
Divider(color: Colors.red,height: 2,),
// 圓角矩形裁剪
ClipRRect(
borderRadius: new BorderRadius.all(new Radius.circular(10.0)),
child:new SizedBox(
width: 100.0,
height:100.0,
child: new Image.asset("assets/images/3.jpg",fit: BoxFit.fill,),
),
),
Divider(color: Colors.red,height: 2,),
//方角矩形裁剪 ,自定義了裁剪路徑
ClipRect(
child:new SizedBox(
width: 100.0,
height:100.0,
child: new Image.asset("assets/images/4.jpg",fit: BoxFit.fill,),
),
clipper: new _MyClipper(),
)
],
),
)
);
}
}
// 自定義裁剪器
class _MyClipper extends CustomClipper<Rect>{
@override
Rect getClip(Size size) {
return new Rect.fromLTRB(10.0, 10.0, size.width - 10.0, size.height- 10.0);
}
@override
bool shouldReclip(CustomClipper<Rect> oldClipper) {
return true;
}
}
// 五角星路徑
class _StarCliper extends CustomClipper<Path>{
final double radius;
_StarCliper({this.radius});
/// 角度轉弧度公式
double degree2Radian(int degree) {
return (Math.pi * degree / 180);
}
@override
Path getClip(Size size) {
double radius = this.radius;
Path path = new Path();
double radian = degree2Radian(36);// 36爲五角星的角度
double radius_in = (radius * Math.sin(radian / 2) / Math
.cos(radian)); // 中間五邊形的半徑
path.moveTo((radius * Math.cos(radian / 2)), 0.0);// 此點爲多邊形的起點
path.lineTo((radius * Math.cos(radian / 2) + radius_in
* Math.sin(radian)),
(radius - radius * Math.sin(radian / 2)));
path.lineTo((radius * Math.cos(radian / 2) * 2),
(radius - radius * Math.sin(radian / 2)));
path.lineTo((radius * Math.cos(radian / 2) + radius_in
* Math.cos(radian / 2)),
(radius + radius_in * Math.sin(radian / 2)));
path.lineTo(
(radius * Math.cos(radian / 2) + radius
* Math.sin(radian)), (radius + radius
* Math.cos(radian)));
path.lineTo((radius * Math.cos(radian / 2)),
(radius + radius_in));
path.lineTo(
(radius * Math.cos(radian / 2) - radius
* Math.sin(radian)), (radius + radius
* Math.cos(radian)));
path.lineTo((radius * Math.cos(radian / 2) - radius_in
* Math.cos(radian / 2)),
(radius + radius_in * Math.sin(radian / 2)));
path.lineTo(0.0, (radius - radius * Math.sin(radian / 2)));
path.lineTo((radius * Math.cos(radian / 2) - radius_in
* Math.sin(radian)),
(radius - radius * Math.sin(radian / 2)));
path.close();// 使這些點構成封閉的多邊形
return path;
}
@override
bool shouldReclip(_StarCliper oldClipper) {
return this.radius != oldClipper.radius;
}
}
customPaint
提供一個畫布的widget,在繪製階段可以在畫布上繪製自定義圖形
- 構造函數
CustomPaint({
Key key,
this.painter,// 繪製主體的繪製工具
this.foregroundPainter,// 繪製前景的繪製工具
this.size = Size.zero,// 畫布大小
this.isComplex = false,// 是否要用到緩衝
this.willChange = false,// 是否應告知光柵緩存此繪製可能在下一幀中更改。
Widget child,// 子組件,通常不用,一般都得不到想要的子組件
})
- 應用示例
mport 'package:flutter/material.dart';
import 'dart:ui';
import 'package:flutter/widgets.dart';
import 'dart:async';
import 'dart:math';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.green,
primaryColor: Colors.pink
),
home:ClockPage(),
);
}
}
class ClockPage extends StatefulWidget {
final double radius;
final Color hourHandColor;
final Color minuteHandColor;
final Color secondHandColor;
final Color numberColor;
final Color borderColor;
const ClockPage(
{Key key,
this.hourHandColor,
this.minuteHandColor,
this.secondHandColor,
this.numberColor,
this.borderColor,
this.radius = 150.0})
: super(key: key);
@override
State<StatefulWidget> createState() {
return ClockPageState();
}
}
class ClockPageState extends State<ClockPage> {
DateTime datetime;
Timer timer;
@override
void initState() {
super.initState();
datetime = DateTime.now();
timer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() {
datetime = DateTime.now();
});
});
}
@override
void dispose() {
super.dispose();
timer.cancel();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("customPainter"),),
body: CustomPaint(
// 繪製主體的繪製工具
painter: ClockPainter(
datetime,
numberColor: Colors.black,
handColor: Colors.black,
borderColor: Colors.black,
radius: widget.radius
),
//畫布大小
size: Size(widget.radius * 2, widget.radius * 2),
),
);
}
}
class ClockPainter extends CustomPainter {
final Color handColor;
final Color numberColor;
final Color borderColor;
final double radius;
List<Offset> secondsOffset = [];
final DateTime datetime;
TextPainter textPainter;
double angle;
double borderWidth;
ClockPainter(this.datetime,
{this.radius = 150.0,
this.handColor = Colors.black,
this.numberColor = Colors.black,
this.borderColor = Colors.black}) {
borderWidth = radius / 14;
final secondDistance = radius - borderWidth * 2;
//init seconds offset
for (var i = 0; i < 60; i++) {
Offset offset = Offset(
cos(degToRad(6 * i - 90)) * secondDistance + radius,
sin(degToRad(6 * i - 90)) * secondDistance + radius);
secondsOffset.add(offset);
}
textPainter = new TextPainter(
textAlign: TextAlign.center,
textDirection: TextDirection.rtl,
);
angle = degToRad(360 / 60);
}
@override
void paint(Canvas canvas, Size size) {
final scale = radius / 150;
//draw border
final borderPaint = Paint()
..color = borderColor
..style = PaintingStyle.stroke
..strokeWidth = borderWidth;
canvas.drawCircle(
Offset(radius, radius), radius - borderWidth / 2, borderPaint);
//draw second point
final secondPPaint = Paint()
..strokeWidth = 2 * scale
..color = numberColor;
if (secondsOffset.length > 0) {
canvas.drawPoints(PointMode.points, secondsOffset, secondPPaint);
canvas.save();
canvas.translate(radius, radius);
List<Offset> bigger = [];
for (var i = 0; i < secondsOffset.length; i++) {
if (i % 5 == 0) {
bigger.add(secondsOffset[i]);
//draw number
canvas.save();
canvas.translate(0.0, -radius + borderWidth * 4);
textPainter.text = new TextSpan(
text: "${(i ~/ 5) == 0 ? "12" : (i ~/ 5)}",
style: TextStyle(
color: numberColor,
fontFamily: 'Times New Roman',
fontSize: 28.0 * scale,
),
);
//helps make the text painted vertically
canvas.rotate(-angle * i);
textPainter.layout();
textPainter.paint(canvas,
new Offset(-(textPainter.width / 2), -(textPainter.height / 2)));
canvas.restore();
}
canvas.rotate(angle);
}
canvas.restore();
final biggerPaint = Paint()
..strokeWidth = 5 * scale
..color = numberColor;
canvas.drawPoints(PointMode.points, bigger, biggerPaint);
}
final hour = datetime.hour;
final minute = datetime.minute;
final second = datetime.second;
// draw hour hand
Offset hourHand1 = Offset(
radius - cos(degToRad(360 / 12 * hour - 90)) * (radius * 0.2),
radius - sin(degToRad(360 / 12 * hour - 90)) * (radius * 0.2));
Offset hourHand2 = Offset(
radius + cos(degToRad(360 / 12 * hour - 90)) * (radius * 0.5),
radius + sin(degToRad(360 / 12 * hour - 90)) * (radius * 0.5));
final hourPaint = Paint()
..color = handColor
..strokeWidth = 8 * scale;
canvas.drawLine(hourHand1, hourHand2, hourPaint);
// draw minute hand
Offset minuteHand1 = Offset(
radius - cos(degToRad(360 / 60 * minute - 90)) * (radius * 0.3),
radius - sin(degToRad(360 / 60 * minute - 90)) * (radius * 0.3));
Offset minuteHand2 = Offset(
radius +
cos(degToRad(360 / 60 * minute - 90)) * (radius - borderWidth * 3),
radius +
sin(degToRad(360 / 60 * minute - 90)) * (radius - borderWidth * 3));
final minutePaint = Paint()
..color = handColor
..strokeWidth = 3 * scale;
canvas.drawLine(minuteHand1, minuteHand2, minutePaint);
// draw second hand
Offset secondHand1 = Offset(
radius - cos(degToRad(360 / 60 * second - 90)) * (radius * 0.3),
radius - sin(degToRad(360 / 60 * second - 90)) * (radius * 0.3));
Offset secondHand2 = Offset(
radius +
cos(degToRad(360 / 60 * second - 90)) * (radius - borderWidth * 3),
radius +
sin(degToRad(360 / 60 * second - 90)) * (radius - borderWidth * 3));
final secondPaint = Paint()
..color = handColor
..strokeWidth = 1 * scale;
canvas.drawLine(secondHand1, secondHand2, secondPaint);
final centerPaint = Paint()
..strokeWidth = 2 * scale
..style = PaintingStyle.stroke
..color = Colors.yellow;
canvas.drawCircle(Offset(radius, radius), 4 * scale, centerPaint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
num degToRad(num deg) => deg * (pi / 180.0);
num radToDeg(num rad) => rad * (180.0 / pi);
backDropFilter
一個widget,它將過濾器應用到現有的繪圖內容,然後繪製孩子。這種效果是比較昂貴的,尤其是如果過濾器是non-local,如blur.
BackdropFilter不是變換背景來實現高斯模糊的效果,而是通過在背景上面蓋上一個模糊層從而達到高斯模糊的效果,因此要做模糊的組件必須要在BackdropFilter底下,通常用Stack來控制佈局的層次擺放
- 構造函數
BackdropFilter({
Key key,
@required this.filter,// 過濾器,構建模糊效果
Widget child
})
- 應用示例
class MyStatePageState extends State<MyStatePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("customPainter"),),
body: Center(
child: Stack(
// 作爲模糊背景的組件放在前面
children: <Widget>[
// Container(
// alignment: Alignment.center,
// child: Image.asset("assets/images/4.jpg",width: 300, height: 300,),
// ),
// // 作爲模糊背景的組件
RaisedButton(onPressed: (){},child: Text("data"),),
// 定義模糊效果
BackdropFilter(
// 過濾器,構建模糊效果
filter: ui.ImageFilter.blur(sigmaX:1, sigmaY: 1),
child: Container(
color: Colors.white.withAlpha(0),
),
),
],
),
),
);
}
}