flutter widget組件之-----------繪製和視覺效果組件

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),
                ),
              ),
          ],
        ),
      ),
    );
  }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章