Flutter入门11 -- 主题与屏幕适配

  • 在Flutter开发中,我们可以通过定义 Theme,复用颜色和字体样式,从而让整个app的设计看起来更一致;
  • Theme分为全局Theme局部Theme
  • 主题的作用:
    • 设置了主题之后,某些Widget会自动使用主题的样式(比如AppBar的颜色);
    • 将某些样式放到主题中统一管理,在应用程序的其它地方直接引用;

全局Theme

  • 全局Theme会影响整个app的颜色和字体样式;
  • 使用起来非常简单,只需要向MaterialApp构造器传入ThemeData即可;
  • 如果没有设置Theme,Flutter将会使用预设的样式,当然我们可以对它进行定制;

局部Theme

  • 如果某个具体的Widget不希望直接使用全局的Theme,只需要在Widget的父节点包裹一下Theme即可;
  • 我们很多时候并不是想完全使用一个新的主题,而且在之前的主题基础之上进行修改;
  • 案例代码如下:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() => runApp(SFMyApp());

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: SFHomePage(),
      theme: ThemeData(
        //亮度 -- 可设置暗黑模式
        brightness: Brightness.light,
        //primarySwatch = primaryColor + accentColor
        primarySwatch: Colors.red,
        //决定导航与tabbar的颜色
        primaryColor: Colors.orange,
        //决定其他组件的颜色
        accentColor: Colors.green,
        //Button的主题
        buttonTheme: ButtonThemeData(
          height: 25,
          minWidth: 50,
          buttonColor: Colors.pink
        ),
        cardTheme: CardTheme(
          elevation: 15,
          color: Colors.purple
        ),
        textTheme: TextTheme(
          bodyText1: TextStyle(fontSize: 16),
          bodyText2: TextStyle(fontSize: 20),
        ),
      ),
    );
  }
}

class SFHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("基础widget")),
      body: Center(
          child: Column(
            children: [
              Text("Hello World!!"),
              Text("Hello World!!",style: Theme.of(context).textTheme.bodyText1,),
              Text("Hello World!!",style: Theme.of(context).textTheme.bodyText2,),
              Switch(value: true,onChanged: (value) {},),
              CupertinoSwitch(value: true,onChanged: (value) {},activeColor: Colors.red,),
              RaisedButton(child: Text("RaisedButton"),onPressed: () {},),
              Card(child: Text("liyanyan",style: TextStyle(fontSize: 30),),),
            ],
          )
        ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          Navigator.of(context).push(MaterialPageRoute(
            builder: (ctx) {
              return SFDetailPage();
            }
          ));
        },
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(
            title: Text("首页"),
            icon: Icon(Icons.home)
          ),
          BottomNavigationBarItem(
              title: Text("分类"),
              icon: Icon(Icons.category)
          )
        ],
      ),
    );
  }
}

class SFDetailPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Theme(
      data: Theme.of(context).copyWith(
        primaryColor: Colors.purple
      ),
      child: Scaffold(
        appBar: AppBar(
          title: Text("详情页"),
        ),
        body: Center(
          child: Text("详情页"),
        ),
// 这个地方修改floatingButton背景颜色有点特殊,请注意
        floatingActionButton: Theme(
          data: Theme.of(context).copyWith(
             colorScheme: Theme.of(context).colorScheme.copyWith(
               secondary: Colors.pink
             )
          ),
          child: FloatingActionButton(
            child: Icon(Icons.pets),
            onPressed: () {

            },
          ),
        ),
      ),
    );
  }
}
  • primarySwatch:primaryColor + accentColor

  • primaryColor:决定导航与tabbar的颜色

  • accentColor:决定其他组件的颜色

  • buttonTheme:所有按钮的主题

  • cardTheme:所有卡片的主题

  • textTheme:所有文本的主题

  • 新建一个详情页面SFDetailPage,可使用一个新的主题:最外层包裹Theme

Theme(
      data: ThemeData(
        primaryColor: Colors.purple
      ),
  • 也可以在首页主题上进行修改:
Theme(
      data: Theme.of(context).copyWith(
        primaryColor: Colors.greenAccent
      ),

暗黑Theme适配

  • 主要通过MaterialApp中有themedartTheme两个参数,进行暗黑Theme的适配;
  • 创建一个App主题类SFAppTheme,如下:
import 'package:flutter/material.dart';

class SFAppTheme {
  
  static const double normalFontSize = 20;
  static const double darkFontSize = 20;
  
  static final Color normalTextColor = Colors.red;
  static final Color darkTextColor = Colors.green;
  
  static final ThemeData normalTheme = ThemeData(
      primarySwatch: Colors.orange,
      textTheme: TextTheme(
          bodyText1: TextStyle(fontSize: normalFontSize,color:normalTextColor)
      )
  );

  static final ThemeData darkTheme = ThemeData(
      primarySwatch: Colors.grey,
      textTheme: TextTheme(
          bodyText1: TextStyle(fontSize: darkFontSize,color: darkTextColor)
      )
  );
}
  • 首页代码:
import 'package:Fluter01/day01/shared/SFAppTheme.dart';
import 'package:flutter/material.dart';

void main() => runApp(SFMyApp());

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: SFHomePage(),
      theme: SFAppTheme.normalTheme,
      darkTheme: SFAppTheme.darkTheme,
    );
  }
}

class SFHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("基础widget")),
      body: SFHomeContent()
    );
  }
}

class SFHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text("Hello World!!!"),
    );
  }
}

Flutter屏幕的适配

Flutter中的单位
  • 在进行Flutter开发时,我们通常不需要传入尺寸的单位;
  • Flutter使用的是类似于iOS中的点pt,也就是point;
rpx适配
  • rpx的适配原理,如下所示:
  • 不管是什么屏幕,统一分成750份
  • 在iPhone5上:1rpx = 320/750 = 0.4266 ≈ 0.42px
  • 在iPhone6上:1rpx = 375/750 = 0.5px
  • 在iPhone6plus上:1rpx = 414/750 = 0.552px
  • 那么我们就可以通过上面的计算方式,算出一个rpx,再将自己的size和rpx单位相乘即可:
  • 比如100px的宽度:100 * 2 * rpx
  • 在iPhone5上计算出的结果是84px
  • 在iPhone6上计算出的结果是100px
  • 在iPhone6plus上计算出的结果是110.4px
  • 封装一个屏幕适配工具类SFSizeFit,如下所示:
import 'dart:ui';

class SFSizeFit {
  static double physicalWidth;
  static double physicalHeight;
  static double screenWidth;
  static double screenHeight;
  static double dpr;
  static double statusHeight;

  static double rpx;
  static double px;

  static void initialize({double standardSize = 750}) {
    //物理分辨率
    physicalWidth = window.physicalSize.width;
    physicalHeight = window.physicalSize.height;
    print("分辨率: $physicalWidth * $physicalHeight");

    //逻辑分辨率
    // final width = MediaQuery.of(context).size.width;
    // final height = MediaQuery.of(context).size.height;
    dpr = window.devicePixelRatio;

    screenWidth = physicalWidth / dpr;
    screenHeight = physicalHeight / dpr;
    print("屏幕宽高: $screenWidth * $screenHeight");

    //状态栏的高度
    statusHeight = window.padding.top / dpr;
    print("状态栏的高度: $statusHeight");

    rpx = screenWidth / standardSize;
    px = screenWidth / standardSize * 2;
  }

  static double setRpx(double size) {
    return size * rpx;
  }

  static double setPx(double size) {
    return size * px;
  }
}
  • SFSizeFit的使用:
import 'package:Fluter01/day01/shared/SFSizeFit.dart';
import 'package:flutter/material.dart';

void main() => runApp(SFMyApp());

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    
    SFSizeFit.initialize();
    
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: Text("基础widget")),
        body: Center(
          child: Container(
            width: SFSizeFit.setPx(200),
            height: SFSizeFit.setPx(200),
            color: Colors.red,
          ),
        )
    );
  }
}
  • 利用扩展Extension对上述代码进行重构
import 'package:Fluter01/day01/shared/SFSizeFit.dart';

extension SFDoubleFit on double {
  double px() {
    return SFSizeFit.setPx(this);
  }

  double rpx() {
    return SFSizeFit.setRpx(this);
  }
}
import 'package:Fluter01/day01/shared/SFSizeFit.dart';

extension SFIntFit on int {
  double get px {
    return SFSizeFit.setPx(this.toDouble());
  }

  double get rpx {
    return SFSizeFit.setRpx(this.toDouble());
  }
}
  • 对double与int进行方法扩展,最后首页调用就变得更加简单了,如下:
import 'package:Fluter01/day01/shared/SFSizeFit.dart';
import 'package:flutter/material.dart';
import 'package:Fluter01/day01/extension/SFIntFit.dart';

void main() => runApp(SFMyApp());

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final String message = "Hello World";
    final result = message.sf_split(" ");

    print(result);
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: Text("基础widget")),
        body: Center(
          child: Container(
            width: 200.px,
            height: 200.px,
            color: Colors.red,
          ),
        )
    );
  }
}

extension StringSplit on String {
  List<String> sf_split(String split) {
    return this.split(split);
  }
}

屏幕适配第三方库

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

推荐阅读更多精彩内容