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閱讀 228,238評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,430評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,134評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,653評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,136評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,372評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,888評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,738評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,179評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,610評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,916評論 2 372

推薦閱讀更多精彩內容