発端
人類「ニューモーフィズムなUI欲しい」
蝦「はい」
ニューモーフィズム(Neumorphism)とは
ニューモーフィズムは、2020年頃から注目されるようになったUIデザインのスタイル。
AI曰く
「ソフトな影効果を使用して、要素があたかも表面から浮き出ているような、あるいは押し込まれているような立体的な表現を実現します。通常、背景色と似た色調で、微妙な明暗の差を付けることで、この効果を生み出します。」
(Claude3.5)
具体例
やり方
まず実装が簡単なHTMLとCSSで実装し、それをFlutter上で再現するという流れでやってみる。(FlutterはWebアプリとしてビルドもできるのだから、WebでできるならFlutterでもできるのでは?という考え。)
HTMLとCSSでの実装
上で取り上げたSkeuomorph Mobile Bankingでは、右上からページ内の要素に光を当てた状態を表現している。そのため光が当たる部分を明るく、当たっていない部分を暗く見せることができれば凹凸を表現できる。
背景色
背景が白だと光が当たっている面を表現することが難しくなるため、ニューモフィックなUIは背景色を白ではなく灰色がかった感じの色にすると実装しやすい。
body {
background-color: #e0e5ec;
}
要素が飛び出す場合
この場合は左上が明るく、右下が暗くなれば良い。
.button-raised {
box-shadow:
6px 6px 12px #b8bec7,
-6px -6px 12px #ffffff;
transition: all 0.2s ease;
}
要素が凹む場合
逆に凹むこの場合は左上が暗く、右下が明るくなれば良い。ただし影が要素の内側に発生する。
.button-pressed {
box-shadow:
inset 6px 6px 12px #b8bec7,
inset -6px -6px 12px #ffffff;
}
Flutterでの実装
上記の方法をFlutterで再現してみる。
既存のパッケージを使う
すでにパッケージが存在する。
clay containerはともかく、flutter_neumorphicは最終更新から3年経過しているため今後使うのは怖い。
パッケージを使わずに実装
飛び出している場合
ContainerでラップしてCSSのbox-shadowをそのまま再現すれば表現可能。
class RaisedButton extends StatelessWidget {
const RaisedButton({
required this.onPressed,
required this.text,
super.key,
});
final VoidCallback onPressed;
final String text;
@override
Widget build(BuildContext context) {
const double distanceValue = 4;
const double blur = 8;
return Container(
height: 64,
width: double.infinity,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: highlightColor,
offset: Offset(distanceValue, distanceValue),
blurRadius: blur,
),
BoxShadow(
color: shadowColor,
offset: Offset(-1 * distanceValue, -1 * distanceValue),
blurRadius: blur,
),
],
),
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(backgroundColor),
overlayColor: WidgetStateProperty.all(Colors.transparent),
shadowColor: WidgetStateProperty.all(Colors.transparent),
elevation: WidgetStateProperty.all(0),
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
onPressed: onPressed,
child: Text(text),
),
);
}
}
凹んでいる場合
基本は飛び出ている場合と同じで、内側に影を作る必要がある。
標準のBoxShadowでは内側に影をつけることができないため、flutter_inset_shadowを用いる。
class PushedButton extends StatelessWidget {
const PushedButton({
required this.onPressed,
required this.text,
super.key,
});
final VoidCallback onPressed;
final String text;
@override
Widget build(BuildContext context) {
const double distanceValue = 4;
const double blur = 8;
return Container(
height: 64,
width: double.infinity,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: highlightColor,
offset: Offset(distanceValue, distanceValue),
blurRadius: blur,
inset: true // 影を内側にする
),
BoxShadow(
color: shadowColor,
offset: Offset(-1 * distanceValue, -1 * distanceValue),
blurRadius: blur,
inset: true // 影を内側にする
),
],
),
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.transparent), // 影が見えなくなるため透明にする
overlayColor: WidgetStateProperty.all(Colors.transparent),
shadowColor: WidgetStateProperty.all(Colors.transparent),
elevation: WidgetStateProperty.all(0),
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
onPressed: onPressed,
child: Text(text),
),
);
}
}
複数の影(+内容に応じて丸み)を付ける事が可能なら凹凸を表現できるため、Containerでラップ可能なウィジェットであれば大体がニューモフィックなUIとして実装できるのでは?と考え中

