🙄

Flutter Webで独特なタブを作ってみた

2024/11/24に公開

💡Tips

Flutter Webで開発をしていて独特のUIを作ることがあった。モバイルアプリの開発でもありますけどね。Googleが考えたMaterialDesignで作って欲しいのだが独特な形状のWidgetを作ることも今まであった。
Webの言語だったら簡単に実装できるデザインもあったりする。今回は左に寄せたタブを使って算数の模範解答と自分の回答を見れるUIを作りました。
内容は適当ですが、独特なUIの参考資料がないのでメモといった感じで記事を書くことにしました。

動作はこんな感じです

https://youtu.be/uBvdEYblAXQ

全体のコード

TabBar が Material ウィジェットの子孫である必要があるのでラップする。でないと赤いエラーが出ます。焦りました😅

あとはパディングの設定をしたりAlignを使用してタブを左寄せにして綺麗に整えます。HTML、CSS、JavaScriptだとまだ自由度があるデザインにできるんですけどね。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const GradientTabView(),
    );
  }
}

class GradientTabView extends StatefulWidget {
  const GradientTabView({super.key});

  
  State<GradientTabView> createState() => _GradientTabViewState();
}

class _GradientTabViewState extends State<GradientTabView> with SingleTickerProviderStateMixin {
  late TabController _tabController;
  
  
  void initState() {
    super.initState();
    _tabController = TabController(length: 2, vsync: this);
  }
  
  
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Material(
      child: Container(
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              Color(0xFFE0E0FF),
              Color(0xFFFFE0E0),
            ],
          ),
        ),
        child: Padding(
          padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
          child: Column(
            children: [
              Expanded(
                child: Container(
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: const BorderRadius.only(
                      topLeft: Radius.circular(16),
                      topRight: Radius.circular(16),
                    ),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.black.withOpacity(0.05),
                        blurRadius: 10,
                        offset: const Offset(0, 2),
                      ),
                    ],
                  ),
                  child: Column(
                    children: [
                      Padding(
                        padding: const EdgeInsets.fromLTRB(4, 4, 4, 0),
                        child: Container(
                          height: 48,
                          decoration: BoxDecoration(
                            color: Colors.grey.shade100,
                            borderRadius: const BorderRadius.only(
                              topLeft: Radius.circular(14),
                              topRight: Radius.circular(14),
                            ),
                          ),
                          child: Align(
                            alignment: Alignment.centerLeft,
                            child: TabBar(
                              controller: _tabController,
                              isScrollable: true, // タブを横スクロール可能に
                              tabAlignment: TabAlignment.start, // タブを左寄せに
                              indicator: BoxDecoration(
                                color: Colors.white,
                                borderRadius: const BorderRadius.only(
                                  topLeft: Radius.circular(12),
                                  topRight: Radius.circular(12),
                                ),
                                boxShadow: [
                                  BoxShadow(
                                    color: Colors.black.withOpacity(0.05),
                                    blurRadius: 4,
                                    offset: const Offset(0, -1),
                                  ),
                                ],
                              ),
                              labelColor: Colors.black87,
                              unselectedLabelColor: Colors.grey.shade600,
                              labelStyle: const TextStyle(
                                fontWeight: FontWeight.bold,
                                fontSize: 14,
                              ),
                              unselectedLabelStyle: const TextStyle(
                                fontWeight: FontWeight.normal,
                                fontSize: 14,
                              ),
                              labelPadding: const EdgeInsets.symmetric(horizontal: 24), // タブの横パディングを調整
                              tabs: const [
                                Padding(
                                  padding: EdgeInsets.only(left: 15, right: 15),
                                  child: Tab(text: '模範解答'),
                                ),
                                Padding(
                                  padding: EdgeInsets.only(left: 15, right: 15),
                                  child: Tab(text: 'あなたの解答'),
                                ),
                              ],
                            ),
                          ),
                        ),
                      ),
                      Expanded(
                        child: TabBarView(
                          controller: _tabController,
                          children: const [
                            BuildTabContent(content: '1 + 1 = 2'),
                            BuildTabContent(content: '1 + 1 = 1'),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class BuildTabContent extends StatelessWidget {
  const BuildTabContent({super.key, required this.content});

  final String content;

  
  Widget build(BuildContext context) {
    return  Container(
      padding: const EdgeInsets.all(16),
      child: Text(
        content,
        style: const TextStyle(
          fontSize: 16,
          color: Colors.black87,
        ),
      ),
    );
  }
}

今回は普段は作らないUIを作成しました。Webの言語だとUIフレームワークのMUIやShadcn/UIがあるので、簡単な方なのですが、Flutter Webだとキツイなと思った。Flutter Webで開発する理由としては、モバイルアプリでもサービスを作りたいという要望もあるのでコードを使い回す目的で、最近は技術選定に選ばれることがあるように思えます。

Discussion