Wiki_Flutter_UI - inoueshinichi/Wiki_Flutter GitHub Wiki

Flutterで使用する主要なUIウィジェット

参考

build関数と非同期処理

  • @override Widget build(BuildContext build)メソッドは, 同期処理のみ.
  • build関数では, async/awaitが使えない.
  • build関数で, 非同期処理を実現する推奨方法は, FlutterBuilderを利用すること.

FlutterBuilder

  • Futureオブジェクトを引数とする.
  • そのFutureオブジェクトの状態変化をトリガーとして, Widgetを再構築する

状態 (3種)

  • データが未確定
  • データが確定
  • エラー
初回のsetState呼び出しのみでFuture関数を実行する方法
// ベストプラクティス (場合によってはそうでない)
class BestPracticeAsyncWidget extends StatefulWidget {
  const BestPracticeAsyncWidget({super.key});

  @override
  State<BestPracticeAsyncWidget> createState() => BestPracticeAsyncState;
}

class BestPracticeAsyncState extends State<BestPracticeAsyncWidget> {
  late Future<String> _the_future;

  @override
  void initState() {
    super.initState();
    _the_future = Future<void>(() async {
      await Future.delayed(const Duration(seconds: 1));
      return 'hoge';
    },
   );
  }
  
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: _the_future,
      builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
        switch (snapshot.connectionState) {
          case connectionState.none:
            return const Text('none');
          case connectionState.warning:
            return const Text('warning');
          case connectionState.active:
            return const Text('active');
          case connectionState.done:
            return const Text('snapshot.done!');
        }
      },
    );
  }
}
毎回のsetState呼び出しでFuture関連の関数が呼ばれる方法
class BadPracticeAsyncWidget extends StatelessWidget {
  const BadPracticeAsyncWidget({super.key});

  Future<String> fetch() async {
    await Future<void>.delayed(const Duration(seconds: 1));
    return 'fuga';
  }
  
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: fetch(),
      builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
        switch (snapshot.connectionState) {
          case connectionState.none:
            return const Text('none');
          case connectionState.warning:
            return const Text('warning');
          case connectionState.active:
            return const Text('active');
          case connectionState.done:
            return const Text('snapshot.done!');
        }
      },
    );
  }
}

BuildContext

  • 対応するウィジェットのツリーを管理するインスタンス

AppBar

AppBar(
  title: <Widget>,
  leading: <Widget>,
  actions: <Widget>[...],
  bottom: <PrefferedSize>,
)

スタイル

=================================================
| [leading] [title] [(action1), (action2), ...] |
-------------------------------------------------
| [           bottom <PrefferedSize>          ] |
=================================================

AppBarの例

Scaffold(
  appBar: AppBar(
    title: Text('My App'),
    leading: BackButton(color: Colors.white),
    actions: <Widget>[
      IconButton(
        icon: Icon(Icons.android),
        tooltip: 'add star ...',
        onPressed: () { },
      ),
      IconButton(
        icon: Icon(Icons.favorite),
        tooltip: 'subtract star ...',
        onPressed: () { },
     ),
     ...,
   ],
   bottom: PrefferedSize(
     prefferedSize: const Size.fromHeight(30.0),
     child: Center(
       child: Text(_stars,
         style: TextStyle(
           fontSize: 22.0,
           color: Colors.white,
         ),
       ),
     ),
    body: ~,
    bottomNavigationBar: ~,
)

BottomNavigationBar

BottomNavigationBar(
  currentIndex: <int>,
  items: <BottomNavigationBarItem>[...],
  onTap: (int value) { }, // void
)

ボタン系

Name Content URL
TextButton - -
ElevatedButton - -
IconButton - -
FloatingActionButton - -
RawMaterialButton - -
PopupMenuButton PopupMenuEntry, PopupMenuItem -

リスト系

Name Content URL
ListView - -
ListTitle - -

ListView

ListView(
  shrinkWrap: bool, // 項目によって中身を自動調整するか否か
  padding: <EdgeInsets>,
  children: <Widget>[...],
)

ListTitle

ListTitle(
  leading: <Icon>,
  title: <Widget>
  selected: bool,
  onTap: () => ...,
  onLongPress: () => ...,
)

class MyApp extends HookWidget {
  
  @override
  Widget build(BuildContext context) {
    final state = useProvider(subListNotifier.state);
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Manage subscription'),
        ),
        body: ListView.separated(
          padding: EdgeInsets.all(5.0),
          itemBuilder: (BuildContext context, int index) {
            var sub = state[index];
            return SubListItem(
              title: sub.name,
              subTitle: '¥${sub.cost}',
              titleColor: sub.Color,
              leading: CostrainedBox(
                constraints: BoxConstraints(
                  minHeight: 44,
                  minWidth: 34,
                  maxHeight: 64,
                  maxWidth: 54,
                ),
              child: FlutterLogo(),
            );
          },
          separatorBuilder: (BuildContext context, int index) {
            return SizedBox(height: 10),
          },
          itemCount: state.Length,
        ),
      ),
    ); // MaterialApp
  } // build
} // MyApp


class SubListItem extends StatelessWidget {
  final String title;
  final String subTitle;
  final Widget leading;
  final Color tileColor;
  
  SubListItem({ @required this.title, @required this.subTitle, @required this.tileColor });

  @override
  Widget build(BuildContext context) {
    return ListTile(title: Text(title), subTitle: Text(subTitle)
                    leading: leading, trailing: Icon(Icons.more_vert),
                    onTap: () => {}, onLongTap: () => {}, 
    );
  }
} // SubListItem
                    

タブ系

Name Content URL
Tab - -
TabBar - -
TabBarView - -
TabController - -
          TabBar
| Tab1 | Tab2 | Tab3 | ... |
============================
|                          |
|                          |
|         Content          |
|                          |
|                          |
============================
  • TabBarは, AppBarのbottomに設定する

Tab

Tab(
  Text('string'),
)

TabBar

TabBar(
  controller: <TabController>,
  tabs: <Tab>[...],
)

TabBarView

TabBarView(
  controller: <TabController>,
  children: <Widget>[...],
)  

TabController

TabController(
  vsync: <TickProvider>, // アニメーションのコールバック呼び出しに関するTickerというクラスを生成する.
  length: <int> tabBar.length,
  initialIndex: <int>,
)
  • vsyncは, TabBarを実装するWidget(State)クラスにwith SingleTickerCountProviderでmixinする.

class _MyTabBarState extends State<MyTabBar>
  with SingleTickerProviderStateMixin {

  const tabs = <Tab>[
    Tab(text: 'One'), Tab(text: 'Two', Tab(text: 'Three')
  ];

  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(
      vsync: this, 
      length: tabs.length,
   );
  }

  Widget createTabToContent(Tab tab) {
    return Center(
      child: Text('This is ${tab.text}',
      style: const TextStyle(fontSize: 32.0, color: Colors.blue),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Tet TabBar'),
        bottom: TabBar(
          controller: _tabController,
          tabs: tabs,
        ),
     body: TabBarView(
       controller: _tabController,
       children: tabs.map((Tab tab) {
         return createTabToContent(tab);
       }).toList(),
     ),
// タブを画面下部に表示するには, bottomNavigationBarにTabBarを埋め込む
    bottomNavigationBar: Container(
      color: Colors.blue,
      child: TabBar(controller: _tabController, tabs: tabs),
    );
  }
}

ドロワー系

Drawer(
  child: <Widget>,
)

// Scaffold
Scaffold(
  appBar: ~,
  body: ~,
  drawer: Drawer(~),
  bottomNavigationBar: ~,
  floatingActionButton: ~,
)

入力系

名前 機能 URL
TextField 値管理は, TextEditingControllerで行う. -
CheckBox - -
Switch - -
Radio - -
Slider - -
DropdownButton 各アイテムはDropdownMenuItemを使う -
PopupMenuButton 各アイテムはPopupMenuEntryもしくは派生クラスを使う -

TextField

TextField(
  controller: <TextFieldController>,
  style: TextStyle(
    fontSize: 28.0,
    color: const Color(0xffFF0000),
    fontWeight: FontWeight.w400, // import 'dart:ui';
    fontFamily: "Roboto",
  ),
  onChanged: () {},
)

Checkbox

bool? _ckeck;

Checkbox(
  value: _checked,
  onChanged: () { setState((bool? value) => _checked = value); },
)

Switch

  • Checkboxと外観が異なるだけ
bool? _ckecked;

Switch(
  value: _checked,
  onChanged: () { setState((bool? value) => _checked = value); },
)

Slider

double? _value;

Slider(
  min: 0.0, // double
  max: 255.0, // double
  value: _value,
  divisions: 255, // 分割数: 255なので1ずつ増減する. なしの場合, 実数値の範囲で変化する
  onChanged: (double? value) { setState(() { if (value != null) { _value = value; } }) },
) 

Radio

bool? _selected;

Radio<String>(
  value: 'A',
  groupValue: _selected!,
  onChanged: (String? value) { setState(() { _selected = value; }) },
)

DropdownButton

DropdownButton<T>(
  onChanged: () {},
  value: <T>,
  style: <TextStyle>,
  items: <DropdownMenuItem>[...],
)

DropdownMenuItem

DropdownMenuItem<T>(
  value: <T>,
  child: <Widget>,
)

// state
static final _selected = 'One';

DropdownButton<String>(
  onChanged: (String? value) { setState(() { _selected = value ?? 'No select'; }) },
  value: _selected,
  style: TextStyle(
    color: Colors.black,
    fontSize: 28.0,
    fontWeight: FontWeight.w400, // import 'dart:ui';
    fontFamily: "Roboto",
  ),
  items: <DropdownMenuItem<String>>[
    const DropdownMenuItem<String>(value: 'One', child: const Text('One'),
    const DropdownMenuItem<String>(value: 'Two', child: const Text('Two'),
  ],
)

PopupMenuButton [:]

String? _popupValue;

Align(
  alignment: Alignment.centerRight,
  child: PopupMenuButton(
    onSelected: (String? value) => setState(() { _popupValue = value ?? 'No select'; }),
    itemBuilder: (BuildContext context) {
      return <PopupMenuEntry<String>>[
        const PopupMenuItem(child: const Text('One'), value: 'One-value'),
        const PopupMenuItem(child: const Text('Two'), value: 'Two-value'),
      ];
    },
  ),
)

ダイアログ系

  • showDialog関数(非同期)を使う.

showDialog

showDialog(
  context: <BuildContext>,
  builder: <WidgetBuilder>, // BuildContextのtypedef(関数エイリアスや関数の引数, 戻り値で利用するもの). Widgetを生成する関数を指定する.
)

// e.g.
showDialog(
  context: context,
  builder: (BuildContext context) => AlertDialog(
    title: Text('Hello!'),
    content: Text('This is alert dialog'),
    actions: <Widget>[
      TextButton(
        child: const Text('Ok'),
        onPressed: () => Navigator.of("ok"),
      ),
      TextButton(
        child: const Text('Cancel'),
        onPressed: () => Navigator.of("cancel"),
      ),
    ],
  ),
).then<void>((value) => resultAlert(value));
// パターン1
showDialog(...).then<void>(...);

// パターン2
void def notify() async {
  await showDialog(...);
} 

Dialogクラス

名前 機能 URL
Dialog 基底クラス -
AllertDialog アラート -
SimpleDialog 通常ダイアログ -

AlertDialog

AlertDialog(

)

SimpleDialog

SimpleDialog(
  title: <Widget>,
  children: <SimpleDialogOption>[
    SimpleDialogOption(
      child: <Widget>,
      onPressed: () {},
    ),
    SimpleDialogOption(
      child: <Widget>,
      onPressed: () {},
    ),
  ],
)

レイアウト系

名前 機能 URL
Row - -
Column - -
Center - -
Align - -
Pandding - -

コンテナ系

名前 機能 URL
Container - -
SizedBox - -
PrefferedSize - -

ナビゲーター系

名前 機能 URL
BackButton - -
ButtonNavigationBar - -
ButtonNavigationItem - -

SingleChildScrollView

  • 一つのWidgetを内部に持たせるコンテナ.
  • そのWidgetの幅に応じて, 自動的にスクロール表示できる.
⚠️ **GitHub.com Fallback** ⚠️