- 在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 + accentColorprimaryColor
:決定導航與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