0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Flutter】ウィジェットの基本的な使い方メモ

Last updated at Posted at 2024-07-15

アイコン一覧
https://qiita.com/hosikiti/items/6e460ad325ce59f026d0

画像の登録
ディレクトリ作成し、そこにドラッグ&ドロップ

スクリーンショット 2024-07-15 12.37.55.png

pubspec.yamlのassetsのコメントを外し、追加した画像ファイルの相対パスを記載する

image.png

main.dart
            // インターネット上の画像を表示するウィジェット
            Image.network(
              'https://jumpneeyan.com/wp-content/uploads/2020/10/kurapika_img_1.png',
              width: 300,
              // アスペクト比を保持したまま小さいのサイズを優先
              height: 100,
            ),
            // プロジェクト内の画像を表示するウィジェット
            Image.asset(
              'assets/kurapika.jpg',
              width: 300,
              height: 500,
            )

ウィジェット実装サンプル

main.dart
// マテリアルデザインをFlutterで実装するための部品群
import 'package:flutter/material.dart';

// 一番最初に実行されるメソッド
void main() {
  // 引数はアプリの大元のウィジェット
  runApp(const MyApp());
}

// StatelessWidgetは状態を持たないウィジェット
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // マテリアルデザインアプリを構築するための大元のウィジェット
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

// StatefulWidget状態をもつウィジェット
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  // _MyHomePageStateにMyHomePageの状態を継承させる
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    // setStateはUIに通知するメソッド
    setState(() {
      print('Before $_counter');
      _counter++;
      print('After $_counter');
    });
  }

  @override
  Widget build(BuildContext context) {
    // Scaffoldはアプリの最も基本的な土台。appBarやbodyというパートを持つ
    // floatingActionButton要素もある(次へボタンに相当)
    return Scaffold(
      // 背景色。ここで条件分を記載できる
      backgroundColor: _counter % 4 == 0 ? Colors.yellow : Colors.blue,
      // AppBarはナビバーの部分
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        // widget.titleには上位で指定された'Flutter Demo Home Page'が入っている
        title: Text(widget.title),
      ),
      // bodyは本文
      body:
          // これでスクロール可能になる(セルの再利用するにはListViewを使う)
          SingleChildScrollView(
        // scrollDirectionはスクロール方向を指定。※ Axis.verticalはデフォルトなので省略可能
        // scrollDirection: Axis.horizontal,
        // Columnは縦に並べるウィジェット
        child: Column(
          // Columnの場合:
          // MainAxisAlignment.center -> 縦方向の親要素の中央に配置
          // MainAxisAlignment.start -> 縦方向の親要素の上揃えに配置
          // MainAxisAlignment.end -> 縦方向の親要素の下揃えに配置
          // CrossAxisAlignment.center -> 横方向の一番広い要素の中央に配置
          // CrossAxisAlignment.start -> 横方向の一番広い要素の左揃えに配置
          // CrossAxisAlignment.end -> 横方向の一番広い要素の右揃えに配置
          // -> CrossAxisAlignmentで要素同士の整列ではなく、全要素を整列したいなら、
          // Column自体をAlignmentでラップしてtopRightなどを指定する
          // Rowの場合:
          // ↑の縦方向と横方向の考え方が逆になるだけ
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // 独自ウィジェット。Columnの中に並べるウィジェット1(静的なウィジェットはconstをつける)
            const HellWidget(),
            HellWidget2(counter: _counter),
            // Textウィジェット。Columnの中に並べるウィジェット2(静的なウィジェットはconstをつける)
            const Text(
              'TextStyleテストTextStyleテストTextStyleテストTextStyleテストTextStyleテストTextStyleテストTextStyleテストTextStyleテストTextStyleテスト',
              maxLines: 2,
              // TextOverflow.ellipsisで3点リーダを表示
              overflow: TextOverflow.ellipsis,
              style: TextStyle(
                color: Colors.pink,
                fontSize: 30,
                fontWeight: FontWeight.bold,
              ),
            ),
            // Textウィジェット。Columnの中に並べるウィジェット3(動的なウィジェットはconstをつけない)
            Text(
              '$_counter', // 変数_counterを使う
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            // インターネット上の画像を表示するウィジェット
            Image.network(
              'https://jumpneeyan.com/wp-content/uploads/2020/10/kurapika_img_1.png',
              width: 300,
              // アスペクト比を保持したまま小さいのサイズを優先
              height: 100,
            ),
            // プロジェクト内の画像を表示するウィジェット
            Image.asset(
              'assets/kurapika.jpg',
              width: 300,
              height: 300,
            ),
            // ColoredBoxは小要素に色をつける(Containerでも同じことができる)
            const ColoredBox(
              color: Color.fromARGB(255, 0, 255, 0),
              child:
                  // Paddingウィジェット
                  // EdgeInsets.all -> 全てにパディング
                  // EdgeInsets.symmetric(vertical, horizontal) -> 上下または左右の指定されたものをパディング
                  // EdgeInsets.only(top, left, right, bottom) -> 引数省略可。指定があったものだけパディング
                  // EdgeInsets.zero -> パディングなし
                  const Padding(
                padding: EdgeInsets.all(50.0),
                child: Text("paddingテスト"),
              ),
            ),
            // Alignは小要素の位置を好きな位置に配置できる
            Align(
              alignment: Alignment.centerRight,
              child: Text("Alignテスト"),
            ),
            // ElevatedButtonは影付きのボタン
            ElevatedButton(
                style: ElevatedButton.styleFrom(
                    // 角丸ボタンの場合
                    // shape: RoundedRectangleBorder(
                    //   // 引数は丸める半径を指定
                    //   borderRadius: BorderRadius.circular(5)
                    // )
                    // 円ボタンにする場合(paddingで円の大きさを大きくなる)
                    shape: CircleBorder(),
                    padding: EdgeInsets.all(34)),
                onPressed: () {
                  print('テキストボタンを押しました');
                  // ダイアログを表示
                  showDialog(
                      context: context,
                      builder: (context) {
                        return AlertDialog(
                          title: Text("お知らせ"),
                          content: Text("テキストボタンを押しました"),
                          actions: [
                            TextButton(
                                onPressed: () {
                                  // ダイアログを閉じる
                                  Navigator.of(context).pop();
                                },
                                child: Text('close'))
                          ],
                        );
                      });
                },
                child: Text("これはテキストボタン1")),
            // 枠線付きのテキストウィジェット
            OutlinedButton(
                onPressed: () {
                  _pushPage();
                },
                child: Text("次の画面へ"))
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter, // ボタンが押された時に呼び出す関数を指定
        tooltip: 'Increment',
        child: const Icon(Icons.add), // ボタンのアイコン.add意外にもIcons.starとかも指定できる
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  void _pushPage() {
    Navigator.push(
// アプリの状況や位置を保持している情報
      context,
// 画面遷移を行うための情報をまとめたもの
      MaterialPageRoute(
        builder: (context) {
          // 遷移先のページ
          return ListViewTestPage(
            pageTitle: '遷移先ページのタイトル',
          );
        },
        // モーダル繊維は以下コメントを外せばいい
        // fullscreenDialog: true,
      ),
    );
  }
}

// stlと打つだけでlive templateでほとんど自動で出てくる
class HellWidget extends StatelessWidget {
  const HellWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return const Text("こんにちは");
  }
}

// stfと打つだけでlive templateでほとんど自動で出てくる
class HellWidget2 extends StatefulWidget {
  // 引数にcounterをとる
  HellWidget2({super.key, required this.counter});

  int counter;

  @override
  State<HellWidget2> createState() => _HellWidget2State();
}

class _HellWidget2State extends State<HellWidget2> {
  @override
  Widget build(BuildContext context) {
    return widget.counter % 2 == 0 ? const Text('おはよう') : const Text('こんばんは');
  }
}

class ListViewTestPage extends StatefulWidget {
  ListViewTestPage({required this.pageTitle});

  final String pageTitle;

  @override
  State<ListViewTestPage> createState() => _ListViewTestPageState();
}

class _ListViewTestPageState extends State<ListViewTestPage> {
  bool isChecked = false;
  double _currentSliderValue = 20;
  String inputText = "";
  bool switchVal = false;
  final TextEditingController _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // ListViewTestPageの引数はこのようにwidget.でアクセスできる
        title: Text(widget.pageTitle),
      ),
      body: ListView(
        children: [
          Container(
            color: Colors.cyan,
            height: 100,
          ),
          Container(
            color: Colors.pink,
            height: 100,
          ),
          // ListTileはListViewのアイテムを定義
          ListTile(
            // 一番初めのアイコン、サムネイル画像など
            leading: Icon(Icons.smoking_rooms),
            // タイトル
            title: Text('タイトル'),
            // サブタイトル
            subtitle: Text('サブタイトル'),
            // 一番最後のアイコン、サムネイル画像など
            trailing: Icon(Icons.arrow_forward_ios),
            onTap: () {
              print("tapped item");
            },
          ),
          // 削除可能なセルはDismissibleでラップする
          Dismissible(
            key: UniqueKey(),
            onDismissed: (direction) {
              // 削除処理
              print("delete");
            },
            child: ListTile(
              // 一番初めのアイコン、サムネイル画像など
              leading: Icon(Icons.smoking_rooms),
              // タイトル
              title: Text('削除可能なセル'),
              // サブタイトル
              subtitle: Text('サブタイトル'),
              // 一番最後のアイコン、サムネイル画像など
              trailing: Icon(Icons.arrow_forward_ios),
              onTap: () {
                print("tapped item");
              },
            ),
          ),
          // チェックボックス付きのListTile。これはListView意外にもColumnの要素にも使える
          CheckboxListTile(
            // checkboxの位置をどこにするかを指定する
            controlAffinity: ListTileControlAffinity.leading,
            // タイトル
            title: Text('タイトル'),
            value: isChecked,
            onChanged: (value) {
              setState(() {
                isChecked = !isChecked;
              });
            },
            // サブタイトル
            subtitle: Text('サブタイトル'),
          ),
          Slider(
              value: _currentSliderValue,
              min: 0,
              max: 100,
              // divisionsは何段階にするかを指定
              divisions: 100,
              label: _currentSliderValue.round().toString(),
              onChanged: (double value) {
                setState(() {
                  _currentSliderValue = value;
                });
              }),
          // SizedBox使い方1:余白を作る
          SizedBox(height: 30.0),
          // SizedBox使い方2:小ウィジェットのサイズを指定する
          // TODO: widthはなぜか効かない(ListViewだから?)
          SizedBox(
              width: 300.0,
              height: 60.0,
              child: ColoredBox(
                color: Colors.yellow,
                child: Text('slider value is $_currentSliderValue'),
              )),
          TextField(
            controller: _controller,
            keyboardType: TextInputType.number,
            decoration: InputDecoration(
              labelText: 'テキストフィールド',
            ),
            onChanged: (text) {
              setState(() {
                inputText = text;
              });
              print('First text field: $text');
            },
          ),
          Text('TextField value is $inputText'),
          Switch(
              value: switchVal,
              onChanged: (newValue) {
                setState(() {
                  switchVal = newValue;
                });
              }),
          Text('Switch value is $switchVal'),
          ElevatedButton(
              onPressed: () {
                // 元の画面に戻る処理(プッシュもモーダルもこれでOK)
                Navigator.pop(context);
              },
              child: Text('戻る'))
        ],
      ),
    );
  }
}

ダークモード対応

dart main.dart

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

  ThemeData _lightTeme() {
    return ThemeData(
      brightness: Brightness.light,
      primarySwatch: Colors.cyan,
    );
  }

  ThemeData _darkTeme() {
    return ThemeData(
      brightness: Brightness.dark,
      primarySwatch: Colors.cyan,
    );
  }

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: _lightTeme(),
      darkTheme: _darkTeme(),
      // ここをThemeMode.darkにするとダークモードになる
      themeMode: ThemeMode.light,
      home: const MyHomePage(),
    );
  }
}

WebView
pubspec.yamlにwebview_flutterを追加してから

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

  @override
  State<WebViewPage> createState() => _WebViewPageState();
}

class _WebViewPageState extends State<WebViewPage> {
  // lateは、後で初期化するという意味
  late final WebViewController webViewController;

  @override
  void initState() {
    super.initState();
    webViewController = WebViewController()
      // ..はカスケード記法。↑のコードを;で終わらせず、生成したオブジェクトのメソッドを使うという意味
      // JavaScript有効
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..loadRequest(Uri.parse('https://google.com'));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: WebViewWidget(
          controller: webViewController,
        ),
      ),
    );
  }
}

■上部のタブバー

■下部のタブバー

■Expanded
https://qiita.com/nannany_stores/items/d4114f615e4d53964121
https://flutter.salon/widget/expanded/
https://zenn.dev/pressedkonbu/articles/flexible-vs-expanded

■ウィジェットを傾ける
https://note-tmk.hatenablog.com/entry/2022/08/15/221820

■svgの表示
SvgPicture
https://zenn.dev/joo_hashi/articles/c6940c20ce06f7

■ウィジェットの配置

image.png

      Container(
      width: 300, // ListViewの中では無効
      height: 300, // ListViewの中でも有効
      color: Colors.grey,
      child:
      Stack(
        children: [
            Align(
              alignment: Alignment.centerLeft,
              child: Container(
                height: 100, // このサイズを指定しない場合、デフォルトで親と同じ高さ = 黄色のviewがそうなってる
                color: Colors.blue,
                child: Column(
                  children: <Widget>[
                    // Expanded(child:
                    Text('centerLeft1'), // デフォルトで親の左上に合わせる
                    // ),
                    // const SizedBox(height: 10),
                    // Expanded(child:
                    Text('centerLeft2'),
                    // ),
                  ],
                ),
              ),
            ),

          // const SizedBox(width: 40),
          Align(
            alignment: Alignment.bottomRight,
            child: Container(
              color: Colors.yellow,
              child: Column(
                children: <Widget>[
                  // Expanded(child:
                  Container(
                    color: Colors.red,
                      child: Text('bottomRight1')), // デフォルトで親の左上に合わせる
                  // ),
                  // const SizedBox(height: 10),
                  // Expanded(child:
                  Container(
                    color: Colors.white,
                      child: Text('bottomRight2')),
                  // ),
                ],
              ),
            ),
          ),

        ],
      ),
    );


0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?