前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Flutter 组件集录】Card | 8 月更文挑战

【Flutter 组件集录】Card | 8 月更文挑战

作者头像
张风捷特烈
发布2022-03-18 15:45:09
7770
发布2022-03-18 15:45:09
举报
一、 认识 Card 组件

卡片效果作为 Material Design 中的一员,Flutter 中 Card 组件自然是要有的。源码注释中是这么描述它的:带有轻微圆角和立面阴影的面板。本文将从源码的角度看一下 Card 组件的构成,并讲述一下 Card 在使用中的一些细小的注意点。

1.Card 基本信息

下面是 Card 组件类的定义构造方法,可以看出它继承自 StatelessWidget。没有必须要传入的参数,可以配置颜色、阴影色、形状、边距等属性。

2.Card 的简单使用

如下所示,通过 buildContent 返回 Container 组件作为内容。上层用 Card 组件的包裹后,会有小圆角 + 阴影 的效果,其中 color 属性就是面板的颜色。

代码语言:javascript
复制
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Card(
          color: const Color(0xffB3FE65),
          child: buildContent(),
        ),
      ),
    );
  }
  
  Widget buildContent() {
    return Container(
        width: 200,
        height: 0.618 * 200,
        padding: const EdgeInsets.all(10),
        child: Text("Card : 卡片", style: TextStyle(fontSize: 20)));
  }
}
2. shadowColor 和 elevation 属性

通过 shadowColor 可以设置阴影的颜色,通过 elevation 可以设置阴影的深度。

代码语言:javascript
复制
Card(
  color: Color(0xffB3FE65),
  shadowColor: Colors.blueAccent,
  elevation: 8,
  child: buildContent(),
)
3.margin 属性

单独一个 Card 也许看不清外边距,可以使用两个辅助的 box 看一下。如下,可以看出 Card 默认是有外边距的。调节外边距的属性便是 margin

使用下面的代码,就可以让左外边距为 20,右外边距为 30.

代码语言:javascript
复制
Card(
  margin: EdgeInsets.only(left: 20,right: 30),
  color: Color(0xffB3FE65),
  child: buildContent(),
)
4. clipBehavior 裁剪行为

Clip 是一个枚举类,包含四种形式,如下:

代码语言:javascript
复制
enum Clip {
  none, // 无
  hardEdge, // 硬边缘
  antiAlias, // 抗锯齿
  antiAliasWithSaveLayer, // 抗锯齿保存图层
}

如下左图,在内容的容器中使用图片装饰,你会很疑惑,为什么没有圆角了。因为 Card 的默认裁剪行为为 Clip.none。这时需要通过指定 clipBehavior 完成圆角,这是一个小细节,不知道的话很可能觉得 Card 组件不好用。

代码语言:javascript
复制
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child:
            Card(
              clipBehavior: Clip.antiAlias, //<--- 裁剪行为
              color: const Color(0xffB3FE65),
              child: buildContent(),
            ),
      ),
    );
  }
  Widget buildContent() {
    return Container(
        width: 200,
        height: 0.618 * 200,
        padding: const EdgeInsets.all(10),
        decoration: const BoxDecoration( //<--- 添加装饰图片
            image: DecorationImage(
                fit: BoxFit.cover,
                image: AssetImage('assets/images/anim_draw.webp')
            )
        ),
        child: Text("Card : 卡片", style: TextStyle(fontSize: 20,color: Colors.white)));
  }
}
5. shape 属性

前面只是简单的属性配置,而你 Card 的强大不止于此。也许你会觉得默认的圆角有点小,想要变大点,或不喜欢圆角装饰,先要搞点创造性装饰,那么 shape 属性将为你打开一扇大门。需要的是一个 ShapeBorder 对象,由于其为抽象类,需要找它的子类,框架中提供如下的子类。关于 shape 属性的适应,之前在《Path在手,天下我有》 中详细介绍过,这里不再赘述。

比如想要增加圆角,可以使用 RoundedRectangleBorder 形状。

代码语言:javascript
复制
Card(
  clipBehavior: Clip.antiAlias,
  color: const Color(0xffB3FE65),
  shape: const RoundedRectangleBorder(
      side: BorderSide.none,
      borderRadius: BorderRadius.all(Radius.circular(10))),
  elevation: 3,
  shadowColor: Colors.blueAccent,
  child: buildContent(),
),

除了内置的形状之外,我们还可以自己定义 Shape, 比如下面通过 nStarPath 获取一个多角星的路径,然后在继承自 ShapeBorder 的 StarShapeBorder#getOuterPath 中返回路径,就可以按照该路径进行裁剪。这里为了方便,多角星的数据写死了,外界的容器宽高该为 100

代码语言:javascript
复制
class StarShapeBorder extends ShapeBorder {
  @override
  EdgeInsetsGeometry get dimensions => null;

  @override
  Path getInnerPath(Rect rect, {TextDirection textDirection}) {
    return null;
  }

  @override
  Path getOuterPath(Rect rect, {TextDirection textDirection}) =>
      nStarPath(9, 50, 40, dx: 50, dy: 50);

  @override
  void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
    
  }

  @override
  ShapeBorder scale(double t) {
    return null;
  }

  Path nStarPath(int num, double R, double r, {dx = 0, dy = 0}) {
    Path _path = Path();
    _path.reset(); //重置路径
    double perRad = 2 * pi / num; //每份的角度
    double radA = perRad / 2 / 2; //a角
    double radB = 2 * pi / (num - 1) / 2 - radA / 2 + radA; //起始b角
    _path.moveTo(cos(radA) * R + dx, -sin(radA) * R + dy); //移动到起点
    for (int i = 0; i < num; i++) { //循环生成点,路径连至
      _path.lineTo(
          cos(radA + perRad * i) * R + dx, -sin(radA + perRad * i) * R + dy);
      _path.lineTo(
          cos(radB + perRad * i) * r + dx, -sin(radB + perRad * i) * r + dy);
    }
    _path.close();
    return _path;
  }
}
5.borderOnForeground 属性

这个属性估计没人在意它,它可以决定 ShapeBorder 的绘制是否显示在前景之中。通过上面可以看到 StarShapeBorder 中有个 paint 方法可以提供绘制操作,这里简单在区域左上角画个小圈。默认 borderOnForegroundtrue,绘制的装饰会显在前景中,如下图。

代码语言:javascript
复制
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
  canvas.drawCircle(Offset.zero, 50, Paint()..color=Colors.blueAccent);
}

如果 borderOnForeground 设置为 false,就说明绘制的内容不出现在前景中。

二、Card 的水波纹
1.错误的使用

如果你将 InkWell 放在了 Center 之上,那么它水波纹会被前景所覆盖。如下图所示,之上 margin 的那点区域显示出来水波纹。

代码语言:javascript
复制
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child:  InkWell(
        onTap: (){
        },
        child:Card(
        clipBehavior: Clip.antiAlias,
        color: const Color(0xffB3FE65),
        elevation: 3,
        shadowColor: Colors.blueAccent,
        child: buildContent()),
      ),
    ),
  );
}
2. 正确的使用

正确的使用方式是在 child 组件上嵌套 InkWell

代码语言:javascript
复制
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child:  Card(
        clipBehavior: Clip.antiAlias,
        color: const Color(0xffB3FE65),
        elevation: 3,
        shadowColor: Colors.blueAccent,
        child: InkWell(
            splashColor: Colors.blue.withAlpha(30),
            onTap: (){
            },
            child:buildContent()),
      ),
    ),
  );
}
3.无法触发水波纹的解决方案

有些时候,比如使用 Image、或为 Container 设置颜色、装之后,水波纹就无法触发。

这是可以通过 Ink 组件来替代 ContainerImage 源码中是怎么说的:

代码语言:javascript
复制
Widget buildContent() {
  return Ink(
      width: 200,
      height: 0.618 * 200,
      decoration: const BoxDecoration(
          image: DecorationImage(
              fit: BoxFit.cover,
              image: AssetImage('assets/images/anim_draw.webp'))),
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Text("Card: 卡片",
            style: TextStyle(fontSize: 20,color: Colors.white)),
      ));
}
三、Card 的源码分析

好啦,又到最后看源码的时间了。Card 组件作为一个 StatelessWidget,肯定是 “白嫖” 了别的组件功能。核心代码如下:可以看出它就是一个 Container + Material 组件的组合体。

那为什么一个件简单的的对象,要单独抽离一个 Card 组件呢?很明显,语义明确,简单易用,简单 就是王道。另外一点就是可以统一设置 CardTheme 来决定 Card 的默认表现。如果没有 Card 组件,想达到效果可以用 Material 组件,但每次用都要设置很多对象,而且无法设置主题,使用封装是为了更好地使用。

代码语言:javascript
复制
@override
Widget build(BuildContext context) {
  final ThemeData theme = Theme.of(context);
  final CardTheme cardTheme = CardTheme.of(context);
  return Semantics(
    container: semanticContainer,
    child: Container(
      margin: margin ?? cardTheme.margin ?? const EdgeInsets.all(4.0),
      child: Material(
        type: MaterialType.card,
        shadowColor: shadowColor ?? cardTheme.shadowColor ?? theme.shadowColor,
        color: color ?? cardTheme.color ?? theme.cardColor,
        elevation: elevation ?? cardTheme.elevation ?? _defaultElevation,
        shape: shape ?? cardTheme.shape ?? const RoundedRectangleBorder(
          borderRadius: BorderRadius.all(Radius.circular(4.0)),
        ),
        borderOnForeground: borderOnForeground,
        clipBehavior: clipBehavior ?? cardTheme.clipBehavior ?? Clip.none,
        child: Semantics(
          explicitChildNodes: !semanticContainer,
          child: child,
        ),
      ),
    ),
  );
}

通过之前看的几个 StatelessWidget 的组件可以发现,这种类型的组件主要的目的就是方便用户使用,其内部都是依赖于别的组件实现的,使用在看 StatelessWidget 时多看看内部的实现方式,就可以将很多组件联系到一块,很多曾经的疑惑点,也就能迎刃而解。了解了内部的实现,在使用时,也会多几分底气。那本文到这里就结束了,谢谢观看,明天见~

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021/08/20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、 认识 Card 组件
  • 1.Card 基本信息
  • 2.Card 的简单使用
  • 2. shadowColor 和 elevation 属性
  • 3.margin 属性
  • 4. clipBehavior 裁剪行为
  • 5. shape 属性
  • 5.borderOnForeground 属性
  • 二、Card 的水波纹
    • 1.错误的使用
      • 2. 正确的使用
        • 3.无法触发水波纹的解决方案
        • 三、Card 的源码分析
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档