12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Flutter]タブの選択状態に応じてTabの表示を変える

Last updated at Posted at 2020-03-17

FlutterのTabの中にIconTextを使うと、タブの選択・非選択状態に応じて色がよしなに変わってくれます。しかし、カスタムWidgetやImage.assetTabの中に入れた場合は何も変化しません。本記事ではその対応方法についてお話します。

標準的な実装 Image.assetを使用 理想型
01-1.gif 02-1.gif 03-1.gif

サンプルコードはここに置いてあります。
https://github.com/shcahill/FlutterTabSample

標準的なTabの実装

一番素直にTabを実装すると、以下のようになると思います。

Tabの標準的な実装
TabController _controller;

override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      bottom: TabBar(
        controller: _controller,
        tabs: _buildTabs(context),
      ),
    ),
    body: TabBarView(
      controller: _controller,
      children: _buildTabPages(),
    ),
  );
}

/// [Tab]の配列を生成
List<Widget> _buildTabs() {
  return [
    Tab(icon: Icon(Icons.home), text: 'HOME'),
    Tab(icon: Icon(Icons.map),  text: 'MAP'),
  ];
}

/// タブの中身として表示するPage(Widget)の配列を生成
List<Widget> _buildTabPages() {
  return [
    HomePage(),
    MapPage(),
  ];
}

これを実行すると以下のようになります。

標準的な実装

ここでは、選択中のTabにおいて文字・アイコンが共に白となり、非選択中のアイコンは逆にグレーとなっています。
そしてタブをスクロールさせると、タブインジケータの位置に応じてゆっくりと色が変化していくことがわかります。

Tabの中身を変えてみる

次はIconsを使わずにassetに取り込んだpngをImage.assetで読み込んで表示させます。

Tabの中にImage.assetを使用する
List<Widget> _buildTabs() {
  return [
    Tab(icon: Image.asset('images/soccer.png')),
    Tab(icon: Image.asset('images/rugby.png')),
  ];
}

すると、表示はされるのですが、非選択状態のタブでもアイコンが黒く表示され、グレーにはなりません。

Image.assetを使用した場合

AnimatedBuilderを使ってタブインジケータの位置に応じてTabの表示を変化させる

先ほどのTabAnimatedBuilderで囲います。

AnimatedBuilderを使ってTabの表示を変化させる
//import 'dart:math' as math;

List<Widget> _buildTabs() {
  return [
    Tab(child: _buildAnimatedWidget(Image.asset('images/soccer.png'), 0)),
    Tab(child: _buildAnimatedWidget(Image.asset('images/rugby.png'), 1)),
  ];
}

/// [AnimatedBuilder]でラップすることにより、タブインジケータの位置に応じて[child]のopacityを変化させます。
/// [index]は生成する[Tab]のインデックスです
Widget _buildAnimatedWidget(Widget child, int index) {
  return AnimatedBuilder(
    animation: _controller.animation,
    builder: (_, __) {
      return Opacity(
        child: child,
        opacity: _calculateOpacity(index, _controller.animation.value),
      );
    },
  );
}

/// 計算対象の[Tab]の[index]と、タブインジケータのスクロール位置を示す[value]から、[Tab]内のWidgetのOpacityを算出します。
double _calculateOpacity(int index, double value) {
  if (index - 1 <= value && value <= index + 1) {
    // 選択状態および隣の[Tab]にフォーカスが当たっている状態
    final abs = (index - value).abs();
    // quiver/iterablesと衝突するため、`math`を付与
    return math.max(math.min(1.0 - abs, 1.0), 0.5);
  } else {
    // 非選択状態
    return 0.5;
  }
}

すると、TabController経由でタブインジケータの位置が変わるたびにbuilderが呼ばれ、Opacityの値が変更されるようになります。
以下でAnimatedBuilderで指定する内容についてみていきます。

AnimatedBuilder#animation

ここには、TabController#animationを指定します。この値はdouble型(厳密にはAnimation<double>型)で、タブインジケータの位置を示します。例えば、一番左のタブを選択した状態なら0.0、2つ目のタブを選択中なら1.0を示します。つまり仮に3つ目のタブがあれば、この値は0.0~2.0の範囲の値をとることになります。

AnimatedBuilder#builder

ここでは簡単な例としてOpacityを使ってWidgetの透過度を変更しています。現在のタブの位置はTabController#animation#valueを参照します。その値から透過度を計算しているのが_calculateOpacityの部分ですが、ここはただの算数ですので説明は割愛します。(このサンプルでは、選択状態ならOpacity=1.0、非選択状態ならOpacity=0.5の値をとるように計算しています)

AnimatedBuilderを使用した場合

このようにAnimatedBuilderの実装次第で好きなようにタブを実装できます。

12
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?