Flutter實(shí)戰(zhàn) 顏色和主題

2021-03-08 11:46 更新

#7.4.1 顏色

在介紹主題前我們先了解一些 Flutter 中的 Color 類。Color 類中顏色以一個(gè)int值保存,我們知道顯示器顏色是由紅、綠、藍(lán)三基色組成,每種顏色占8比特,存儲(chǔ)結(jié)構(gòu)如下:

Bit(位) 顏色
0-7 藍(lán)色
8-15 綠色
16-23 紅色
24-31 Alpha (不透明度)

上面表格中的的字段在 Color 類中都有對應(yīng)的屬性,而 Color 中的眾多方法也就是操作這些屬性的,由于大多比較簡單,讀者可以查看類定義了解。在此我們主要討論兩點(diǎn):色值轉(zhuǎn)換和亮度。

#如何將顏色字符串轉(zhuǎn)成Color對象

如 Web 開發(fā)中的色值通常是一個(gè)字符串如"#dc380d",它是一個(gè) RGB 值,我們可以通過下面這些方法將其轉(zhuǎn)為 Color 類:

Color(0xffdc380d); //如果顏色固定可以直接使用整數(shù)值
//顏色是一個(gè)字符串變量
var c = "dc380d";
Color(int.parse(c,radix:16)|0xFF000000) //通過位運(yùn)算符將Alpha設(shè)置為FF
Color(int.parse(c,radix:16)).withAlpha(255)  //通過方法將Alpha設(shè)置為FF

#顏色亮度

假如,我們要實(shí)現(xiàn)一個(gè)背景顏色和 Title 可以自定義的導(dǎo)航欄,并且背景色為深色時(shí)我們應(yīng)該讓 Title 顯示為淺色;背景色為淺色時(shí),Title 顯示為深色。要實(shí)現(xiàn)這個(gè)功能,我們就需要來計(jì)算背景色的亮度,然后動(dòng)態(tài)來確定 Title 的顏色。Color 類中提供了一個(gè)computeLuminance()方法,它可以返回一個(gè)[0-1]的一個(gè)值,數(shù)字越大顏色就越淺,我們可以根據(jù)它來動(dòng)態(tài)確定Title的顏色,下面是導(dǎo)航欄 NavBar 的簡單實(shí)現(xiàn):

class NavBar extends StatelessWidget {
  final String title;
  final Color color; //背景顏色


  NavBar({
    Key key,
    this.color,
    this.title,
  });


  @override
  Widget build(BuildContext context) {
    return Container(
      constraints: BoxConstraints(
        minHeight: 52,
        minWidth: double.infinity,
      ),
      decoration: BoxDecoration(
        color: color,
        boxShadow: [
          //陰影
          BoxShadow(
            color: Colors.black26,
            offset: Offset(0, 3),
            blurRadius: 3,
          ),
        ],
      ),
      child: Text(
        title,
        style: TextStyle(
          fontWeight: FontWeight.bold,
          //根據(jù)背景色亮度來確定Title顏色
          color: color.computeLuminance() < 0.5 ? Colors.white : Colors.black,
        ),
      ),
      alignment: Alignment.center,
    );
  }
}

測試代碼如下:

Column(
  children: <Widget>[
    //背景為藍(lán)色,則title自動(dòng)為白色
    NavBar(color: Colors.blue, title: "標(biāo)題"), 
    //背景為白色,則title自動(dòng)為黑色
    NavBar(color: Colors.white, title: "標(biāo)題"),
  ]
)

運(yùn)行效果如圖7-4所示:

#MaterialColor

MaterialColor是實(shí)現(xiàn) Material Design 中的顏色的類,它包含一種顏色的10個(gè)級(jí)別的漸變色。MaterialColor通過"[]"運(yùn)算符的索引值來代表顏色的深度,有效的索引有:50,100,200,…,900,數(shù)字越大,顏色越深。MaterialColor的默認(rèn)值為索引等于500的顏色。舉個(gè)例子,Colors.blue是預(yù)定義的一個(gè)MaterialColor類對象,定義如下:

static const MaterialColor blue = MaterialColor(
  _bluePrimaryValue,
  <int, Color>{
     50: Color(0xFFE3F2FD),
    100: Color(0xFFBBDEFB),
    200: Color(0xFF90CAF9),
    300: Color(0xFF64B5F6),
    400: Color(0xFF42A5F5),
    500: Color(_bluePrimaryValue),
    600: Color(0xFF1E88E5),
    700: Color(0xFF1976D2),
    800: Color(0xFF1565C0),
    900: Color(0xFF0D47A1),
  },
);
static const int _bluePrimaryValue = 0xFF2196F3;

Colors.blue[50]Colors.blue[900]的色值從淺藍(lán)到深藍(lán)漸變,效果如圖7-5所示:

NavBar

#7.4.2 Theme

Theme組件可以為 Material APP 定義主題數(shù)據(jù)(ThemeData)。Material 組件庫里很多組件都使用了主題數(shù)據(jù),如導(dǎo)航欄顏色、標(biāo)題字體、Icon 樣式等。Theme內(nèi)會(huì)使用InheritedWidget來為其子樹共享樣式數(shù)據(jù)。

#ThemeData

ThemeData用于保存是 Material 組件庫的主題數(shù)據(jù),Material 組件需要遵守相應(yīng)的設(shè)計(jì)規(guī)范,而這些規(guī)范可自定義部分都定義在 ThemeData 中了,所以我們可以通過 ThemeData 來自定義應(yīng)用主題。在子組件中,我們可以通過Theme.of方法來獲取當(dāng)前的ThemeData。

注意:Material Design 設(shè)計(jì)規(guī)范中有些是不能自定義的,如導(dǎo)航欄高度,ThemeData 只包含了可自定義部分。

我們看看ThemeData部分?jǐn)?shù)據(jù)定義:

ThemeData({
  Brightness brightness, //深色還是淺色
  MaterialColor primarySwatch, //主題顏色樣本,見下面介紹
  Color primaryColor, //主色,決定導(dǎo)航欄顏色
  Color accentColor, //次級(jí)色,決定大多數(shù)Widget的顏色,如進(jìn)度條、開關(guān)等。
  Color cardColor, //卡片顏色
  Color dividerColor, //分割線顏色
  ButtonThemeData buttonTheme, //按鈕主題
  Color cursorColor, //輸入框光標(biāo)顏色
  Color dialogBackgroundColor,//對話框背景顏色
  String fontFamily, //文字字體
  TextTheme textTheme,// 字體主題,包括標(biāo)題、body等文字樣式
  IconThemeData iconTheme, // Icon的默認(rèn)樣式
  TargetPlatform platform, //指定平臺(tái),應(yīng)用特定平臺(tái)控件風(fēng)格
  ...
})

上面只是ThemeData的一小部分屬性,完整的數(shù)據(jù)定義讀者可以查看 SDK。上面屬性中需要說明的是primarySwatch,它是主題顏色的一個(gè)"樣本色",通過這個(gè)樣本色可以在一些條件下生成一些其它的屬性,例如,如果沒有指定primaryColor,并且當(dāng)前主題不是深色主題,那么primaryColor就會(huì)默認(rèn)為primarySwatch指定的顏色,還有一些相似的屬性如accentColor 、indicatorColor等也會(huì)受primarySwatch影響。

#示例

我們實(shí)現(xiàn)一個(gè)路由換膚功能:

class ThemeTestRoute extends StatefulWidget {
  @override
  _ThemeTestRouteState createState() => new _ThemeTestRouteState();
}


class _ThemeTestRouteState extends State<ThemeTestRoute> {
  Color _themeColor = Colors.teal; //當(dāng)前路由主題色


  @override
  Widget build(BuildContext context) {
    ThemeData themeData = Theme.of(context);
    return Theme(
      data: ThemeData(
          primarySwatch: _themeColor, //用于導(dǎo)航欄、FloatingActionButton的背景色等
          iconTheme: IconThemeData(color: _themeColor) //用于Icon顏色
      ),
      child: Scaffold(
        appBar: AppBar(title: Text("主題測試")),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            //第一行Icon使用主題中的iconTheme
            Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Icon(Icons.favorite),
                  Icon(Icons.airport_shuttle),
                  Text("  顏色跟隨主題")
                ]
            ),
            //為第二行Icon自定義顏色(固定為黑色)
            Theme(
              data: themeData.copyWith(
                iconTheme: themeData.iconTheme.copyWith(
                    color: Colors.black
                ),
              ),
              child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Icon(Icons.favorite),
                    Icon(Icons.airport_shuttle),
                    Text("  顏色固定黑色")
                  ]
              ),
            ),
          ],
        ),
        floatingActionButton: FloatingActionButton(
            onPressed: () =>  //切換主題
                setState(() =>
                _themeColor =
                _themeColor == Colors.teal ? Colors.blue : Colors.teal
                ),
            child: Icon(Icons.palette)
        ),
      ),
    );
  }
}

運(yùn)行后點(diǎn)擊右下角懸浮按鈕則可以切換主題,如圖7-6、7-7所示:

圖7-6圖7-7

需要注意的有三點(diǎn):

  • 可以通過局部主題覆蓋全局主題,正如代碼中通過 Theme 為第二行圖標(biāo)指定固定顏色(黑色)一樣,這是一種常用的技巧,F(xiàn)lutter 中會(huì)經(jīng)常使用這種方法來自定義子樹主題。那么為什么局部主題可以覆蓋全局主題?這主要是因?yàn)?widget 中使用主題樣式時(shí)是通過Theme.of(BuildContext context)來獲取的,我們看看其簡化后的代碼:

  • static ThemeData of(BuildContext context, { bool shadowThemeOnly = false }) {
     // 簡化代碼,并非源碼  
     return context.dependOnInheritedWidgetOfExactType<_InheritedTheme>().theme.data
    }

context.dependOnInheritedWidgetOfExactType 會(huì)在 widget 樹中從當(dāng)前位置向上查找第一個(gè)類型為_InheritedTheme的 widget。所以當(dāng)局部指定Theme后,其子樹中通過Theme.of()向上查找到的第一個(gè)_InheritedTheme便是我們指定的Theme。

  • 本示例是對單個(gè)路由換膚,如果想要對整個(gè)應(yīng)用換膚,則可以去修改MaterialApptheme屬性。
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)