Flutter了解之入門篇5-2(常用組件2)

目錄

  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自定義繪制)
  1. 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,
            ),
          );
        ],
      ),
    );
  }
}
  1. 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,
  ),
),
  1. 自定義指示器(通過CustomPainter自定義繪制)
  1. 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,
  }) 
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市股缸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吱雏,老刑警劉巖敦姻,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異歧杏,居然都是意外死亡镰惦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門犬绒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旺入,“玉大人,你說我怎么就攤上這事凯力∫瘃” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵咐鹤,是天一觀的道長拗秘。 經常有香客問我,道長祈惶,這世上最難降的妖魔是什么雕旨? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任扮匠,我火速辦了婚禮,結果婚禮上凡涩,老公的妹妹穿的比我還像新娘棒搜。我一直安慰自己,他們只是感情好活箕,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布力麸。 她就那樣靜靜地躺著,像睡著了一般讹蘑。 火紅的嫁衣襯著肌膚如雪末盔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天座慰,我揣著相機與錄音陨舱,去河邊找鬼。 笑死版仔,一個胖子當著我的面吹牛游盲,可吹牛的內容都是我干的。 我是一名探鬼主播蛮粮,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼益缎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了然想?” 一聲冷哼從身側響起莺奔,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎变泄,沒想到半個月后令哟,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡妨蛹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年屏富,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛙卤。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡狠半,死狀恐怖,靈堂內的尸體忽然破棺而出颤难,到底是詐尸還是另有隱情神年,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布行嗤,位于F島的核電站瘤袖,受9級特大地震影響,放射性物質發(fā)生泄漏昂验。R本人自食惡果不足惜捂敌,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一艾扮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧占婉,春花似錦泡嘴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至奖慌,卻和暖如春抛虫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背简僧。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工建椰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人岛马。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓棉姐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親啦逆。 傳聞我的和親對象是個殘疾皇子伞矩,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內容