🎛️

Neumorphic designsを作ってみた。

2024/06/01に公開

Neumorphicとは

Neumorphism(ニューモーフィズム)とは、近年注目されているデザインスタイルです。
ニューモフィズムは、「新しい」と「スケウモフィズム(物理的なオブジェクトのリアルな模倣)」の組み合わせから生まれた造語です。

以下の記事とかみてもらえると理解が深かまるかなと思います。
https://www.m-hand.co.jp/blog/design/10319/
https://note.com/hironobukimura/n/n0431c73714e8
https://fastcoding.jp/blog/all/info/neumorphism/

パッケージについて

ニューモーフィズムデザインが個人的に好きなので、Flutterで個人開発をしているアプリをニューモーフィズムデザインにしてみましたので、私なりのやり方をご紹介します。
また、調べた限り以下2つパッケージがありました。
https://pub.dev/packages/flutter_neumorphic/score
https://pub.dev/packages/clay_containers
ですが、今回はこれらのパッケージは使わずに自分で作ってみました。

やり方

やり方はそんなに難しくは無いかと思います。
手順としては3つあります。
①ニューモーフィズムデザインにしたいWidgetのColorと背景色を合わせる。
②そのWidgetをContainefまたはSizedBoxでラップをする。
③ラップしたContainefまたはSizedBoxでBoxDecorationのBoxShadowで影を作る
この3手順でできました。
以下、ニューモーフィズムデザインログイン画面です。

// 省略

class LoginPage extends HookConsumerWidget {
  const LoginPage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
//省略
    Color baseColor = Colors.orange.shade100;
    return Scaffold(
      body: Center(
        child: Column(
          children: [
            const SizedBox(height: 80),
            Container(
                color: baseColor,
                child: Center(
                  // 浮き出ている感じにする
                    child: Container(
                        height: 60,
                        width: 300,
                        alignment: Alignment.center,
                        decoration: BoxDecoration(
                          color: baseColor,
                          borderRadius: BorderRadius.circular(30),
                          boxShadow: [
                            BoxShadow(
                              // 右下に濃い色を入れる
                              color: Colors.orange.withOpacity(0.4),
                              spreadRadius: 5, // 影の広がり
                              blurRadius: 7, // 影のぼかし具合
                              offset: const Offset(3, 3),
                              // 影の位置を調整、デフォルトだとContainerに隠れているので。
                              // Offset(3, 3) は、X軸方向(右方向)に 3px、Y軸方向(下方向)に 3px
                            ),
                            BoxShadow(
                              // // 左上に明るい色を入れる
                              color: Colors.white.withOpacity(0.5),
                              spreadRadius: 5,
                              blurRadius: 7,
                              offset: const Offset(-3, -3),
                            ),
                          ],
                        ),
                        child: Container(
                            height: 40,
                            width: 250,
                            alignment: Alignment.center,
                            child: const Text(
                              "Login Page",
                              style: TextStyle(
                                fontSize: 20,
                                color: Colors.orange,
                                fontWeight: FontWeight.bold,
                              ),
                            ))))),
            const SizedBox(height: 40),
            ・・・・
            const SizedBox(height: 40),
            Form(
              child: Container(
                padding: const EdgeInsets.all(24),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.end,
                  children: <Widget>[
                // TextFormFieldをContainerでラップする。 
                    Container(
                        color: baseColor,
                        child: Center(
                          // 凹んだ感じにする。
                            child: Container(
                          height: 55,
                          width: 350,
                          decoration: BoxDecoration(
                            color: baseColor,
                            borderRadius: BorderRadius.circular(30),
                            boxShadow: [
                              // 凹ませるには浮き出ている漢字とは逆に左上に濃い色
                              BoxShadow(
                                color: Colors.orange.withOpacity(0.4),
                                spreadRadius: 5,
                                blurRadius: 7,
                                offset: const Offset(-3, -3),
                              ),
                              // 右下に明るい色
                              BoxShadow(
                                color: Colors.white.withOpacity(0.6),
                                spreadRadius: 5,
                                blurRadius: 7,
                                offset: const Offset(3, 3),
                              ),
                            ],
                          ),
                          child: TextFormField(
                            focusNode: emailFocusNode,
                            controller: emailController,
                            maxLength: null,
                            maxLines: null,
                            keyboardType: TextInputType.multiline,
                            autofillHints: const [AutofillHints.email],
                            validator: (value) {
                              if (value == null || value.isEmpty) {
                                return '値を入力してください';
                              }
                              return null;
                            },
                            decoration: InputDecoration(
                              fillColor: Colors.orange.shade100,
                              filled: true,
                              isDense: true,
                              hintText: "e-mail",
                              hintStyle: const TextStyle(
                                  fontSize: 12, fontWeight: FontWeight.w100),
                              border: OutlineInputBorder(
                                borderRadius: BorderRadius.circular(32),
                                borderSide: BorderSide.none,
                              ),
                            ),
                            textAlign: TextAlign.start,
                            onChanged: (String value) async {
                              email.value = value;
                            },
                          ),
                        ))),
                    const SizedBox(height: 30),
                    Container(
                        color: baseColor,
                        child: Center(
                            child: Container(
                          height: 55,
                          width: 350,
                          decoration: BoxDecoration(
                            color: baseColor,
                            borderRadius: BorderRadius.circular(30),
                            boxShadow: [
                              BoxShadow(
                                color: Colors.orange.withOpacity(0.4),
                                spreadRadius: 5,
                                blurRadius: 7,
                                offset: const Offset(-3, -3),
                              ),
                              BoxShadow(
                                color: Colors.white.withOpacity(0.6),
                                spreadRadius: 5,
                                blurRadius: 7,
                                offset: const Offset(3, 3),
                              ),
                            ],
                          ),
                          child: TextFormField(
                            focusNode: passwordFocusNode,
                            controller: passwordController,
                            obscureText: obscureText.value,
                            keyboardType: TextInputType.multiline,
                            autofillHints: const [AutofillHints.password],
                            decoration: InputDecoration(
                              fillColor: Colors.orange.shade100,
                              filled: true,
                              isDense: true,
                              hintText: "password",
                              hintStyle: const TextStyle(
                                  fontSize: 12, fontWeight: FontWeight.w100),
                              border: OutlineInputBorder(
                                borderRadius: BorderRadius.circular(32),
                                borderSide: BorderSide.none,
                              ),
                              suffixIcon: IconButton(
                                icon: Icon(obscureText.value
                                    ? Icons.visibility_off
                                    : Icons.visibility),
                                onPressed: () {
                                  obscureText.value = !obscureText.value;
                                },
                              ),
                            ),
                            textAlign: TextAlign.left,
                            onChanged: (String value) async {
                              password.value = value;
                            },
                          ),
                        ))),
                        // 省略
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: [
                        TextButtonContainerでラップする。
                        Container(
                            color: baseColor,
                            child: Center(
                                child: Container(
                              height: 40,
                              width: 100,
                              decoration: BoxDecoration(
                                color: baseColor,
                                borderRadius: BorderRadius.circular(30),
                                boxShadow: [
                                  BoxShadow(
                                    color: Colors.orange.withOpacity(0.4),
                                    spreadRadius: 5,
                                    blurRadius: 7,
                                    offset: const Offset(3, 3),
                                  ),
                                  BoxShadow(
                                    color: Colors.white.withOpacity(0.5),
                                    spreadRadius: 5,
                                    blurRadius: 7,
                                    offset: const Offset(-3, -3),
                                  ),
                                ],
                              ),
                              child: TextButton(
                                  style: TextButton.styleFrom(
                                    backgroundColor: Colors.orange.shade100,
                                  ),
                                  child: const Text(
                                    '登録',
                                    style: TextStyle(
                                      color: Colors.orange,
                                      fontWeight: FontWeight.bold,
                                    ),
                                  ),
                                // 省略
                            ))),
                        //  TextButtonをラップする。
                        Container(
                            color: baseColor,
                            child: Center(
                                child: Container(
                              height: 40,
                              width: 100,
                              decoration: BoxDecoration(
                                color: baseColor,
                                borderRadius: BorderRadius.circular(30),
                                boxShadow: [
                                  BoxShadow(
                                    color: Colors.orange.withOpacity(0.4),
                                    spreadRadius: 5,
                                    blurRadius: 7,
                                    offset: const Offset(3, 3),
                                  ),
                                  BoxShadow(
                                    color: Colors.white.withOpacity(0.5),
                                    spreadRadius: 5,
                                    blurRadius: 7,
                                    offset: const Offset(-3, -3),
                                  ),
                                ],
                              ),
                              child: TextButton(
                                style: TextButton.styleFrom(
                                  backgroundColor: Colors.orange.shade100,
                                ),
                                child: const Text(
                                  'Login',
                                  style: TextStyle(
                                    color: Colors.orange,
                                    fontWeight: FontWeight.bold,
                                  ),
                                ),
                                // 省略
                              ),
                            ))),
                      ],
                    ),
                    const SizedBox(height: 20)
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

```dart
## 最後
今回リリースしたアプリのMemoPlaceはメモに位置情報をつけたアプリです。
登録した位置に近づくとプッシュ通知がされ、メモした内容を思い出させてくれます。
今回の記事執筆時点(6/1)時点ではニューモーフィズムにアプデしていませんので随時アプデしていきたいと思います。
https://apps.apple.com/jp/app/memoplace/id6479640629

Discussion