- 在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中有
theme
和dartTheme
两个参数,进行暗黑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);
}
}
屏幕适配第三方库
- flutter_screenutil
- 地址:https://github.com/OpenFlutter/flutter_screenutil
网友评论