Flutter 屏幕适配、颜色、样式、Icon 管理

1、屏幕适配

当前手机屏幕尺寸各不相同,导致我们平时写布局的时候会在个不同的移动设备上显示的效果不同。

为了达到一套代码所有手机(安卓、苹果)体验一致效果,需要做尺寸上的适配。

适配方案

计算公式 = 实际尺寸 = UI尺寸 * 设备宽度/设计图宽度

实现代码

import 'package:flutter/material.dart';

/// @desc:屏幕适配
/// @time 2019/3/11 5:07 PM
/// @author Cheney
class LcfarmSize {
  static LcfarmSize instance = LcfarmSize();

  //设计稿的设备尺寸修改
  double width;
  double height;
  bool allowFontScaling;

  static MediaQueryData _mediaQueryData;
  static double _screenWidth;
  static double _screenHeight;
  static double _pixelRatio;
  static double _statusBarHeight;

  static double _bottomBarHeight;

  static double _textScaleFactor;

  LcfarmSize({
    this.width = 375,
    this.height = 677,
    this.allowFontScaling = false,
  });

  static LcfarmSize getInstance() {
    return instance;
  }

  void init(BuildContext context) {
    ///release 模式下取不到
//    _mediaQueryData = MediaQueryData.fromWindow(window);
    MediaQueryData mediaQuery = MediaQuery.of(context);
    _mediaQueryData = mediaQuery;
    _pixelRatio = mediaQuery.devicePixelRatio;
    _screenWidth = mediaQuery.size.width;
    _screenHeight = mediaQuery.size.height;
    _statusBarHeight = mediaQuery.padding.top;
    _bottomBarHeight = _mediaQueryData.padding.bottom;
    _textScaleFactor = mediaQuery.textScaleFactor;
//    print("lcfarm init _screenWidth=$_screenWidth");
//    print("lcfarm init _screenHeight=$_screenHeight");
//    print("lcfarm init _textScaleFactor=$_textScaleFactor");
//    print("lcfarm init scaleWidth=${_screenWidth / instance.width}");
  }

  static MediaQueryData get mediaQueryData => _mediaQueryData;

  ///每个逻辑像素的字体像素数,字体的缩放比例
  static double get textScaleFactory => _textScaleFactor;

  ///设备的像素密度
  static double get pixelRatio => _pixelRatio;

  ///当前设备宽度 dp
  static double get screenWidth => _screenWidth;

  ///当前设备高度 dp
  static double get screenHeight => _screenHeight;

  ///当前设备宽度 px
  static double get screenWidthPx => _screenWidth * _pixelRatio;

  ///当前设备高度 px
  static double get screenHeightPx => _screenHeight * _pixelRatio;

  ///状态栏高度 刘海屏会更高 dp
  static double get statusBarHeight => _statusBarHeight;

  ///底部安全区距离 dp
  static double get bottomBarHeight => _bottomBarHeight;

  ///状态栏高度 刘海屏会更高 px
  static double get statusBarHeightPx => _statusBarHeight * _pixelRatio;

  ///底部安全区距离 px
  static double get bottomBarHeightPx => _bottomBarHeight * _pixelRatio;

  ///根据屏幕宽度适配,实际的dp与设计稿px的比例
  static double get ratio => instance.scaleWidth;

  ///根据屏幕宽度适配,实际的dp与设计稿px的比例
  get scaleWidth => _screenWidth / instance.width;

  /// 根据设计稿的设备高度适配
  /// 当发现设计稿中的一屏显示的与当前样式效果不符合时,
  /// 或者形状有差异时,高度适配建议使用此方法
  /// 高度适配主要针对想根据设计稿的一屏展示一样的效果
  get scaleHeight => _screenHeight / instance.height;

  ///默认根据宽度适配
  static double dp(double width) => instance.setWidth(width);

  ///根据设计稿的设备宽度适配
  ///高度也根据这个来做适配可以保证不变形
  double setWidth(double width) => width * scaleWidth; //

  /// 高度也根据setWidth来做适配可以保证不变形(当你想要一个正方形的时候)
  ///setHeight方法主要是在高度上进行适配, 在你想控制UI上一屏的高度与实际中显示一样时使用.
  double setHeight(double height) => height * scaleHeight;

  ///字体大小适配方法
  static double sp(double fontSize) => instance.setSp(fontSize);

  ///字体大小适配方法,在 ios 中_textScaleFactor 不起作用
  ///@param fontSize 传入设计稿上字体的px ,
  ///@param allowFontScaling 控制字体是否要根据系统的“字体大小”辅助选项来进行缩放。默认值为true。
  ///@param allowFontScaling Specifies whether fonts should scale to respect Text Size accessibility settings. The default is true.
  double setSp(double fontSize) {
    return allowFontScaling
        ? setWidth(fontSize)
        : setWidth(fontSize) / _textScaleFactor;
  }
}

思路

1、默认375设计图

2、引入 'dart:ui' 获得屏幕尺寸相关信息

3、计算真实像素值

使用

//在build方法中进行初始化,后续方法中直接使用设置控件顶部间距10为设计尺寸,内部间距25为设计尺寸


  @override
  Widget build(BuildContext context) {
    super.build(context); //保存状态必须加上
    LcfarmSize.getInstance().init(context);
//    LogUtil.v(
//        "ModalRoute.of(context).settings.name=${ModalRoute.of(context).settings.name}");
    
  }
  
Container(

  margin: EdgeInsets.only(top: LcfarmSize.dp(10)),

  padding: EdgeInsets.all(LcfarmSize.dp(25)),

  child: Column(

    children: <Widget>[

      _buildPasswordField(),

      Container(

        margin: EdgeInsets.only(top: LcfarmSize.dp(60)),

        child: _buildNextBtn(),

      ),

    ],

  ),

);

// 设置文本大小 17 为设计图尺寸

 Text(
    '原登录密码',
    style: TextStyle(
         fontSize: LcfarmSize.sp(17),
     )
)

2、颜色管理

在flutter中,color使用的ARGB,

Widget _buildChild() {
    return  Container(
      margin: const EdgeInsets.all(10.0),
      color: const Color(0xFF0099ff),///0x 后面开始 两位FF表示透明度16进制,
///之后的0099ff 代表RGB色值
      height: 100.0,
      child:  Text('iam Container'),
    );
  }

Flutter 内置了一套 Material Design 的颜色系统,在应用里我们可以直接使用这个颜色系统里提供的各种不同的颜色。这些颜色是在 Colors 这个类里定义的一些静态属性,所以使用它们的时候一般就是用 Colors 这个类的名字后面加上要用的颜色的名字,比如 Colors.redColors.yellowColors.blue。

Widget _buildChild() {
    return new Container(
      margin: const EdgeInsets.all(10.0),
      color: const Color(0xFF0099ff),///0x 后面开始 两位FF表示透明度16进制,
    ///之后的0099ff 代表RGB色值
      height: 100.0,
      child: new Text('iam Container',
        new TextStyle(
        color:Colors.red///这里是flutter内部封装的 同样的方式也可以使用Colors.black,
        ///这里另外需要注意的是,我们还可以这样color:Colors.red[100],给它一个值,
        ///值的范围是 100的整数倍,比如说这样color:Colors.red[300],
        ///color:Colors.red[600]呈线形变化
        )
    ),
    );

  }

自定义颜色使用

项目中颜色主要是以主色调加辅助色来搭配使得,颜色相对比较统一。

参考官方写法,对于项目中使用到的颜色统一使用LcfarmColor 来使用。

import 'dart:ui';

/// @desp:统一颜色管理
/// @time 2019/3/11 4:31 PM
/// @author chenyun
/// 格式argb [0-255]
///
class LcfarmColor {
  static const Color colorFFFFFF = const Color(0xffffffff);

  static const Color color00000000 = const Color(0x00000000);

  static const Color color80000000 = const Color(0x80000000);

  static const Color color333333 = const Color(0xff333333);

  static const Color color999999 = const Color(0xff999999);

  static const Color color666666 = const Color(0xff666666);

  static const Color color10BFC7 = const Color(0xff10BFC7);

  static const Color color8010BFC7 = const Color(0x8010BFC7);
}

约定 :
命名采用color+6或8位颜色数值组成,8位时前2位是16进制的透明值

使用如下:

RaisedButton(
  onPressed: _isBtnEnabled ? _btnClick() : null,
  child: Text(
    '下一步',
    style: LcfarmStyle.styleFFFFFF_18,
  ),
  textColor: LcfarmColor.colorFFFFFF,
  disabledTextColor: LcfarmColor.colorFFFFFF,
  color: LcfarmColor.color10BFC7,
  disabledColor: LcfarmColor.color8010BFC7,
  elevation: 0,
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(LcfarmSize.dp(23)),
  ),
)

3、样式管理

在项目中经常使用 Text 控件,必然要设置其颜色、字体大小等。则必须使用 TextStyle 设置样式。项目中大多 Text 样式是相同的,设计也希望按一定规范去设计。可以参考布谷农场设计规范(如上图)。

从 规范中可以看出颜色与字体大小的组合是有限的,很多地方可能会反复写一些重复代码。

常用写法

直接在控件代码中设置 TextStyle。

Text(
  '下一步',
  style: TextStyle(
    color: LcfarmColor.colorFFFFFF,
    fontSize: LcfarmSize.sp(18),
  ),
)

缺点

不灵活,不方便统一修改,代码重复

优化写法

对于样式相同的,在使用时多处设置其 TextStyle,不如直接封装出一个,使用时直接调用即可。
具体参与 LcfarmStyle

/// @desp: 格式 颜色_字号
/// @time 2019/3/13 4:44 PM
/// @author chenyun
///
class LcfarmStyle {
  ///白色相关字号
  static final TextStyle styleFFFFFF_12 = TextStyle(
    color: LcfarmColor.colorFFFFFF,
    fontSize: LcfarmSize.sp(12),
  );


  static final TextStyle styleFFFFFF_18 = TextStyle(
    color: LcfarmColor.colorFFFFFF,
    fontSize: LcfarmSize.sp(18),
  );


  ///灰色相关字号
  static final TextStyle style999999_12 = TextStyle(
    color: LcfarmColor.color999999,
    fontSize: LcfarmSize.sp(12),
  );

<font color=red>约定 :
只定义文本颜色、文本字体大小
命名采用 style+颜色(大写)+ 下划线+字体大小
</font>

上面代码可以优化成:

Text(
  '下一步',
  style: LcfarmStyle.styleFFFFFF_18,
)

优点

统一的管理方式,方便新增和修改</br>使用方便,自动提示

4、Icon引入管理

前端开发中,Icon的使用是必不可少,大多都可以通过icon图片图标及 iconfont字体图标来展示。
在 Flutter 中同样如此。

使用图片

这个主要就是配置一下 pubspec.yaml里的assets,在pubspec.yaml文件中的配置如下。

flutter:
  # To add Flutter specific assets to your application, add an assets section, 
  # like this:
  assets:
        - icons/

使用的话跟图片一样,缺点就是大小和颜色不太好控制,而且会增加项目的体积。

IconButton(
         iconSize: LcfarmSize.dp(18),
         icon: ImageIcon(
           AssetImage('icons/icon_clear.png'),
           color: Colors.blue,
         ),
         onPressed: () {
           _controller.clear();
         },
       )

使用 Iconfont

在Flutter中,可以像web开发一样使用iconfont,iconfont即“字体图标”,它是将图标做成字体文件,然后通过指定不同的字符而显示不同的图片。

在字体文件中,每一个字符都对应一个位码,而每一个位码对应一个显示字形,不同的字体就是指字形不同,即字符对应的字形是不同的。而在iconfont中,只是将位码对应的字形做成了图标,所以不同的字符最终就会渲染成不同的图标。

在Flutter开发中,iconfont和图片相比有如下优势:

  1. 体积小:可以减小安装包大小。
  2. 矢量的:iconfont都是矢量图标,放大不会影响其清晰度。
  3. 可以应用文本样式:可以像文本一样改变字体图标的颜色、大小对齐等。
  4. 可以通过TextSpan和文本混用。

使用Material Design字体图标

Flutter默认包含了一套Material Design的字体图标,在pubspec.yaml文件中的配置如下

flutter:
  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

Material Design所有图标可以在其官网查看:https://material.io/tools/icons/
使用如下:

IconButton(
          iconSize: LcfarmSize.dp(18),
          icon: Icon(Icons.clear),
          onPressed: () {
            _controller.clear();
          }
    )

通过这里可以看到,使用图标就像使用文本一样,Flutter封装了一个IconData和Icon来专门显示字体图标。</br>
Icons类中包含了所有Material Design图标的IconData静态变量定义。
内置图标还是挺多的,基本上还是够用的,但对于实际项目,往往图标都是设计师根据当前应用主调定制的,并不能完全满足应用需求。

使用自定义字体图标

对于实际项目还可以使用自定义字体图标。iconfont.cn上有很多字体图标素材,我们可以选择自己需要的图标打包下载后,会生成一些不同格式的字体文件,在Flutter中,我们使用ttf格式即可。

  • 将设计师提供的 svg 文件上传至 iconfont.cn 上对应的项目中
iconfont 管理.jpg

<font color="red">注意:这里的文件名统一采用小写+下划线,以便后续统一生成 icon统一规范。</font>

  • 下载字体文件到本地


    170ebdefdfeb6ec1.jpg
  • 将下载的字体文件放到某个目录下,比如说 fonts 文件夹下:


    font.jpg
  • 找到pubspec.yaml文件,添加引用路径

image

这里需要注意右边第一个fonts左边的空格,如果不对会引用不到,family是自定义的名字

  • 执行generate_iconfont.dart文件,生成自定义图标的 IconData 代码
image
  • 使用如下
IconButton(
  padding: widget.isPassword
      ? EdgeInsets.only(right: LcfarmSize.dp(40))
      : EdgeInsets.only(right: LcfarmSize.dp(0)),
  iconSize:
      !StringUtil.isEmpty(_controller.text) ? LcfarmSize.dp(18) : 0,
  icon: Icon(
    LcfarmIcon.icon_clear,
    color: LcfarmColor.color999999,
  ),
  onPressed: () {
    _controller.clear();
  },
)

最后

  如果在使用过程遇到问题,欢迎下方留言交流。

学习资料

请大家不吝点赞!因为您的点赞是对我最大的鼓励,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容