效果图
源代码
以下代码基于flutter 3.3.3 版本,不同之间版本稍有差别
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[300],
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Pretty3DButton(
text: "Press Me!",
height: 300,
width: 300,
blurRadius: 5,
spreadRadius: 1,
offset: 4,
),
])));
}
}
class Pretty3DButton extends StatefulWidget {
final String text;
final double height;
final double width;
final double blurRadius;
final double offset;
final double spreadRadius;
const Pretty3DButton(
{super.key,
required this.text,
required this.height,
required this.width,
required this.blurRadius,
required this.offset,
required this.spreadRadius});
@override
State<StatefulWidget> createState() {
return Pretty3DButtonState();
}
}
class Pretty3DButtonState extends State<Pretty3DButton> {
bool _isElevated = true;
final animationDuration = const Duration(milliseconds: 80);
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (TapDownDetails details) async {
setState(() {
_isElevated = false;
});
},
onTapUp: (TapUpDetails details) async {
setState(() {
_isElevated = true;
});
},
child: AnimatedContainer(
duration: animationDuration,
height: widget.height,
width: widget.width,
decoration: BoxDecoration(
color: Colors.blue[300],
borderRadius: BorderRadius.circular(10),
boxShadow: _isElevated
? [
BoxShadow(
color: Colors.blue[500]!,
offset: Offset(widget.offset, widget.offset), // 偏移量
blurRadius: widget.blurRadius, // 模糊半径
spreadRadius: widget.spreadRadius, // 扩散半径
),
BoxShadow(
color: Colors.white,
offset: Offset(-widget.offset, -widget.offset),
blurRadius: widget.blurRadius,
spreadRadius: widget.spreadRadius,
)
]
: null),
child: Center(
child: Text(
widget.text,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: _isElevated ? Colors.white : Colors.grey[300]),
)),
));
}
}
实现原理
-
定义全局状态码 _isElevated 区分按下与弹起的状态
-
利用 Container自带的Decoration中 boxShadow 属性,先实现 多级阴影,做出立体效果
-
将Container替换成 AnimatedContainer ,支持状态之间通过一定的动画时长去切换
-
GestureDetector 响应按下和弹起事件,分别设置 _isElevated 状态值为 true 和 false并刷新 组件状态
支持的属性
| 属性名 | 类型 | 说明 |
|---|---|---|
| text | String | 内部文案 |
| height | double | 组件高度 |
| width | double | 组件宽度 |
| blurRadius | double | 阴影模糊半径 |
| spreadRadius | double | 阴影扩散半径 |
| offset | double | 阴影相对于Container自身的偏移量 |