8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Flutterで折り畳み式のFloatingActionButtonを実装する(Speed Dialの実装)

Last updated at Posted at 2019-10-04

Material Designで提唱されているSpeed Dialの実装について簡単に説明します。

優秀なパッケージが既に用意されております。
speed_dial 0.0.1
お急ぎの方は是非に

1.Preliminary

ScaffoldのFAB(FloatActionButton)について

final Widget floatingActionButton;

で定義されております。

ご察しの通り、RowやColumnなどで様々なWidgetを
Scaffoldに入れいることができます。

Flutterで2つのFloatingActionButtonを表示する方法

尚、この記事はStackを利用して実装していきます。(捻りとは :fearful:)

2.BlurBackground

class FloatButtonBackground extends StatelessWidget {

  final Color color;

  FloatButtonBackground({this.color});

  @override
  Widget build(BuildContext context) {
    // TODO: implement buier
    return BackdropFilter(
      filter: ImageFilter.blur(sigmaX: 4.0, sigmaY: 4.0),
      child: Container(
        color: color,
      ),
    );
  }
}

3.FoldAnimation

class FoldAnimationWrap extends StatefulWidget {

  final Duration duration;
  final Curve animationCurve;
  final bool isExpanded;

  final Widget child;

  FoldAnimationWrap({@required this.duration, this.animationCurve = Curves.ease,@required this.isExpanded, this.child});

  @override
  State<StatefulWidget> createState() {
    return _FoldAnimationWrap();
  }
}

class _FoldAnimationWrap extends State<FoldAnimationWrap> with TickerProviderStateMixin {

  AnimationController _controller;
  Animation<double> _expandAnimation;
  
  @override
  void initState() {
    _controller = AnimationController(
      duration: widget.duration,
      vsync: this,
      reverseDuration: widget.duration,
    );
    _expandAnimation = _controller.drive(CurveTween(curve: widget.animationCurve));
    if(widget.isExpanded){
      _controller.value = 1;
    }
  }

  @override
  void didUpdateWidget(FoldAnimationWrap oldWidget) {
    super.didUpdateWidget(oldWidget);
    if(oldWidget.isExpanded != widget.isExpanded) {
      if (widget.isExpanded) {
        _controller.forward();
      }
      else {
        _controller.reverse();
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return FadeTransition(
      opacity: _expandAnimation,
      child: widget.child,
    );
  }

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }
}

4.FoldFloatButtonWrap

SpeedDialを構成するクラスになります。

class FoldFloatButtonWrap extends StatelessWidget {

  final bool isExpanded;

  //** 常に表示されるFAB(Sampleのバーガーメニュー) **//
  final Widget floatButton;

  //** 折り畳まれれるFAB **//
  final List<Widget> expandedWidget;
  final Duration foldAnimationDuration = Duration(milliseconds: 400);

  FoldFloatButtonWrap({
    @required this.isExpanded,
    @required this.floatButton,
    @required this.expandedWidget,
  });

  @override
  Widget build(BuildContext context) {
    return Stack(
      fit: StackFit.expand,
      overflow: Overflow.visible,
      children: <Widget>[
        //** Background **//
        Positioned(
          top: 0,
          left: 0,
          right: -32,  //Scaffoldによるマージン
          bottom:  -32,
          child:FoldAnimationWrap(
            isExpanded: isExpanded,
            duration: foldAnimationDuration,
            child: FloatButtonBackground(color: Color.fromARGB(50, 0, 0, 0)),
          ),
        ),

        //** FAB **//
        Positioned(
          bottom: 0,
          right: 0,
          child: Column(
            children: <Widget>[
              FoldAnimationWrap(
                isExpanded: isExpanded,
                duration: foldAnimationDuration,
                child: Column(children: expandedWidget),
              ),
              floatButton,
            ],
          )
        ),
      ],
    );
  }
}

5.Implement Widget

Speed Dialの実装個所
既存のFABをFoldFloatButtonWrapで囲んで
最後にsetStateでisExpandedで制御すれば完成です。

bool isExpanded = false;

@override
Widget build(BuildContext context) {
  return Scaffold(

    backgroundColor: ...,
    body: ...,

    floatingActionButton: 
      FoldFloatButtonWrap(
        isExpanded: isExpanded,
        floatButton: FloatingActionButton(
          child: isExpanded ? Icon(Icons.close) : Icon(Icons.menu) ,
          backgroundColor: Colors.deepOrangeAccent,
          onPressed: (){
            setState(() {
              isExpanded ^= true;
            });
          },
        ),
        expandedWidget: <Widget>[
          FloatingActionButton(
            child: Icon(Icons.content_copy, size: 16),
            backgroundColor: Colors.deepOrangeAccent,
            mini: true,
            onPressed: (){
              setState(() {
                isExpanded ^= true;
                text = "Copied";
              });
            },
          ),
          SizedBox(height: 4),
          FloatingActionButton(
            child: Icon(Icons.refresh,size: 16,),
            backgroundColor: Colors.deepOrangeAccent,
            mini: true,
            onPressed: (){
              setState(() {
                isExpanded ^= true;
                text = "Refrashed";
              });
            },
          ),
          SizedBox(height: 4),
          FloatingActionButton(
            child: Icon(Icons.delete,size: 16,),
            backgroundColor: Colors.deepOrangeAccent,
            mini: true,
            onPressed: (){
              setState(() {
                isExpanded ^= true;
                text = "Deleted";
              });
            },
          ),
          SizedBox(height: 8),
        ],
    ),
  );
}

5.最後に

画面制御やアニメーションなどとっつきにくいと感じて
手間を惜しんでパッケージをついつい利用しがちになりますが、
一歩踏み込んで自分なりに解釈して実装してみるのも一興かと思います。

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?