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);
  }
}

屏幕适配第三方库

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容