---
title: "Flutter LinearGradientの使い方｜透明が灰色に濁る原因と角度指定・アニメーション【3.47対応】"
url: "https://www.issoh.co.jp/tech/details/4030/"
published: 2024-11-01
updated: 2026-11-03
categories: ["Flutter"]
publisher: "株式会社一創"
---

# Flutter LinearGradientの使い方｜透明が灰色に濁る原因と角度指定・アニメーション【3.47対応】

FlutterのLinearGradientで終点に`Colors.transparent`を置くと、白から透明へ抜けるはずのグラデーションが途中で灰色に沈みます。実装ミスではありません。`Colors.transparent`は「透明な白」ではなく**透明な黒**として定義されているためです。Flutter 3.47.9（2026年10月29日のstable）で描画結果のピクセルを読み戻した値をもとに、原因と書き方、角度指定・`withOpacity`・ボタン・AppBar・アニメーションでの注意点を示します。

## まとめ

透明グラデーションが濁る原因は、`Colors.transparent`が`Color(0x00000000)`（RGBが黒のまま不透明度0）であることです。`Colors.white.withValues(alpha: 0)`か`Color(0x00FFFFFF)`で書けば直ります。ShaderMaskでBlendMode.dstInまたはBlendMode.dstOutを使う場合は、マスクの不透明度だけが参照されるため、RGBが違っても同じ結果です。

そのほかに引っかかりやすい点は3つあります。

- `transform: GradientRotation(...)`は矩形の中心でグラデーションを回すだけで、長さは変わりません。既定のbegin・endを使い、幅400px・高さ100pxの矩形で90度回すと、色幅の中央25%だけが画面に出ます。縦や斜めは`begin`・`end`で指定するのが基本です。
- `LinearGradient.withOpacity(0.5)`は各色の不透明度を0.5に**上書き**します。不透明度によるフェードが消え、白から透明な白へのグラデーションなら一色になります。このメソッド自体にanalyzerの非推奨警告は出ません。
- `AnimatedContainer`でLinearGradientからRadialGradientへ切り替えると、補間の中間点（t=0.5）でグラデーションが完全に透明になり、背景が見えます。同じ型どうしなら色とstopsは補間されます。ただし、GradientRotationなどのtransformは中間点で切り替わり、回転角度そのものは滑らかに補間されません。

## LinearGradientの基本指定（begin・end・stops・tileMode）

### begin・endで決まる方向と横長の矩形での実際の角度

LinearGradientはウィジェットではなく、`BoxDecoration`の`gradient`に渡す描画指定です。必須は`colors`だけで、方向は`begin`と`end`に`Alignment`を渡して決めます。既定値は`begin: Alignment.centerLeft`、`end: Alignment.centerRight`の左から右です（[LinearGradientのAPIリファレンス](https://api.flutter.dev/flutter/painting/LinearGradient-class.html)）。

```
Container(
  decoration: const BoxDecoration(
    gradient: LinearGradient(
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
      colors: <Color>[Color(0xFF2196F3), Color(0xFF9C27B0)],
    ),
  ),
)
```

`Alignment`では矩形の左右端・上下端がそれぞれ-1.0と1.0に対応するので、`topLeft`から`bottomRight`への指定は正方形のときだけ45度になります。幅400px・高さ100pxの矩形では、色が変化する向きは対角線と同じ水平から約14度です。黒から白へ塗ると右上の角は240、左下の角は15で、色はほぼ左右方向に変わります。その代わり、左上は0、右下は254と、指定した2色がどちらも画面に出ます。

### stopsの件数不一致で出る実行時エラー

`stops`を省略すると色は等間隔に並びます。配分を変える場合は0.0〜1.0の位置を指定しますが、**要素数を`colors`と一致させる必要があります**。

```
const LinearGradient(
  colors: <Color>[Colors.red, Colors.yellow, Colors.green],
  stops: <double>[0.0, 0.2, 1.0],
)
```

件数がずれてもコンパイルは通ります。Flutter 3.47.9で色3件・stops2件のまま`createShader`を呼ぶと、実行時に次の例外が出ました。色を後から1つ足したときに起きやすいエラーです。

```
Invalid argument(s): "colors" and "colorStops" arguments must have equal length.
```

### tileModeで決まる描画範囲外の扱い

`tileMode`は、beginからendまでの範囲より外側をどう塗るかの指定です。値は`clamp`（既定・端の色を引き延ばす）、`repeated`（繰り返す）、`mirror`（反転しながら繰り返す）、`decal`（範囲外を透明にする）の4種類です。beginとendが矩形の端にある既定の使い方では違いが出ません。`end: Alignment(-0.6, 0)`のように範囲を短くして`repeated`を指定すると、ストライプ状になります。

## GradientRotationによる角度指定と色幅が欠ける条件

角度を数値で指定したい場合は`transform`に`GradientRotation`（ラジアン）を渡します。度で考えるなら`度 * math.pi / 180`で変換します。

```
import 'dart:math' as math;

const LinearGradient diagonal = LinearGradient(
  colors: <Color>[Colors.black, Colors.white],
  transform: GradientRotation(math.pi / 4), // 時計回りに45度
);
```

注意点は、`GradientRotation`が「外接矩形の中心まわりにグラデーションを回す」だけの変換であることです（[gradient.dartのGradientRotation定義](https://github.com/flutter/flutter/blob/3.47.9/packages/flutter/lib/src/painting/gradient.dart)）。グラデーションの長さは回転前のbeginからendまで、既定なら矩形の幅のままです。横長の矩形で90度回すと、幅400pxぶんのグラデーションを高さ100pxの窓から覗く形になります。

幅400px・高さ100pxで黒（0）から白（255）を塗り、上端中央（x=200、y=0）と下端中央（x=200、y=99）、左上・右下の画素値を読み戻した結果が次の表です。

| 指定                            | 上端  | 下端  | 左上の角 | 右下の角 |
| ----------------------------- | --- | --- | ---- | ---- |
| topCenter → bottomCenter      | 1   | 254 | 1    | 254  |
| GradientRotation(math.pi / 2) | 95  | 159 | 95   | 159  |
| topLeft → bottomRight         | 120 | 135 | 0    | 254  |
| GradientRotation(math.pi / 4) | 105 | 150 | 15   | 240  |

90度回転では上端95・下端159で、黒から白の中央25%しか出ていません。400pxのグラデーションの中央100pxだけが見えている計算と一致します。縦方向が欲しいなら回転ではなく`begin: Alignment.topCenter`・`end: Alignment.bottomCenter`と書いてください。斜めは、両端の色を必ず出したいなら`begin`・`end`を角に置き、複数のカードで傾きを揃えたいときだけ`GradientRotation`を使います。後者は`stops`を内側へ寄せて、見える範囲に色を収めます。

## 透明色の書き方とwithOpacityの非推奨（Flutter 3.27以降）

### withValues(alpha:)への移行とanalyzerの警告

Flutter 3.27で`Color`の内部表現が8bit整数から浮動小数点に変わり、広色域（`ColorSpace.displayP3`など）に対応しました。8bitを前提にしたAPIはこのとき非推奨になっています。3.47.9のソースでも`withOpacity`には`@Deprecated('Use .withValues() to avoid precision loss.')`が付いたままです（[dart:uiのpainting.dart](https://github.com/flutter/flutter/blob/3.47.9/engine/src/flutter/lib/ui/painting.dart)）。公式の[移行ガイド](https://docs.flutter.dev/release/breaking-changes/wide-gamut-framework)では、旧APIを削除するPhase 2の時期が「Not yet」のままです。

非推奨APIを4種類含む検証用ファイル（`lib/snippets.dart`）に`flutter analyze`をかけると、次のinfoが出ます。行番号と列番号はそのファイルでの位置です。

```
info • 'withOpacity' is deprecated and shouldn't be used. Use .withValues() to avoid precision loss. Try replacing the use of the deprecated member with the replacement • lib/snippets.dart:31:32 • deprecated_member_use
info • 'opacity' is deprecated and shouldn't be used. Use .a. Try replacing the use of the deprecated member with the replacement • lib/snippets.dart:32:57 • deprecated_member_use
info • 'value' is deprecated and shouldn't be used. Use component accessors like .r or .g, or toARGB32 for an explicit conversion. Try replacing the use of the deprecated member with the replacement • lib/snippets.dart:33:35 • deprecated_member_use
info • 'red' is deprecated and shouldn't be used. Use (*.r * 255.0).round().clamp(0, 255). Try replacing the use of the deprecated member with the replacement • lib/snippets.dart:34:33 • deprecated_member_use
```

| 旧API（3.27で非推奨）         | 新API                         | 戻り値の型           |
| ---------------------- | ---------------------------- | --------------- |
| color.withOpacity(0.5) | color.withValues(alpha: 0.5) | Color           |
| color.opacity          | color.a                      | double（0.0〜1.0） |
| color.value            | color.toARGB32()             | int             |
| color.red              | (color.r \* 255.0).round()   | int             |

警告はinfoなのでビルドは止まりません。移行は戻り値が`Color`のまま変わらない`withOpacity`から始めてください。`.a`も旧`opacity`も戻り値は`double`です。ただし、旧APIは8bitへ丸めた不透明度を返すため、移行すると等値比較やMapのキーの一致結果が変わる場合があります。Flutter本体の版ごとの変更点は[Flutter 3.47とは？material\_ui移行・iOS 15と3.47.7までの修正【2026年10月時点】](/tech/details/16894/)にまとめています。

### ARGBリテラルで透明を書く場合の0x00FFFFFF

Flutterの`Color`リテラルは`0xAARRGGBB`の並びで、先頭2桁が不透明度です。`0x00`が完全透明、`0xFF`が完全不透明で、CSSの`rgba(255, 255, 255, 0)`に当たるのは`Color(0x00FFFFFF)`です。`Color(0x00000000)`と書くとRGBが黒になり、次章の濁りが出ます。約50%の白は`0x80FFFFFF`（0x80は128で、正確には50.2%）です。`const`の中に置くならリテラル、実行時に既存の色から作るなら`withValues`と使い分けます。

### LinearGradient.withOpacityがフェードを消す挙動

グラデーション全体を半透明にしたくて`gradient.withOpacity(0.5)`と書くと、意図と違う結果になります。3.47.9の実装は、各色に`color.withOpacity(opacity)`を入れ直すだけだからです。不透明度は掛け算ではなく**上書き**なので、「不透明な白から透明な白」が「50%の白から50%の白」に変わり、フェードが消えます。

青地の上に描いて左端・中央・右端を読み戻すと、元のグラデーションは254→127→0（赤・緑成分）でした。`withOpacity(0.5)`後は128・128・128の一色です。`Color.withOpacity`と違い`LinearGradient.withOpacity`には`@Deprecated`が付いていないため、`flutter analyze`も何も言いません。各色の不透明度に倍率を掛けたいときは、標準APIの`scale`を使います。`scale(0.5)`は各色を「その色の透明版」へ半分だけ近づけるので、RGBはそのまま不透明度だけが0.5倍になります。結果は127→63→0で、フェードが残ったまま全体が半分の濃さになりました。

```
final LinearGradient base = LinearGradient(
  colors: <Color>[Colors.white, Colors.white.withValues(alpha: 0)],
);
final LinearGradient half = base.scale(0.5); // 不透明度 1.0→0.5、0.0→0.0
```

ウィジェットごと薄くしてよいなら、`Opacity`や`FadeTransition`で包むほうが簡単です。

## 白から透明が灰色に濁る原因と実測値

### Colors.transparentは透明な黒という定義

Flutter 3.47.9のソースで、`Colors.transparent`は次のように定義されています（[material/colors.dart](https://github.com/flutter/flutter/blob/3.47.9/packages/flutter/lib/src/material/colors.dart)）。

```
/// Completely invisible.
static const Color transparent = Color(0x00000000);
```

RGBはすべて0、つまり黒です。補間はRGBと不透明度を別々に線形補間するので、白と透明な黒の中間は「50%不透明の灰色」になり、背景の上では暗い帯に見えます。

Flutter側もこの挙動を把握しています。[Color.lerpのドキュメント](https://api.flutter.dev/flutter/dart-ui/Color/lerp.html)には、片方がnullのときは相手の色の透明版から補間すること、そしてそれが「`Colors.transparent`（`const Color(0x00000000)`）から補間するより通常は望ましい。`Colors.transparent`はとりわけ透明な_黒_である」ことが書かれています。

### 青地に重ねた中点ピクセルの実測値

不透明な青（`0xFF0000FF`）の上に幅400px・高さ8pxで白からのグラデーションを描き、高さ方向の中央の行を読み戻した値です。Flutter 3.47.9の`flutter test`上で`PictureRecorder`に描いて計測しました。ディザリングにより、読む行によって値が±1ほど動きます。

| 位置   | Colors.transparent | white.withValues(alpha: 0) |
| ---- | ------------------ | -------------------------- |
| 0.00 | 254, 254, 254      | 254, 254, 255              |
| 0.25 | 142, 142, 207      | 190, 190, 255              |
| 0.50 | 63, 63, 191        | 127, 127, 255              |
| 0.75 | 15, 15, 207        | 63, 63, 255                |
| 1.00 | 0, 0, 255          | 0, 0, 255                  |

中点では、正しい書き方の赤・緑成分127に対し`Colors.transparent`版は63です。白と黒を50%で混ぜた色がそのまま乗り、青の成分も255から191へ落ちています。3.44.9で測った値と同じで、3.47でも挙動は変わっていません。

```
// リテラルで書く（const にできる）
const LinearGradient fadeOut = LinearGradient(
  begin: Alignment.topCenter,
  end: Alignment.bottomCenter,
  colors: <Color>[Colors.white, Color(0x00FFFFFF)],
);

// 既存の色から作る
final Color baseColor = Theme.of(context).colorScheme.surface;
final LinearGradient fade = LinearGradient(
  begin: Alignment.topCenter,
  end: Alignment.bottomCenter,
  colors: <Color>[baseColor, baseColor.withValues(alpha: 0)],
);
```

実務では後者を標準にしてください。テーマ色を差し替えたときに、片方の端だけ古い色が残る事故を防げます。

### ShaderMaskのマスクなら濁らない境界

「`Colors.transparent`は使うな」と覚えると行き過ぎです。ShaderMaskで`BlendMode.dstIn`を使う場合、参照されるのはマスクの不透明度だけで、RGBは結果に出ません。

RGBが正反対の2つのマスク、黒→`Colors.transparent`（RGBは黒）と黒→`Colors.white.withValues(alpha: 0)`（RGBは白）で同じ白い前景を抜き、青地に重ねて比べました。位置0.25で191,191,255、0.50で127,127,255、0.75で63,63,255と、両者は1バイトも違いませんでした。比較相手に`Colors.black.withValues(alpha: 0)`を選ぶと、`Colors.transparent`と同じ`0x00000000`どうしを比べることになるので検証になりません。

グラデーションを**色として塗る**（`BoxDecoration`の`gradient`、`Paint.shader`）なら`Colors.transparent`は使わず、**マスクとして使う**（ShaderMaskの`dstIn`・`dstOut`）ならどちらでも構いません。迷ったら`withValues(alpha: 0)`で書けば両方で正しく動きます。

## 背景・ボタン・AppBar・テキストへの適用

### 画面全体の背景とextendBodyBehindAppBar

Scaffoldの`backgroundColor`は単色しか受け付けないので、`body`に置いた`Container`で塗ります。AppBarの下まで塗るには`extendBodyBehindAppBar: true`を指定し、AppBarを`backgroundColor: Colors.transparent`・`elevation: 0`で透過させます。ここは色を塗らない指定なので`Colors.transparent`で問題ありません。

3.47.9のM3既定では、内容がAppBarの下へスクロールで潜ると高さが`scrolledUnderElevation: 3.0`に変わります。既定の`shadowColor`と`surfaceTintColor`はどちらも`Colors.transparent`なので、既定のままなら影や色は付きません。テーマで影色や表面色を設定しているアプリでは、その色がスクロール時に境界へ出るので、`scrolledUnderElevation: 0`で高さを変えないようにしておくと確実です。

```
Scaffold(
  extendBodyBehindAppBar: true,
  appBar: AppBar(
    backgroundColor: Colors.transparent,
    elevation: 0,
    scrolledUnderElevation: 0,
  ),
  body: Container(
    decoration: const BoxDecoration(
      gradient: LinearGradient(
        begin: Alignment.topCenter,
        end: Alignment.bottomCenter,
        colors: <Color>[Color(0xFF1E88E5), Color(0xFF6A1B9A)],
      ),
    ),
  ),
)
```

### AppBarだけをグラデーションにするflexibleSpace

AppBarにも`gradient`プロパティはありません。AppBarだけを塗りたいときは、`flexibleSpace`に`DecoratedBox`を渡します。`flexibleSpace`はタイトルの背面・背景色の上に積まれるので、半透明の色を含めるときだけ`backgroundColor: Colors.transparent`にします。`scrolledUnderElevation: 0`はスクロール時に高さを変えない指定です。

```
AppBar(
  title: const Text('設定'),
  backgroundColor: Colors.transparent,
  scrolledUnderElevation: 0,
  flexibleSpace: const DecoratedBox(
    decoration: BoxDecoration(
      gradient: LinearGradient(
        colors: <Color>[Color(0xFF00897B), Color(0xFF1E88E5)],
      ),
    ),
  ),
)
```

### ボタンのグラデーションとInkによる波紋の表示

ボタンは`ElevatedButton`の`ButtonStyle.backgroundBuilder`で背景をグラデーションにできます。無効時の操作抑止やフォーカス、押下時の波紋を利用できるので、自前で組むより先にこちらを検討してください。`backgroundBuilder`が返すウィジェットはボタンの`Material`の子になり、ボタンの中身はさらにその子として渡されます。

```
ElevatedButton(
  onPressed: onPressed,
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.transparent,
    shadowColor: Colors.transparent,
    foregroundColor: Colors.white,
    backgroundBuilder:
        (BuildContext context, Set<WidgetState> states, Widget? child) {
      return Ink(
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            colors: <Color>[Color(0xFFFF6D00), Color(0xFFD500F9)],
          ),
        ),
        child: child,
      );
    },
  ),
  child: const Text('送信する'),
)
```

ここで`Ink`の代わりに`DecoratedBox`や`Container`を返すと、タップしても波紋が見えません。[Inkのドキュメント](https://github.com/flutter/flutter/blob/3.47.9/packages/flutter/lib/src/material/ink%5Fdecoration.dart)にあるとおり、波紋は下の`Material`に描かれ、その上に重なった不透明な描画に隠れるためです。`Ink`は`decoration`を`Material`そのものに描くので、波紋がグラデーションの上に出ます。

3.47.9のウィジェットテストでボタンを押したまま300ms進め、ボタンを囲む200px×60pxの`RepaintBoundary`内のRGB値の合計を押す前と比べると、`DecoratedBox`では差が約0.01%、`Ink`では約2.6%でした。`Material`と`InkWell`で自前のボタンを組む場合も、`Container`に`gradient`を入れると差は0、`Ink`なら約22%で、同じ結果です。

### ShaderMaskによるテキスト・アイコンの着色と画像のフェード

`Text`や`Icon`の`color`も単色なので、グラデーションを乗せるにはShaderMaskで包みます。文字やアイコンを塗るなら既定の`BlendMode.modulate`、画像の下端を透明へ抜くなら`BlendMode.dstIn`です。**modulateでグラデーションの色をそのまま出したい文字・アイコンは、子ウィジェットの色を白にしてください**。dstInで画像をフェードさせる場合、画像を白くする必要はありません。modulateは前景とグラデーションの色を掛け合わせるため、子が中間灰色（`0xFF808080`）だと中央の色が233,54,124から117,27,62へ半減し、青だと赤と緑が0になりました。

```
ShaderMask(
  shaderCallback: (Rect bounds) => const LinearGradient(
    begin: Alignment.topLeft,
    end: Alignment.bottomRight,
    colors: <Color>[Color(0xFFFF6D00), Color(0xFFD500F9)],
  ).createShader(bounds),
  child: const Icon(Icons.favorite, size: 64, color: Colors.white),
)
```

```
ShaderMask(
  shaderCallback: (Rect bounds) => const LinearGradient(
    begin: Alignment.topCenter,
    end: Alignment.bottomCenter,
    colors: <Color>[Colors.black, Colors.transparent],
  ).createShader(bounds),
  blendMode: BlendMode.dstIn,
  child: Image.asset('assets/photo.png'),
)
```

`shaderCallback`が受け取る`bounds`は子ウィジェットの矩形なので、そのまま`createShader`に渡します。固定の`Rect`を渡すと、画面サイズが変わったときに範囲がずれます。半透明を重ねるすりガラス表現をFlutterで作る選択肢は[FlutterのLiquid Glass対応状況｜公式実装は作業中、今使えるパッケージと選び方](/tech/details/16743/)で比べています。

## グラデーションのアニメーションとGradient.lerpの補間

グラデーションを切り替えるときは、`AnimatedContainer`の`decoration`を差し替えるだけで補間されます。`BoxDecoration.lerp`が内部で`Gradient.lerp`を呼ぶためです。

```
AnimatedContainer(
  duration: const Duration(milliseconds: 400),
  decoration: BoxDecoration(
    gradient: LinearGradient(
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
      colors: selected
          ? const <Color>[Color(0xFF2196F3), Color(0xFF9C27B0)]
          : const <Color>[Color(0xFFFF9800), Color(0xFFE91E63)],
    ),
  ),
)
```

`selected`はStatefulWidgetの状態として持ち、`setState`で切り替えます（状態の持ち方は[StatelessWidgetとStatefulWidgetの違いと使い分け｜Flutter 3.44の実装で確認する判定基準](/tech/details/4442/)）。補間の結果は、前後の型が同じかどうかで変わります。

- **LinearGradientどうし**：色数やstopsが違ってもつながります。色2つ（stopsなし）と色3つ（stops 0.0・0.3・1.0）の中間を`Gradient.lerp`で取り出すと、stopsは両者を合わせた0.0・0.3・1.0の3点になり、各点の色が補間されていました。
- **LinearGradientからRadialGradientへ**：互いに補間できないため、前半で元のグラデーションを透明へ、後半で次のグラデーションを透明から戻します。白地で測ると、t=0.25では色の不透明度が0.5、t=0.5では全色の不透明度が0で、白地がそのまま見えました。

型をまたぐ切り替えで画面が一瞬抜けて見えるのはこのためです。避けたいなら、型を揃える（RadialGradientどうしで`center`や`radius`を動かす）か、`AnimatedSwitcher`で2枚を重ねてクロスフェードさせます。

## RadialGradient・SweepGradientとの使い分け

線形のほかに2種類のグラデーションがあり、どちらも`BoxDecoration`の`gradient`に同じ形で渡せます。

| クラス            | 色の変化     | 位置指定                | 主な用途          |
| -------------- | -------- | ------------------- | ------------- |
| LinearGradient | 直線方向     | begin / end         | 背景・ボタン・フェード   |
| RadialGradient | 中心から外周へ  | center / radius     | スポットライト・円形ボタン |
| SweepGradient  | 中心まわりの角度 | center / startAngle | 進捗リング・カラーホイール |

### RadialGradientのradiusの基準と既定値0.5

RadialGradientの`radius`は、ソースのドキュメントでは「描画する矩形の**短辺の長さ**に対する比率」です。幅100px・高さ200pxなら`radius: 1.0`は中心から100pxで、短辺の端にちょうど外側の色が来る`0.5`が既定値です。この矩形で1.0へ上げると、外周の色に届くのは上下端の中央と四隅だけで、左右端の中央は中間の色のままです。「横方向に外周の色が出ない」ときはこの基準を確認し、中心をずらすなら`center`に`Alignment`を渡します。

### SweepGradientの開始位置とGradientRotationの角度

SweepGradientは3時の位置から時計回りに色が進みます。`startAngle`と`endAngle`はラジアンで、既定は0.0から`math.pi * 2`の全周です。

開始位置を変えるときは公式ドキュメントの説明に注意してください。[GradientTransformのAPIリファレンス](https://api.flutter.dev/flutter/painting/GradientTransform-class.html)は「6時から始めるには`pi/4`ラジアン（45度）の`GradientRotation`を渡す」と書き、[GradientRotationのサンプル](https://api.flutter.dev/flutter/painting/GradientRotation-class.html)は`math.pi/4`を「4分の1回転」と説明しています。3.47.9で黒から白のSweepGradientを200px四方に描くと、`math.pi / 4`では黒（0）が4時半の位置に来ました。6時から始まったのは`math.pi / 2`のときです。6時開始にするなら`math.pi / 2`、12時開始なら`-math.pi / 2`を渡します。

Web側で3Dのグラデーション背景を作る場合は[ShaderGradientとは？React・Vue・Next.jsで3Dグラデーション背景を実装する手順](/tech/details/2679/)が別の選択肢になります。

## 縞（バンディング）と継ぎ目を抑える配色

グラデーションが縞に見える現象をカラーバンディングと呼びます。原因は階調の不足です。Flutter 3.47.9で、幅を変えながら1行に現れる色の数を数えました（高さ4pxで描画し、4行それぞれを集計）。

| 配色                 | 幅200px   | 幅400px   | 幅1200px |
| ------------------ | -------- | -------- | ------- |
| #1A1A1A から #000000 | 27色      | 27色      | 27色     |
| #FFFFFF から #000000 | 150〜200色 | 220〜256色 | 256色    |

**今回の8bitグレースケール出力で表現できる階調の上限は両端の色差で決まり、実際に1行へ現れる色数は幅やディザリングにも左右されます**。#1A1A1Aと#000000の間には整数値が26段しかないため、1200pxに広げても27色のまま、1色あたり約44pxの帯になります。白黒の行ごとの差はディザリングによるものです。暗い背景で近い2色を広い面に引き延ばすのは避け、色差を広げてください。

もう1つの見え方の問題が、透明へ抜けるフェードの始点に出る「線」です。直線補間は始点で不透明度の減り方が急に変わるため、その位置が境目として見えることがあります。イージング曲線に沿った中間色を`Color.lerp`で作って並べると、変化がなだらかになります。

```
List<Color> easedColors(Color from, Color to,
    {int steps = 8, Curve curve = Curves.easeInOut}) {
  return <Color>[
    for (int i = 0; i <= steps; i++)
      Color.lerp(from, to, curve.transform(i / steps))!,
  ];
}

final LinearGradient shadow = LinearGradient(
  begin: Alignment.topCenter,
  end: Alignment.bottomCenter,
  colors: easedColors(Colors.black, Colors.black.withValues(alpha: 0)),
);
```

stopsを省略すると9色が等間隔に並びます。このときの不透明度は1.000・0.969・0.871・0.708・0.500・0.292・0.129・0.031・0.000で、両端の変化が緩やかになりました。同じ考え方をまとめたパッケージに[easing\_gradient](https://pub.dev/packages/easing%5Fgradient)があります（1.0.0・2026年10月22日公開・Flutter 3.47.0以上）。依存を増やすほどの用途でなければ、上の十数行で足ります。

グラデーションを使った画面をiOS・Androidで同じ見た目に揃えるところまで含めて開発を任せたい場合は、[Flutter / React Native開発](/service/apps/cross%5Fplatform/)で相談を受けています。画面とウィジェットの分け方は[Flutterアーキテクチャのベストプラクティス｜公式ガイドのレイヤー設計・ディレクトリ構成・設計パターン【Flutter 3.47対応】](/tech/details/7232/)で扱っています。

## よくある質問

### Flutterで透明のカラーコードはどう書きますか？

`Color(0x00FFFFFF)`のように、先頭2桁の不透明度を`00`にします。Flutterの`Color`は`0xAARRGGBB`の順で、CSSの`rgba()`とは不透明度の位置が違います。`0x00000000`は透明な黒（`Colors.transparent`と同値）で、グラデーションでは灰色の濁りの原因になります。

### withOpacityは使えなくなりましたか？

Flutter 3.47.9でも動きます。3.27で非推奨になりましたが削除時期は未定で、`flutter analyze`でinfoが出るだけです。内部で8bitに丸めるため、`withValues(alpha:)`へ移行してください。なお`LinearGradient.withOpacity`は非推奨ではありませんが、各色の不透明度を上書きするのでフェードが消えます。

### グラデーションを斜めや任意の角度にするにはどうしますか？

45度前後の斜めなら`begin: Alignment.topLeft`・`end: Alignment.bottomRight`のように角を指定します。両端の色が必ず出ますが、傾きは矩形の縦横比で変わります。角度を固定したいなら`transform: GradientRotation(角度 * math.pi / 180)`を使います。こちらはグラデーションの長さが回転前のままなので、矩形の縦横比と回転角度によっては端の色が画面に出ません。

### 円形・放射状のグラデーションはどう作りますか？

`LinearGradient`を`RadialGradient`に置き換え、`begin`・`end`の代わりに`center`と`radius`を指定します。`radius`は矩形の短辺の長さを1.0とした比率で、既定値は0.5です。角度で一周させるなら`SweepGradient`を使います。

### 白から透明のグラデーションが灰色になるのはバグですか？

仕様どおりの動作です。`Colors.transparent`はRGBが黒の`Color(0x00000000)`なので、白から補間すると中間が灰色になります。`Colors.white.withValues(alpha: 0)`か`Color(0x00FFFFFF)`に置き換えれば直ります。ShaderMaskの`dstIn`マスクでは不透明度しか見ないので起きません。

## 関連記事

- [Flutter 3.47とは？material\_ui移行・iOS 15と3.47.7までの修正【2026年10月時点】](/tech/details/16894/)
- [Flutterアーキテクチャのベストプラクティス｜公式ガイドのレイヤー設計・ディレクトリ構成・設計パターン【Flutter 3.47対応】](/tech/details/7232/)
- [StatelessWidgetとStatefulWidgetの違いと使い分け｜Flutter 3.44の実装で確認する判定基準](/tech/details/4442/)
- [FlutterのLiquid Glass対応状況｜公式実装は作業中、今使えるパッケージと選び方](/tech/details/16743/)
- [ShaderGradientとは？React・Vue・Next.jsで3Dグラデーション背景を実装する手順](/tech/details/2679/)

---

出典: [Flutter LinearGradientの使い方｜透明が灰色に濁る原因と角度指定・アニメーション【3.47対応】](<https://www.issoh.co.jp/tech/details/4030/>)（株式会社一創）
