🥃
FlutterでちょこっとGlassmorphism
最近話題(?)のGlassmorphicなUIをFlutterでちょこっと試してみました。
Material DesignのWidgetをベースにそれっぽくしただけなので、細かいところはご自身で修正しながら色々試してみてください。
できたもの
glassmorphic_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../widgets/glass_app_bar.dart';
import '../widgets/glass_button.dart';
import '../widgets/glass_container.dart';
import '../widgets/glass_drawer.dart';
import '../widgets/glass_floating_action_button.dart';
import '../widgets/glass_list_tile.dart';
import '../widgets/glass_text.dart';
class GlassmorphismScreen extends StatelessWidget {
Widget build(BuildContext context) {
// 画面上部のバーを透明にする
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(statusBarColor: Colors.transparent),
);
return Stack(
children: [
Container(
decoration: BoxDecoration(
image: const DecorationImage(
image: AssetImage('path/to/background/image'),
fit: BoxFit.cover,
),
),
),
Scaffold(
backgroundColor: Colors.transparent,
drawerScrimColor: Colors.black.withOpacity(0.2),
extendBodyBehindAppBar: true,
appBar: GlassAppBar(
title: const GlassText('GlassAppBar'),
centerTitle: true,
),
drawer: const GlassDrawer(),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
GlassContainer(
width: 300,
height: 300,
child: Center(
child: GlassText(
'GlassContainer',
fontSize: 32,
),
),
),
const SizedBox(height: 32),
GlassListTile(
leading: CircleAvatar(
backgroundColor: Colors.white.withOpacity(0.3),
),
title: const GlassText('GlassListTile'),
subtitle: const GlassText(
'Subtitle',
opacity: 0.2,
),
trailing: Icon(
Icons.arrow_forward_ios,
color: Colors.white.withOpacity(0.3),
),
onTap: () {},
onLongPress: () {},
),
const SizedBox(height: 32),
GlassButton(
child: const GlassText('GlassButton'),
onPressed: () {},
),
const SizedBox(height: 32),
GlassButton(
child: const GlassText('Show GlassBottomSheet'),
onPressed: () {
const double radius = 16;
final BorderRadius borderRadius = BorderRadius.vertical(
top: Radius.circular(16),
);
showModalBottomSheet(
context: context,
shape:
RoundedRectangleBorder(borderRadius: borderRadius),
backgroundColor: Colors.transparent,
barrierColor: Colors.black.withOpacity(0.2),
builder: (BuildContext ctx) {
return GlassContainer(
radius: radius,
borderRadius: borderRadius,
child: Center(
child: GlassText(
'GlassBottomSheet',
fontSize: 32,
),
),
);
},
);
},
),
],
),
),
),
floatingActionButton: GlassFloatingActionButton(
child: Icon(
Icons.add,
color: Colors.white.withOpacity(0.4),
),
onPressed: () {},
),
),
],
);
}
}
GlassText
glass_text.dart
import 'package:flutter/material.dart';
class GlassText extends StatelessWidget {
const GlassText(
this.data, {
this.color = Colors.white,
this.opacity = 0.4,
this.fontSize,
this.fontWeight = FontWeight.bold,
});
final String data;
final Color color;
final double opacity;
final double fontSize;
final FontWeight fontWeight;
Widget build(BuildContext context) {
return Text(
data,
style: TextStyle(
color: color.withOpacity(opacity),
fontSize: fontSize,
fontWeight: fontWeight,
),
);
}
}
GlassContainer
glass_container.dart
import 'dart:ui';
import 'package:flutter/material.dart';
class GlassContainer extends StatelessWidget {
GlassContainer({
this.width,
this.height,
this.radius = 16,
BorderRadius borderRadius,
this.blur = 20,
this.child,
}) {
this.borderRadius = borderRadius ?? BorderRadius.circular(radius);
}
final double width;
final double height;
final double radius;
BorderRadius borderRadius;
final double blur;
final Widget child;
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
blurRadius: 24,
spreadRadius: 16,
color: Colors.black.withOpacity(0.2),
)
],
),
// ぼかす範囲をContainerの背後のみにする
child: ClipRRect(
borderRadius: borderRadius,
child: BackdropFilter(
// 背景をぼかす
filter: ImageFilter.blur(
sigmaX: blur,
sigmaY: blur,
),
child: Container(
height: height,
width: width,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
border: Border.all(
width: 1.5,
color: Colors.white.withOpacity(0.2),
),
borderRadius: borderRadius,
),
child: child,
),
),
),
);
}
}
GlassListTile
glass_list_tile.dart
import 'package:flutter/material.dart';
import './glass_container.dart';
class GlassListTile extends StatelessWidget {
const GlassListTile({
Key key,
this.borderRadius = 16,
this.leading,
this.title,
this.subtitle,
this.trailing,
this.onTap,
}) : super(key: key);
final double borderRadius;
final Widget leading;
final Widget title;
final Widget subtitle;
final Widget trailing;
final void Function() onTap;
Widget build(BuildContext context) {
return GlassContainer(
radius: borderRadius,
child: Center(
child: Material(
color: Colors.transparent,
child: ListTile(
leading: leading,
title: title,
subtitle: subtitle,
trailing: trailing,
onTap: onTap,
),
),
),
);
}
}
GlassButton
glass_button.dart
import 'package:flutter/material.dart';
import './glass_container.dart';
class GlassButton extends StatelessWidget {
const GlassButton({
this.radius = 16,
this.child,
this.onPressed,
this.onLongPressed,
});
final double radius;
final Widget child;
final void Function() onPressed;
final void Function() onLongPressed;
Widget build(BuildContext context) {
return RawMaterialButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(radius),
),
onPressed: onPressed,
onLongPress: onLongPressed,
child: GlassContainer(
radius: radius,
child: Padding(
padding: const EdgeInsets.all(10),
child: child,
),
),
);
}
}
GlassAppBar
glass_app_bar.dart
import 'package:flutter/material.dart';
import './glass_container.dart';
class GlassAppBar extends StatelessWidget with PreferredSizeWidget {
GlassAppBar({
this.leading,
this.title,
this.centerTitle,
}) : preferredSize = Size.fromHeight(kToolbarHeight);
final Widget leading;
final Widget title;
final bool centerTitle;
final Size preferredSize;
Widget build(BuildContext context) {
return AppBar(
backgroundColor: Colors.transparent,
flexibleSpace: const GlassContainer(radius: 0),
leading: leading,
title: title,
centerTitle: centerTitle,
iconTheme: const IconThemeData(
color: Colors.white,
opacity: 0.4,
),
);
}
}
GlassDrawer
glass_drawer.dart
import 'package:flutter/material.dart';
import './glass_container.dart';
import './glass_text.dart';
class GlassDrawer extends StatelessWidget {
const GlassDrawer();
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(
canvasColor: Colors.transparent,
),
child: Drawer(
child: GlassContainer(
borderRadius: BorderRadius.only(
topRight: Radius.circular(16),
bottomRight: Radius.circular(16),
),
child: ListView(
padding: const EdgeInsets.all(0),
children: [
DrawerHeader(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
backgroundColor: Colors.white.withOpacity(0.3),
foregroundColor: Colors.white.withOpacity(0.3),
radius: 30,
child: const Icon(
Icons.person,
size: 32,
),
),
const SizedBox(height: 8),
const GlassText(
'Glass Drawer',
fontSize: 24,
),
const GlassText(
'example@example.com',
opacity: 0.25,
),
],
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
),
),
ListView.separated(
padding: const EdgeInsets.all(0),
shrinkWrap: true,
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: GlassText('Drawer Item ${index + 1}'),
onTap: () {},
);
},
separatorBuilder: (BuildContext context, int index) {
return Divider(
thickness: 1.5,
indent: 10,
endIndent: 10,
color: Colors.white.withOpacity(0.1),
);
},
),
],
),
),
),
);
}
}
ListViewにpaddingを設定しないと、Drawerと重なっている部分のみステータスバーの色が透明になりませんでした。
GlassFloatingActionButton
glass_floating_action_button.dart
import 'dart:ui';
import 'package:flutter/material.dart';
class GlassFloatingActionButton extends StatelessWidget {
const GlassFloatingActionButton({
this.onPressed,
this.child,
});
final Function() onPressed;
final Widget child;
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(30),
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 30,
sigmaY: 30,
),
child: FloatingActionButton(
elevation: 20,
onPressed: onPressed,
backgroundColor: Colors.transparent,
shape: CircleBorder(
side: BorderSide(
color: Colors.white.withOpacity(0.2),
width: 1.5,
),
),
child: child,
),
),
);
}
}
GlassBottomSheet
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
barrierColor: Colors.black.withOpacity(0.2),
builder: (BuildContext ctx) {
return GlassContainer(
child: Center(
child: GlassText(
'GlassBottomSheet',
fontSize: 32,
),
),
);
},
);
Discussion
めちゃくちゃ参考になりました!
こちらのGlassAppbarを使ってTabを実装したいのですが、
bottomを追加すると、文字が透明になります。
高さはAppbarのクラスから、
preferredSize = Size.fromHeight(toolbarHeight ??
kToolbarHeight + (bottom?.preferredSize.height ?? 0.0));
を取得してきて、Tabを表示するスペースは確保しました。
もしよろしければ、カスタムAppbarでのタブの実装方法も教えていただけると助かります!