flutterでProcreateみたいなカラーパレットを作る

こんにちは。

iPadを使用している方ならご存知のペイントアプリ「Procreate」ですが、flutterでアプリを作る時に「Procreate」のカラーパレットが使いやすいので作ってみました。「Procreate」のカラーパレットはこんな感じです。

今回作ったProcreate風カラーパレットはこちらです。

mami
カラーパレットをテーマに合わせて用意するとユーザも選びやすいんじゃないかなぁ、と勝手に思います

デザイン以外の部分を作成

作り方はシンプルです。パレットテーマ([ビビット][ポップ][シック]など)単位にListviewを作成し、その中に8列×3行のGridViewを用意します。カラーが選択されたら、そのカラー情報を呼び出し元に返却します。ただ、どうしてもデザインを角丸にしたかったり、影を付けて立体感を出したり、GridViewの下に背景色を入れたかったり、で簡単に出来そうな事が以外と難しくてデザイン面で少し複雑な事をしています。もう少しソースを綺麗にして部品化出来るといいなー。

ではまずは以下でデザイン以外の部分を作成します。

import 'package:flutter/material.dart';
import '../baseappbar.dart';

class Palette {
  String scene;
  String colorview;
  Color paintcolor;

  Palette(
      this.scene,
      this.colorview,
      this.paintcolor,
      );
}

class SelectColor extends StatefulWidget {

  final String? color;

  const SelectColor({
    Key? key,
    this.color,
  }) : super(key: key);

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

class _SelectColorState extends State<SelectColor> {
  final int col = 8;
  final int row = 3;
  List<Palette> paletteList=[];
  List<String> paletteName=[];
  List<List<Color>> paletteColor=[];
  Future? _future;

  @override
  void initState() {
    super.initState();
    _future = initPalette();

  }
  Future<List<Palette>> initPalette() async {

    paletteList = [
      Palette('ビビッド','Red',Color(0xFFFF107E)),
      ....
    ];

    List<String> tmpScene=[];
    for (int i = 0; i < paletteList.length; i++) {
      tmpScene.add(paletteList[i].scene);
    }
    paletteName = tmpScene.toSet().toList();


    for (int i = 0; i < paletteName.length; i++) {
      List<Palette> tmpPalette = paletteList.where((item) => item.scene==paletteName[i]).toList();
      List<Color> tmpColor=[];
      for (int j = 0; j < tmpPalette.length; j++) {
        tmpColor.add(tmpPalette[j].paintcolor);
      }
      paletteColor.add(tmpColor);
    }
    return paletteList;
  }

カラーパレットは勿論自由に定義してもらって良いですが、とりあえず同じように試したい方は以下をどうぞ。

paletteList = [
  Palette('ビビッド','Red',Color(0xFFFF107E)),
  Palette('ビビッド','Red',Color(0xFF00CAEB)),
  Palette('ビビッド','Red',Color(0xFFFEB42E)),
  Palette('ビビッド','Red',Color(0xFFFF7687)),
  Palette('ビビッド','Red',Color(0xFF005DB3)),
  Palette('ビビッド','Red',Color(0xFFFF3B00)),
  Palette('ビビッド','Red',Color(0xFFEF9BCF)),
  Palette('ビビッド','Red',Color(0xFFFF9701)),
  Palette('ビビッド','Red',Color(0xFF44AC8D)),
  Palette('ビビッド','Red',Color(0xFFEBDD3E)),
  Palette('ビビッド','Red',Color(0xFFB5184F)),
  Palette('ビビッド','Red',Color(0xFFBFEEAA)),
  Palette('ビビッド','Red',Color(0xFF00509D)),
  Palette('ビビッド','Red',Color(0xFF016344)),
  Palette('ビビッド','Red',Color(0xFFFEB621)),
  Palette('ビビッド','Red',Color(0xFFF774A8)),
  Palette('ビビッド','Red',Color(0xFFFF00C8)),
  Palette('ビビッド','Red',Color(0xFF005983)),
  Palette('ビビッド','Red',Color(0xFF08EEC0)),
  Palette('ビビッド','Red',Color(0xFFA3E375)),
  Palette('ビビッド','Red',Color(0xFF017AED)),
  Palette('ビビッド','Red',Color(0xFFC59ECB)),
  Palette('ビビッド','Red',Color(0xFF4D2C55)),
  Palette('ビビッド','Red',Color(0xFFE55321)),

  Palette('ソフト','Red',Color(0xFFDCA6BD)),
  Palette('ソフト','Red',Color(0xFFFFDD79)),
  Palette('ソフト','Red',Color(0xFF789FBB)),
  Palette('ソフト','Red',Color(0xFF8FDDBE)),
  Palette('ソフト','Red',Color(0xFF329AA0)),
  Palette('ソフト','Red',Color(0xFFB9E2AB)),
  Palette('ソフト','Red',Color(0xFFEDB57A)),
  Palette('ソフト','Red',Color(0xFFCB759B)),
  Palette('ソフト','Red',Color(0xFFF39764)),
  Palette('ソフト','Red',Color(0xFFD4D5EC)),
  Palette('ソフト','Red',Color(0xFFFFB49E)),
  Palette('ソフト','Red',Color(0xFFCADFDF)),
  Palette('ソフト','Red',Color(0xFFF17F66)),
  Palette('ソフト','Red',Color(0xFFFDC33C)),
  Palette('ソフト','Red',Color(0xFFB4B5DD)),
  Palette('ソフト','Red',Color(0xFFB2D4ED)),
  Palette('ソフト','Red',Color(0xFFB0AB4D)),
  Palette('ソフト','Red',Color(0xFFF4AFC2)),
  Palette('ソフト','Red',Color(0xFFEF9188)),
  Palette('ソフト','Red',Color(0xFFB7E8D7)),
  Palette('ソフト','Red',Color(0xFFFFA74A)),
  Palette('ソフト','Red',Color(0xFF7FBCB1)),
  Palette('ソフト','Red',Color(0xFFCBE58A)),
  Palette('ソフト','Red',Color(0xFFEA688B)),

  Palette('シック','Red',Color(0xFFEAA397)),
  Palette('シック','Red',Color(0xFFCF331F)),
  Palette('シック','Red',Color(0xFF91A16A)),
  Palette('シック','Red',Color(0xFF515478)),
  Palette('シック','Red',Color(0xFF2F4638)),
  Palette('シック','Red',Color(0xFFC5557F)),
  Palette('シック','Red',Color(0xFFADAB7C)),
  Palette('シック','Red',Color(0xFF1D6640)),
  Palette('シック','Red',Color(0xFFD86976)),
  Palette('シック','Red',Color(0xFFDF6E5C)),
  Palette('シック','Red',Color(0xFF272F41)),
  Palette('シック','Red',Color(0xFF6E9887)),
  Palette('シック','Red',Color(0xFFF3AB41)),
  Palette('シック','Red',Color(0xFF536951)),
  Palette('シック','Red',Color(0xFFFF7247)),
  Palette('シック','Red',Color(0xFFC58200)),
  Palette('シック','Red',Color(0xFFAC9C0F)),
  Palette('シック','Red',Color(0xFF42471A)),
  Palette('シック','Red',Color(0xFF02302C)),
  Palette('シック','Red',Color(0xFFE0A4AD)),
  Palette('シック','Red',Color(0xFF6EAE82)),
  Palette('シック','Red',Color(0xFF355C66)),
  Palette('シック','Red',Color(0xFF23292E)),
  Palette('シック','Red',Color(0xFF7C1A49)),
];

 

デザインを作成

Widgetを用意します。まずは大枠部分です。

@override
Widget build(BuildContext context) {
  return Scaffold(
      resizeToAvoidBottomInset : false,
      appBar: TransAppBar(
        title: 'カラーパレット',
        appBar: AppBar(),
      ),
      body: FutureBuilder(
          future: _future,
          builder: (context, snapshot) {
            switch (snapshot.connectionState) {
              case ConnectionState.waiting:
                return Center(
                  child: CircularProgressIndicator(),
                );
              default:
                if (snapshot.hasError) {
                  print(snapshot.error);
                  return Text('Error: ${snapshot.error}');
                } else {
                  return createList();
                }
            }
          }
      )
  );
}

上記のcreateList()を作成します。デザインのメイン部分です。

createList() {
  return ListView.builder(
      itemCount: paletteName.length,
      itemBuilder: (BuildContext context, int l_index) {
        return Container(
            padding: EdgeInsets.all(10),
            child:Container(
              decoration: new BoxDecoration (
                borderRadius: BorderRadius.only(
                  bottomRight: Radius.circular(10),
                  bottomLeft: Radius.circular(10),
                ),
                boxShadow: [
                  BoxShadow(
                    color: Colors.grey.withOpacity(1),
                    spreadRadius: 1,
                    blurRadius: 1,
                    offset: Offset(0.0, 5),
                  ),
                ],
              ),

                child: Column(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    mainAxisSize: MainAxisSize.min,
                    children: <Widget>[
                      Container (
                        height: 40,
                        decoration: new BoxDecoration (
                            borderRadius: BorderRadius.only(
                              topRight: Radius.circular(10),
                              topLeft: Radius.circular(10),
                            ),
                            color: Color(0xFF3E3A39),
                        ),
                        child: Row(
                          children: <Widget>[
                            Text(
                              paletteName[l_index].toString(),
                              style: TextStyle(
                                // fontWeight: FontWeight.w500,
                                fontSize: 14.0,
                                color:Colors.white,
                              ),
                            )
                          ], mainAxisAlignment: MainAxisAlignment.center,),
                      ),
                      Container (
                        width: double.infinity,
                        decoration: BoxDecoration (
                          borderRadius: BorderRadius.only(
                            bottomRight: Radius.circular(10),
                            bottomLeft: Radius.circular(10),
                          ),
                          color: Color(0xFFEFEFEF),
                        ),
                        child: Material(
                          color: Colors.transparent,
                          child:GridView.builder(

                            itemCount: paletteColor[l_index].length,
                            shrinkWrap: true,
                            physics: NeverScrollableScrollPhysics(),
                            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                              crossAxisSpacing: 1,
                              mainAxisSpacing: 1,
                              crossAxisCount: col,
                            ),
                            itemBuilder: (context, gindex) {
                              return Ink(
                                decoration: BoxDecoration(
                                    borderRadius: BorderRadius.only(
                                      bottomRight: gindex==col*row-1? Radius.circular(10) :Radius.circular(0),
                                      bottomLeft: gindex==col*(row-1)? Radius.circular(10) :Radius.circular(0),
                                    ),
                                    color:paletteColor[l_index][gindex]
                                ),
                                child: InkWell(
                                  onTap: () {
                                    var nav = Navigator.of(context);
                                    nav.pop(paletteColor[l_index][gindex].value);
                                  },
                                ),
                              );
                            },
                          ),
                        ),
                      ),
                    ]
                )

            )
        );
      });

}

 

Radius.circularを使っているところは、角丸にする部分になりますので、角丸の必要がなければ不要です。角丸にするにはClipRRectというWidgetが用意されていますが、これが上手くいかない!!という事でチマチマGridViewの数を計算して角丸にしています。

またテーマごとに8×3=24色を用意出来ない場合、GridViewの下に背景色(↓薄いグレー)を置きたいとします。

48行目のContainer と59行目のchild:GridView.builderだけでは、上手くいかず、(GridViewの色よりContainerの色が前面に表示されてしまう)57~58行目の child: Material、 color: Colors.transparent,が必要となります。

flutterは初心者に優しい気がするけど、少し凝った事をしようとすると、難しくなるような気がします。