🌺
【Dart/Flutter】スクロール時FloatingActionButton.extended⇔FloatingActionButton
パッケージ「flutter_scrolling_fab_animated」をシンプルにFloatingActionButton.extended⇔FloatingActionButtonと考えるアイデアとなります。
【Dart/Flutter】スクロール時FloatingActionButton.extended⇔FloatingActionButton
-
実行環境
-
DartPadやAndroid Studio等で実行
- Based on Flutter 3.7.3 Dart SDK 2.19.2
-
DartPadやAndroid Studio等で実行
-
参考
実行結果
コード
DartPadに貼り付け確認可能
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(useMaterial3: true),
home: const Home(),
);
}
}
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<String> items = [for (int i = 0; i < 30; i++) i.toString()];
final ScrollController _scrollController = ScrollController();
double indicator = 10.0;
bool onTop = true;
void dispose() {
_scrollController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Scrolling Fab Animated Demo'),
),
body: ListView.builder(
controller: _scrollController,
itemCount: items.length,
itemBuilder: (BuildContext ctxt, int index) {
return Card(
child: ListTile(
title: Text(items[index]),
));
}),
floatingActionButton: ScrollingFabAnimated(
icon: const Icon(
Icons.add,
color: Colors.white,
),
text: const Text(
'Add',
style: TextStyle(color: Colors.white, fontSize: 16.0),
),
onPress: () {
print('OnPress!!');
},
scrollController: _scrollController,
// animateIcon: true,
// inverted: false,
),
);
}
}
/// Widget to animate the button when scroll down
class ScrollingFabAnimated extends StatefulWidget {
/// Function to use when press the button
final GestureTapCallback? onPress;
/// Double value to set the button elevation
final double? elevation;
/// Double value to set the button width
final double? width;
/// Double value to set the button height
final double? height;
/// Value to set the duration for animation
final Duration? duration;
/// Widget to use as button icon
final Widget? icon;
/// Widget to use as button text when button is expanded
final Widget? text;
/// Value to set the curve for animation
final Curve? curve;
/// ScrollController to use to determine when user is on top or not
final ScrollController? scrollController;
/// Double value to set the boundary value when scroll animation is triggered
final double? limitIndicator;
/// Color to set the button background color
final Color? color;
/// Value to indicate if animate or not the icon
final bool? animateIcon;
/// Value to inverte the behavior of the animation
final bool? inverted;
/// Double value to set the button radius
final double? radius;
const ScrollingFabAnimated(
{Key? key,
required this.icon,
required this.text,
required this.onPress,
required this.scrollController,
this.elevation = 5.0,
this.width = 120.0,
this.height = 60.0,
this.duration = const Duration(milliseconds: 250),
this.curve,
this.limitIndicator = 10.0,
this.color,
this.animateIcon = true,
this.inverted = false,
this.radius})
: super(key: key);
_ScrollingFabAnimatedState createState() => _ScrollingFabAnimatedState();
}
class _ScrollingFabAnimatedState extends State<ScrollingFabAnimated> {
/// Double value for tween ending
double _endTween = 100;
void setState(VoidCallback fn) {
if (mounted) super.setState(fn);
}
void initState() {
super.initState();
if (widget.inverted!) {
setState(() {
_endTween = 0;
});
}
_handleScroll();
}
void dispose() {
widget.scrollController!.removeListener(() {});
super.dispose();
}
/// Function to add listener for scroll
void _handleScroll() {
ScrollController scrollController = widget.scrollController!;
scrollController.addListener(() {
if (scrollController.position.pixels > widget.limitIndicator! &&
scrollController.position.userScrollDirection ==
ScrollDirection.reverse) {
setState(() {
_endTween = widget.inverted! ? 100 : 0;
});
} else if (scrollController.position.pixels <= widget.limitIndicator! &&
scrollController.position.userScrollDirection ==
ScrollDirection.forward) {
setState(() {
_endTween = widget.inverted! ? 0 : 100;
});
}
});
}
Widget build(BuildContext context) {
return Card(
elevation: widget.elevation,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(widget.height! / 2))),
child: TweenAnimationBuilder(
tween: Tween<double>(begin: 0, end: _endTween),
duration: widget.duration!,
builder: (BuildContext _, double size, Widget? child) {
double widthPercent = (widget.width! - widget.height!).abs() / 100;
bool isFull = _endTween == 100;
return SizedBox(
height: widget.height,
width: widget.height! + widthPercent * size,
child: Transform.rotate(
angle: widget.animateIcon! ? (3.6 * math.pi / 180) * size : 0,
child: isFull
? FloatingActionButton.extended(
onPressed: widget.onPress,
label: widget.text!,
icon: widget.icon,
)
: FloatingActionButton(
onPressed: widget.onPress,
child: widget.icon,
),
),
);
},
),
);
}
}
- ポイントは以下
return SizedBox(
height: widget.height,
width: widget.height! + widthPercent * size,
child: Transform.rotate(
angle: widget.animateIcon! ? (3.6 * math.pi / 180) * size : 0,
child: isFull
? FloatingActionButton.extended(
onPressed: widget.onPress,
label: widget.text!,
icon: widget.icon,
)
: FloatingActionButton(
onPressed: widget.onPress,
child: widget.icon,
),
),
);
Discussion