【Flutter】画面下から出てくるピッカー（ドラムロール）を実装する

![picker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105756/68307a99-a85a-773a-a20e-2966352eb7c2.gif)

Flutter で画面下から出てくる iOS 風ピッカー（ドラムロール）を実装します。

- `Flutter 1.12.13+hotfix.5`
- `Dart 2.7.1`

## コード

```dart
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Modal Picker',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Modal Picker'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              onPressed: () {
                _showModalPicker(context);
              },
              child: const Text('Show Picker'),
            ),
            Text(_selectedItem)
          ],
        ),
      ),
    );
  }

  void _showModalPicker(BuildContext context) {
    showModalBottomSheet<void>(
      context: context,
      builder: (BuildContext context) {
        return Container(
          height: MediaQuery.of(context).size.height / 3,
          child: GestureDetector(
            onTap: () {
              Navigator.pop(context);
            },
            child: CupertinoPicker(
              itemExtent: 40,
              children: _items.map(_pickerItem).toList(),
              onSelectedItemChanged: _onSelectedItemChanged,
            ),
          ),
        );
      },
    );
  }

  String _selectedItem = 'none';

  final List<String> _items = [
    'item1',
    'item2',
    'item3',
    'item4',
    'item5',
    'item6',
  ];

  Widget _pickerItem(String str) {
    return Text(
      str,
      style: const TextStyle(fontSize: 32),
    );
  }

  void _onSelectedItemChanged(int index) {
    setState(() {
      _selectedItem = _items[index];
    });
  }
}
```

## 解説

[CupertinoPicker](https://api.flutter.dev/flutter/cupertino/CupertinoPicker-class.html) を使用するため、 `cupertino.dart` をインポートします。

```dart
import 'package:flutter/cupertino.dart';
```

ボタンをタップするとモーダル画面出てくるようにします。
モーダルの高さは画面の 3 分の 1 のサイズにしています。
[showModalBottomSheet function - material library - Dart API](https://api.flutter.dev/flutter/material/showModalBottomSheet.html)

```dart
RaisedButton(
  onPressed: () {
    _showModalPicker(context);
  },
  // ...
),

// ...

void _showModalPicker(BuildContext context) {
    showModalBottomSheet<void>(
      context: context,
      builder: (BuildContext context) {
        return Container(
          height: MediaQuery.of(context).size.height / 3,
          // ...
        );
      },
    );
  }
```

`CupertinoPicker` を `GestureDetector` でラップし、ピッカー部分をタップするとモーダルが閉じるようにしています。

```dart
GestureDetector(
  onTap: () {
    Navigator.pop(context);
  },
  child: CupertinoPicker(
    // ...
  )
)
```

`itemExtent` ：アイテムの高さを指定します。
`children` ：表示するアイテムを `List<Widget>` で渡します。
`onSelectedItemChanged` ：アイテムが選択された際に実行する関数を指定します。

```dart
// ...
{
  child: CupertinoPicker(
    itemExtent: 40,
    children: _items.map(_pickerItem).toList(),
    onSelectedItemChanged: _onSelectedItemChanged,
  ),
}
// ...

String _selectedItem = 'none';

final List<String> _items = [
  'item1',
  'item2',
  'item3',
  'item4',
  'item5',
  'item6',
];

Widget _pickerItem(String str) {
  return Text(
    str,
    style: const TextStyle(fontSize: 32),
  );
}

void _onSelectedItemChanged(int index) {
  setState(() {
    _selectedItem = _items[index];
  });
}
```

## 参考

- [Pickers - Controls - iOS - Human Interface Guidelines - Apple Developer](https://developer.apple.com/design/human-interface-guidelines/ios/controls/pickers/)
- [CupertinoPicker class - cupertino library - Dart API](https://api.flutter.dev/flutter/cupertino/CupertinoPicker-class.html)
- [showModalBottomSheet function - material library - Dart API](https://api.flutter.dev/flutter/material/showModalBottomSheet.html)