Flutter UI - image 系 Widget

2,792 阅读7分钟

Flutter 里面关于图片的内容挺多,大家好好看下:

  • Icon
  • Image
  • 关于图片的在 row、colomu 中的缩放
  • image 开源组件
  • 阿里巴巴矢量图标字体
  • SVG 矢量图支持

Icon

Flutter 非常贴心的提供了很多 MD 风格的 icon,于是这就有了Icon这个 Widget

Icon属性如下:

  const Icon(this.icon, {
    Key key,
    this.size,
    this.color,
    this.semanticLabel, // 图片的描述,没啥用
    this.textDirection,
  })
class DD extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Icon(
          Icons.access_alarm,
          size: 50,
          semanticLabel: "AAA",
        ),
      ],
    );
  }
}

  • 其他的不用说,重点看Icons这个类,这里面保存着所有 Flutter 内置的 icon 给我们使用,非常多
  • 再一个就是size了,Icon 天然的是正方形的, 大家注意
  • color是前景色,默认的是混合模式,color概念Icon图片的颜色,这里color给一个红色玩玩
class DD extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Icon(
          Icons.access_alarm,
          size: 50,
          color: Colors.red,
          semanticLabel: "AAA",
        ),
      ],
    );
  }
}


Image

一看名字大家就知道我们熟悉的回来了,属性值和 android 一样,下面就说说值得注意的点

  const Image({
    Key key,
    @required this.image,
    this.semanticLabel, // 图片的描述,没啥用
    this.excludeFromSemantics = false, // 是否从语义上排除该图片,默认值为false
    this.width,
    this.height,
    this.color, // 图片的前景色,和colorBlendMode结合使用
    this.colorBlendMode,
    this.fit, // 图片缩放模式
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
    this.centerSlice,
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
    this.filterQuality = FilterQuality.low,
  })

1. 加载图片

Image widget 使用ImageProvider抽象类来提供图片资源,这是典型的接口模式,自然 Flutter 提供了多种ImageProvider抽象类实现,可以从:本地/网络/项目资源中读取图片,设计思路非常 nice,我是非常喜欢、欣赏的~

  • NetworkImage - 从网路加载图片
Image(
        image: NetworkImage("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000"
            "&sec=1567948974&di=cb4ccca1f7932dfdbf09eef5b1e6d4d3&imgtype=jpg&er=1&src="
            "http%3A%2F%2Fwww.ahgame.com%2Fuploads%2Fallimg%2F161124%2F16112416235712.jpg"),
),

// 这样写也OK
Image.network(
      "https://img-blog.csdnimg.cn/20181210151747299.jpg",
    );

  • AssetImage - 加载 asset 资源图片
Image(
      image: AssetImage("assets/icons/icon_2.jpg"),
),

// 或者这样
Image.asset("imgs/code.png");

2. Flutter 资源文件结构

Flutter 没有指定资源文件地址,全完是配置式的,随便你怎么组织文件形式,完事在pubspec.yaml文件中声明即可

图片可以每一张都在pubspec.yaml里面配置,但是这样图片多了的话很没麻烦的,不可能都写一遍吧。所以我们把图片放在一个固定的文件夹里,把文件夹地址配置到pubspec.yaml里面,注意该配置要写在pubspec.yaml文件的Flutter标签下:

ame: flutter_app4
description: A new Flutter application.

version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:

  uses-material-design: true

  # 图片资源配置
  assets:
    - assets/icons/
    # 配置单张图片,一般不这么写,图片多了要写到什么时候去啊
    - assets/icons/icon_1.gif 

图片文件层级:

  • 注意啊,对于 pubspec.yaml 来说,最外层文件assets的子文件夹icons必须单独配置,是不能指望- assets/就把icons里面的图片逗声明进去的,必须给二级文件夹再声明一次- assets/icons/,这样配置是包含icons的子文件夹的内容的,不用再考虑三级文件夹的事,Google 这是设置是为了让我们把资源分门别类分好

代码:

class DD extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Image(
          image: AssetImage("assets/icons/icon_2.jpg"),
        ),
      ],
    );
  }
}

3. Fit 缩放

Image 只写宽或者高的时候,默认是按比例图片适配的,举个例子,我在外面包一层Container,蓝色背景

3.1 只是高度时: 是可以自动适配图片比例的

class DD extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Container(
          color: Colors.blue,
          child: Image(
            image: AssetImage("assets/icons/icon_3.png"),
            height: 200,
          ),
        ),
      ],
    );
  }
}

3.2 宽高都写时: 就得注意对外层包裹容器大小的影响了

class DD extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Container(
          color: Colors.blue,
          child: Image(
            image: AssetImage("assets/icons/icon_3.png"),
            width: 400,
            height: 200,
          ),
        ),
      ],
    );
  }
}

3.3 fit 缩放模式

不详细展开了,大家看吧,应该都没问题的

4. BlendMode 颜色混合模式

Image里的color是前景色,写的话会覆盖图片,所以就需要和BlendMode颜色混合一起用了,Iconcolor默认处理了,我们自己不能改,Image这里我们可以自由发挥了

Flutter 的BlendMode颜色混合模式是 android PorterDuffXferMode和前端background-blend-mode的混合,下面的模式都取自 android 和前端,这样吧我不详列出BlendMode的属性了,我把 android 的和前端的放出来,大家参考下

  • Flutter
  • android
  • 前端

这些效果挺蛋疼的,我挨个试了一遍,效果明显的我截个图,省以后事了

原图如下,color 设置的红色:

下面是各种效果,大家自己看吧


关于图片的在 row、colomu 中的缩放

icon、image 在 row、colomu 中即便加上 expanded 壳,也不会占满父控件的全部,必须要借助 BoxConstraints 对子 widget 的强制约束才行,下面举个例子

例子1:

class EE extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Expanded(
            flex: 1,
            child: Image.asset('assets/icons/icon_1.gif', fit: BoxFit.cover)),
        Expanded(
            flex: 1,
            child: Image.asset('assets/icons/icon_2.jpg', fit: BoxFit.cover)),
        Expanded(
            flex: 1,
            child: Image.asset('assets/icons/icon_3.png', fit: BoxFit.cover))
      ],
    );
  }
}

例子1 没达到我们想要占满全部宽度的想法......

例子2:

class FF extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Expanded(
            flex: 1,
            child: ConstrainedBox(
              child: Image.asset(
                'assets/icons/icon_1.gif',
                fit: BoxFit.cover,
              ),
              constraints: new BoxConstraints.expand(),
            )),
        Expanded(
            flex: 1,
            child: ConstrainedBox(
              child: Image.asset(
                'assets/icons/icon_2.jpg',
                fit: BoxFit.cover,
              ),
              constraints: new BoxConstraints.expand(),
            )),
        Expanded(
            flex: 1,
            child: Container(
              child: Image.asset('assets/icons/icon_3.png', fit: BoxFit.cover),
              constraints: new BoxConstraints.expand(),
            )),
      ],
    );
  }
}

BoxConstraints 盒子模型是会对子 widget 有强制约束的


image 开源组件

Flutter 原生的 image 组件功能很少,没有占位图,没有淡入淡出动画,只支持内存缓存,没有文件缓存,很硬伤的,所以有必要提到2个开源组件:

  • FadeInImage - 支持 loading 占位图
  • CachedNetworkImage - 支持 loading、error 占位图,并且支持文件缓存

下面内容不难,我直接贴 Demo 了,其他属性大家也看的懂的~

FadeInImage

导包:

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2
  transparent_image: ^1.0.0
// 有时候不用到这个包
import 'package:transparent_image/transparent_image.dart';

class EE extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FadeInImage.assetNetwork(
      fadeInDuration: Duration(milliseconds: 300),
      fadeOutDuration: Duration(milliseconds: 300),
      fadeInCurve: Curves.easeInCirc,
      placeholder: "assets/icons/icon_loading.gif",
      image: "xxx",
    );
  }
}

fadeInCurve 属性是图片切换的动画, Curves 里预预置了很多

CachedNetworkImage

导包:

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2
  cached_network_image: ^1.1.1
import 'package:cached_network_image/cached_network_image.dart';

class FF extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CachedNetworkImage(
      placeholder: (context, url) => Image.asset("assets/icons/icon_loading.gif"),
      errorWidget: (context, url, Object) => Image.asset("assets/icons/icon_loading.gif"),
      imageUrl: "xxx",
    );
  }
}

阿里巴巴矢量图标字体

这个大家在 android 里应该都熟悉,阿里巴爸爸矢量图可以打包成ttf字体格式下载下来,当做字体来显示,原理和emoil表情一样

不过实测过后发现 Flutter 不支持阿里矢量库里的彩色 icon,会按照黑白来显示

阿里图片原样:

ttf 文件放置:

yaml

flutter:

  uses-material-design: true

  # 图片资源配置
  assets:
    - assets/icons/

  fonts:
    - family: font1
      fonts:
        - asset: assets/fonts/font_fangzheng_duhei.ttf

    - family: font2
      fonts:
        - asset: assets/fonts/font_ali_f1.ttf

创建阿里图标对象:

import 'package:flutter/cupertino.dart';

class ALiFonts{
  static const IconData realTime = IconData(0xe74b,fontFamily: "font2");
  static const IconData computing = IconData(0xe743,fontFamily: "font2");
}

使用:

// Icon 承载阿里图标
class EE extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Icon(
          ALiFonts.realTime,
          size: 30,
        ),
        Icon(
          ALiFonts.computing,
          size: 30,
        ),
      ],
    );
  }
}

注意:

  • 阿里图标对应的16进制标准样式:0xe74b,字幕自己写一遍,网站复制的话显示不出来,数值部分只取最后4位,0x是固定添加打头的

SVG 矢量图支持

我看有说法是:官方不打算支持 SVG,目前对于 SVG 有2种思路:

  • 转换成ttf使用
  • 第三方库支持

1. SVG 转 ttf

官方提供:Flutter icon 库,可以提供 SVG 转 ttf 的服务

这种思路我不写了,大家看这篇介绍吧:Flutter 中使用svg资源

2. 开源组件:flutter_svg

flutter_svg 可以提供如原生 image 一样的使用体验

依赖:

dependencies:
  flutter:
    sdk: flutter

  flutter_svg: ^0.14.1

图片路径:

使用:

import 'package:flutter_svg/flutter_svg.dart';

class EE extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        SvgPicture.asset(
          "assets/svgs/svg_1.svg",
          width: 150,
          height: 150,
          color: Colors.red,
        )
      ],
    );
  }
}

原理研究

这部分暂时没看,先方文章