flutter 主题风格、屏幕适配 主题风格、屏幕适配

主题风格、屏幕适配

主题

样式统一管理

全局样式

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // 1.亮度: light-dark
        brightness: Brightness.light,
        // 2.primarySwatch: primaryColor/accentColor的结合体
        primarySwatch: Colors.red,
        // 3.主要颜色: 导航/底部TabBar
        primaryColor: Colors.pink,
        // 4.次要颜色: FloatingActionButton/按钮颜色
        accentColor: Colors.orange,
        // 5.卡片主题
        cardTheme: CardTheme(
          color: Colors.greenAccent,
          elevation: 10,
          shape: Border.all(width: 3, color: Colors.red),
          margin: EdgeInsets.all(10)
        ),
        // 6.按钮主题
        buttonTheme: ButtonThemeData(
          minWidth: 0,
          height: 25
        ),
        // 7.文本主题
        textTheme: TextTheme(
          title: TextStyle(fontSize: 30, color: Colors.blue),
          display1: TextStyle(fontSize: 10),
        )
      ),
      home: XXTHomePage(),
    );
  }
}

Text组件使用全局主题:

body: Center(
        child: Column(
          children: <Widget>[
            Text("Hello World"),
            Text("Hello World", style: TextStyle(fontSize: 14),),
            Text("Hello World", style: TextStyle(fontSize: 20),),
            Text("Hello World", style: Theme.of(context).textTheme.body2,),
            Text("Hello World", style: Theme.of(context).textTheme.display3,),
            Switch(value: true, onChanged: (value) {},),
            CupertinoSwitch(value: true, onChanged: (value) {}, activeColor: Colors.red,),
            RaisedButton(child: Text("R"), onPressed: () {},),
            Card(child: Text("你好啊,李银河", style: TextStyle(fontSize: 50),),)
          ],
        ),
      ),

局部Theme

如果某个具体的Widget不希望直接使用全局的Theme,而希望自己来定义,应该如何做呢?
非常简单,只需要在Widget的父节点包裹一下Theme即可
创建另外一个新的页面,页面中使用新的主题:
在新的页面的Scaffold外,包裹了一个Theme,并且设置data为一个新的ThemeData

class XXTSecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Theme(
      data: ThemeData(),
      child: Scaffold(
      ),
    );
  }
}

新的主题
但是,我们很多时候并不是想完全使用一个新的主题,而且在之前的主题基础之上进行修改:

class XXTSecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Theme(
      data: Theme.of(context).copyWith(
        primaryColor: Colors.greenAccent
      ),
      child: Scaffold(
      ),
    );
  }
}
特殊问题: accentColor在这里并不会被覆盖。
//页面内局部theme,无法改变accentColor,widget内设置accentColor也不生效
Theme(
      data: Theme.of(context).copyWith(
        accentColor: Colors.greenAccent
      ),
      child: FloatingActionButton(
      ),
    );


//需要通过以下方式设置
floatingActionButton: Theme(
          data: Theme.of(context).copyWith(
            colorScheme: Theme.of(context).colorScheme.copyWith(
              secondary: Colors.pink
            )
          ),
          child: FloatingActionButton(
            child: Icon(Icons.pets),
            onPressed: () {
            },
          ),
        )

官方回答

页面背景色

项目一般都是有自己的颜色风格,设置统一的主题,页面背景色等。使用canvasColor,然后某页面如果有特殊的背景色,可以在Scafold中设置backgroundColor

暗黑模式适配

MaterialApp中有theme和dartTheme两个参数:
按照下面的写法,我们已经默认适配了暗黑主题

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData.light(),
      darkTheme: ThemeData(
            primaryColor: Colors.grey,
            primaryTextTheme: TextTheme(
                title: TextStyle(
                    color: Colors.white,
                    fontSize: _titleFontSize
                )
            ),
            textTheme: TextTheme(
                title: TextStyle(color: Colors.white),
                body1: TextStyle(color: Colors.white70)
            )
        );
      home: XXTHomePage(),
    );
  }
}

屏幕适配

Flutter中的单位

在进行Flutter开发时,我们通常不需要传入尺寸的单位,那么Flutter使用的是什么单位呢?

  • Flutter使用的是类似于iOS中的点pt,也就是point。
  • 所以我们经常说iPhone6的尺寸是375x667,但是它的分辨率其实是750x1334。
  • 因为iPhone6的dpr(devicePixelRatio)是2.0,iPhone6plus的dpr是3.0

iPhone设备参数
在Flutter开发中,我们使用的是对应的逻辑分辨率

Flutter设备信息

// 1.媒体查询信息
final mediaQueryData = MediaQuery.of(context);

// 2.获取宽度和高度
final screenWidth = mediaQueryData.size.width;
final screenHeight = mediaQueryData.size.height;
final physicalWidth = window.physicalSize.width;
final physicalHeight = window.physicalSize.height;
final dpr = window.devicePixelRatio;
print("屏幕width:$screenWidth height:$screenHeight");
print("分辨率: $physicalWidth - $physicalHeight");
print("dpr: $dpr");

// 3.状态栏的高度
// 有刘海的屏幕:44 没有刘海的屏幕为20
final statusBarHeight = mediaQueryData.padding.top;
// 有刘海的屏幕:34 没有刘海的屏幕0
final bottomHeight = mediaQueryData.padding.bottom;
print("状态栏height: $statusBarHeight 底部高度:$bottomHeight");

注意一个知识点:

获取屏幕宽高的时候,如果是在MyApp的build函数中执行的,会报错,大致意思是MediaQuery还没有初始化完,这个可以看下MediaQuery的源码

源码流程:

//1、获取屏幕宽度的代码:
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final double width = MediaQuery.of(context).size.width;
    return MaterialApp(
    );
  }
}


//2、width是从size获取,size是从MediaQuery.of(context)得来,那么看MediaQuery.of(context)的逻辑
  static MediaQueryData of(BuildContext context) {
    assert(context != null);
    assert(debugCheckHasMediaQuery(context));
    return context.dependOnInheritedWidgetOfExactType<MediaQuery>()!.data;
  }
  
of函数获取是的MediaQueryData,并且是基于MediaQuery获取的,然后获取data,,这能感觉到是先生成的data,然后通过of(context)获取的


//3、那么就找data初始化的地方。
  @override
  Widget build(BuildContext context) {
    MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
    ...
    ...
  }
  
//3.1、看MediaQueryData初始化的逻辑,,代码就略了,,内部看到也是基于window获取,然后计算的,,所以,可以有一些启发,,外部我们使用的时候即使不用MediaQuery,自己也可以通过window计算


//分析

以上是MediaQuery.of(context)的代码逻辑,重点是MediaQuery.of(context)函数中的断言,debugCheckHasMediaQuery(context),,这里给出了原因:
  

No MediaQuery ancestor could be found starting from the context that was passed to MediaQuery.of(). This can happen because you have not added a WidgetsApp, CupertinoApp, or MaterialApp widget (those widgets introduce a MediaQuery), or it can happen if the context you use comes from a widget above those widgets.

大致意思就是用到MediaQueryData的时候,但是WidgetsApp, CupertinoApp, or MaterialApp还不存在,或者说还没创建完,,,

总结:所以,如果要用MediaQuery.of(context)获取屏幕参数,不要在程序入口的build内获取,,,或者,通过window自己去获取计算

获取一些设备相关的信息,可以使用官方提供的一个库:

dependencies:
  device_info: ^0.4.2+1

适配方案

小程序中以iPhone6作为设计稿,宽度375,分辨率750

rpx适配:小程序中rpx的原理是什么呢?

不管是什么屏幕,统一分成750份

在iPhone5上:1rpx = 320/750 = 0.4266 ≈ 0.42px

在iPhone6上:1rpx = 375/750 = 0.5px

在iPhone6plus上:1rpx = 414/750 = 0.552px

。。。

  • 屏幕适配也可以使用第三方库:flutter_screenutil

flutter_screenutil

工具封装

工具类方式

class XXTSizeFit {
  // 1.基本信息
  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}) {
    // 1.手机的物理分辨率
    physicalWidth = window.physicalSize.width;
    physicalHeight = window.physicalSize.height;

    // 2.获取dpr
    dpr = window.devicePixelRatio;

    // 3.宽度和高度
    screenWidth = physicalWidth / dpr;
    screenHeight = physicalHeight / dpr;

    // 4.状态栏高度
    statusHeight = window.padding.top / dpr;

    // 5.计算rpx的大小
    rpx = screenWidth / standardSize;
    px = screenWidth / standardSize * 2;
  }

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

  static double setPx(double size) {
    return px * size;
  }
}

使用

body: Center(
        child: Container(
          width: XXTSizeFit.setPx(200),
          height: XXTSizeFit. setRpx(400),
          color: Colors.red,
          alignment: Alignment.center
        ),
      )

extension

extension DoubleFit on double {

  double px() {
    return XXTSizeFit.setPx(this);
  }

  double rpx() {
    return XXTSizeFit.setRpx(this);
  }
}

使用

body: Center(
        child: Container(
          width: 200.px(),
          height: 400.rpx(),
          color: Colors.red,
          alignment: Alignment.center
        ),
      )

这种方式是不是比单纯的工具类方式方便多了,extension是dart语法,,跟ios的category差不多,category是种类/分类的意思

flutter中extension的使用也大致有两个好处:

  • 增加类自定义的方法
  • 扩展可以更好的分类方法集

但是200.px()这种方式使用还是不像android中200px那样简洁,,给extension扩展get方法

extension - get方法

extension DoubleFit on double {
  double get px {
    return XXTSizeFit.setPx(this);
  }

  double get rpx {
    return XXTSizeFit.setRpx(this);
  }
}

使用

body: Center(
        child: Container(
          width: 200.px,
          height: 400.rpx,
          color: Colors.red,
          alignment: Alignment.center
        )
      )

注意,扩展的get方法不需要写()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章