iT邦幫忙

2024 iThome 鐵人賽

DAY 14
0

或許大家應該都贊同;一個精美流暢的使用者介面可以協助提升使用者體驗,增加黏著度。

Flutter 支援很多方式可以加入樣式。首先我們會聚焦在靜態圖片,組件在閒置時、互動,啟用等情況下的外觀。然後進一步在後續學習使用動畫以及圖片。這大概是相對有趣的部分,因為可以讓組件產生明顯的變化。

在之前的範例我們使用了 TextImageContainer 等等組件來組織佈局,呈現內容。我們已經知道一些基本改變呈現的方式,接著我們將進一步深入:

顏色

任何時候當我們希望變更組件的顏色的時候,必須了解如何使用顏色 Color 類別。其中最簡單的方式就是使用包含了許多內建的顏色的 Colors 。在之前的範例我們已經使用過了例如:

Container(
  color: Colors.grey, // <- 這裡
)

如我們所預期的,Colors.grey 會回傳對應的顏色。除了預設顏色之外,還可以進一步指定顏色深淺。若你使用過 Material Design 設計規範或相關框架應該理解調色盤色票。Material Design 規範的色彩系統,也就是包含了一些預先定義的色票。每一個主色還定義了 50, 100 - 900 的漸層色號,數字越小越淡。
下面表示相同顏色,你也可以調整 50, 100 - 900 的深淺顏色。

Colors.grey
Colors.grey[400]
Colors.grey.shade400

若你不想使用預先建立的色彩系統,你可以使用 Hex 或 RGB

var color = Color(0xff32a852);

上面程式碼會使用 Hex 構建一個 Color 的物件,0x 表示是一個 16 進制值。

8位元和6位元的 Hex

常見的 Hex 顏色參數使用 6 位來表達例如在 CSS 中的 #0000ff。但在 Flutter 中,Color 預設建構子不支持 6 位的 Hex 顏色值。Color 類別是一個不可變的 32 位 ARGB 顏色值。因此須使用 8 位來表示,前兩位表示透明度。

Color(0xFF00FF00) ARGB 透明度計算

當你看到 Color(0xFF00FF00),這個顏色值是按照 ARGB 的方式來解釋的:

  • FF 是 Alpha (透明度) 值,表示完全不透明。
  • 00 是 Red (紅色) 值。
  • FF 是 Green (綠色) 值。
  • 00 是 Blue (藍色) 值。

透明度的部分和一般我們使用的 0 ~ 1000.x ~ 1 設值方式不太一樣,也不太直覺。它的計算方式是 255 * [希望的%] ~= 設定值。例如 50% 255 * 0.5 ≈ 128,在 16 進制中為 80

其他範例:

Color c1 = const Color(0xFF42A5F5);
Color c2 = const Color.fromARGB(0xFF, 0x42, 0xA5, 0xF5);
Color c3 = const Color.fromARGB(255, 66, 165, 245);
Color c4 = const Color.fromRGBO(66, 165, 245, 1.0);

漸層色

除了單一色,Flutter 也支持漸層色,可以通過 LinearGradientRadialGradient 實現:

Container(
  decoration: BoxDecoration(
    gradient: LinearGradient(
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
      colors: [Colors.blue, Colors.green],
    )
  ),
)

字體樣式

文字算是所有組件中最基本與常用的元素之一。前面我們已經知道可以使用 Text 來顯示內容

Text("我們的內容")

Text 很常作為其他組件的子元素用於顯示文字內容,如果沒有特別指定樣式,一般會套用定義在應用程式的 Theme 的預設字體樣式。如果要自訂樣式,可以使用 style ,這個參數便是為了這個需求而設計的。

該參數需要搭配TextStyle 物件作為參數,下面就是 TextStyle 一些主要參數:

  • backgroundColor 用於設置文字背景色。同樣接受一個 Color 物件作為值。
  • color 此參數可以設定字體顏色,一樣使用 Color 為其值。
  • decoration 用於為文字裝飾,如下劃線、上劃線或刪除線等。可以使用 TextDecoration 類進行設置,如 TextDecoration.underline 表示下劃線。
  • fontSize 用於設置字體的大小。在 Flutter 中,字體大小使用邏輯像素為單位 px,這是一種與設備無關的單位,會根據設備的像素密度自動縮放,以確保在不同解析度的螢幕上字體大小保持一致。一般來說,小字的大小約為 6-8 px,一般字為 12-16 px ,較大的字體大約落在 30-60 px。
  • fontWeight 用於設置字體的粗細。它使用 FontWeight 類進行設置,其值範圍從 FontWeight.w100 (最細)到 FontWeight.w900 (最粗)。
  • fontStyle 設置字體的樣式,如斜體。可以使用 FontStyle 類進行設置,如 FontStyle.italic 表示斜體。
  • wordSpacingletterSpacing 這兩個屬性分別控制單詞之間和字母之間的間距。它們接受一個 double 值,表示間距的大小。
  • overflow 當文字內容超出其容器的範圍,且沒有使用捲軸時,可以使用 overflow 屬性控制文字的顯示方式。它接受 TextOverflow enum 值,如 TextOverflow.clip (裁切多餘的文字)、TextOverflow.ellipsis (在末尾顯示省略號 ... )或 TextOverflow.fade (多餘的文字淡出)。

下面為使用範例:

Text(
	"你的文字",
  style: TextStyle(
  	backgroundColor: Color.fromRGBO(10, 150, 80, 1),
    color: Colors.red,
    fontSize: 16,
    fontWeight: FontWeight.w300,
    fontStyle: FontStyle.italic,
    letterSpacing: 2,
    wordSpacing: 3,
    overflow: TextOverflow.fade,
    decoration: TextDecoration.underline
  ),
)

如果你有 CSS 的開發經驗,可能會發現 Flutter 中的一些值需要使用特定的物件或 enum 進行設定,而不是直接指定具體的值。這是因為 Flutter 使用了強型別的設計,提供了更好的型別安全性和可讀性。但確實新手需要花點時間習慣。

另外,3.x 之後還支援了 RichTextTextSpan 協助我們處理複雜的文字組合呈現:

RichText(
  text: TextSpan(
    text: '哈嘍 ',
    style: DefaultTextStyle.of(context).style,
    children: const <TextSpan>[
      TextSpan(text: '粗體字', style: TextStyle(fontWeight: FontWeight.bold)),
      TextSpan(text: ' 世界!'),
    ],
  ),
)

容器樣式

Container 是一個排版蠻常使用的組件,一樣也可以定義樣式。主要使用 decoration 參數搭配 Decoration 類別或者更準確的說,是 Decoration 的子類別,因為 Decoration 的用途類似於抽象類別。

雖然這裡我們是用 Container 作為例子,但這些知識也適用於其他有 decoration 的組件。

對照上面的 TextDecoration 你可以更清楚所謂的子類別。

下面是一般 Container 的範例

Container(
	child: Text(widget.name)
)

在容器內部我們傳入了 Text,接著讓我們為 Container 加入一些樣式例如簡單的下方邊線,這個效果會很接近文字的底線,但這次我們不是使用 TextStyle,而是使用 BoxDecoration 如下

Container(
  decoration: const BoxDecoration(
      border: Border(
    bottom: BorderSide(
      color: Colors.blue,
      width: 3,
    ),
  )),
  child: Text(widget.name)
)

上面範例我們使用了 BoxDecoration 然後使用 border 參數和其對應設值的物件。我們可以使用 Border 個別指定每一邊的樣式或者使用 Border.allBorder.symmetric 工廠建構子來設定。上面例子我們指定了 bottom 的樣式。

除此之外,為了讓你更快的熟悉實戰,我們也加入了 const ,在 Flutter 專案中為了改善效能,如果不會變動的組件建議使用 const 一般來說你的開發編輯器應該也會提示你。

總之,上面範例我們在文字下方建立了一個很像底線的效果。容器有很多樣式可以使用,後續你應該需要花點時間學習這些效果如何使用和搭配,這裡我們再提供另一個範例:

Container(
	decoration: ShapeDecoration(
  	shape: StadiumBorder(
    	side: BorderSide(
      	color: Colors.green,
        width: 6,
      ),
    ),
  ),
  child: Text(widget.name)
)

使用圖片

Container(
  width: 300,
  height: 200,
  decoration: BoxDecoration(
    image: DecorationImage(
      image: NetworkImage('https://example.com/image.jpg'),
      fit: BoxFit.cover,
    ),
    borderRadius: BorderRadius.circular(20),
  ),
)

按鈕

在之前的範例我們已經使用過一些按鈕組件,Flutter 提供了多種按鈕組件,如 ElevatedButtonTextButton,和 OutlinedButton

這些按鈕可以使用 ButtonStyle 類別作為 style 參數的值。我們可以選擇需要的樣式屬性,只要在 ButtonStyle 覆寫屬性即可,未定義的屬性將保持預設值。

例如要修改 ElevatedButton 的對齊方式如下:

ElevatedButton(
	onPressed: () {},
  child: Text("按鈕"),
  style: ButtonStyle(
  	alignment: Alignment.centerLeft,
  ),
)

常用的 ButtonStyle 如下:

  • backgroundColor: 設置按鈕的背景色。
  • foregroundColor: 設置按鈕的前景色,即文字和圖標的顏色。
  • overlayColor: 設置按鈕在按下時的覆蓋色。
  • shadowColor: 設置按鈕的陰影顏色。
  • elevation: 設置按鈕的陰影高度。
  • padding: 設置按鈕的內邊距。
  • shape: 設置按鈕的形狀,例如矩形、圓角矩形、圓形等。
  • side: 設置按鈕的邊框。
  • textStyle: 設置按鈕文字的樣式。

然而一個按鈕包含了多個不同的狀態,例如活動狀態 Active,停用 Disable,被點擊等,樣式需要配合這些狀態。一個方法是使用 MaterialStateProperty 或其子類別如 MaterialStateColor 支援讀取按鈕狀態,你就可以決定要設定什麼樣式。

例如:我們希望按鈕背景樣式是綠色,但被按下的時候變藍色

ElevatedButton(
	onPressed: () {},
  child: Text("按鈕"),
  style: ButtonStyle(
  	backgroundColor: MaterialStateProperty.resolveWith<Color>(
      (Set<MaterialState> states) {
      	if (states.contains(MaterialState.pressed)) {
          return Colors.blue;
        }
        return Colors.green;
    	}
    )
  )
)

這樣的使用方式,第一次看到的時候也許會感到很困惑,尤其是一些已經很習慣網頁應用程式的開發者。不過上面我們讓背景色根據狀態產生變化。通過 resolveWith 靜態方法和其提供的 MaterialState 然後依據狀態回傳顏色。

以下是一些常見的 MaterialState 值:

  • MaterialState.pressed: 按鈕處於被按下的狀態。
  • MaterialState.hovered: 按鈕處於被懸停的狀態。
  • MaterialState.focused: 按鈕處於獲得焦點的狀態。
  • MaterialState.disabled: 按鈕處於禁用的狀態。
  • MaterialState.error: 按鈕處於錯誤的狀態。

如同一開始提到的 MaterialStateColorMaterialStateProperty 的子類別,因此下面的寫法也是等價的:

backgroundColor: MaterialStateColor.resolveWith((states) {
  if (states.contains(MaterialState.pressed)) {
    return Colors.blue;
  }
  return Colors.green;
})

ButtonStyle 的其他屬性也可以使用類似 MaterialStateProperty 的用法,讓我們能夠在按鈕不同狀態下靈活控制樣式。

到此希望讓你對於 Flutter 中按鈕組件動態樣式的處理有了基本的概念。這種方法雖然初看起來可能有些複雜,但它提供了非常靈活的方式來處理不同狀態下的樣式變化。

響應式佈局

Flutter 的佈局系統支援不同螢幕尺寸的對應不同的設計,即網頁常見的響應式設計。LayoutBuilder 是實現這一目標的關鍵組件

LayoutBuilder(
  builder: (BuildContext context, BoxConstraints constraints) {
    if (constraints.maxWidth > 600) {
      return WideLayout();
    } else {
      return NarrowLayout();
    }
  },
)

通過熟悉這些組件的使用,我們將越來越具備開發自己心目中或者需求應用程式的能力。


上一篇
Day 13 表單與欄位
下一篇
Day 15 進階 UI 組件
系列文
Flutter 開發實戰 - 30 天逃離新手村26
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言