1

I'm experimenting with SpeedDial and I wanted to know if it's possible to have a SpeedDial and a floatingActionButton in the same screen.

The next code has worked for me for having two FloatingActionButton, but when adding the speeddial, the limits of the screen go to hell (it says bottom overflowed by infinity pixels).

  Widget build(BuildContext context) {
    return Scaffold(
        appBar: searchBar.build(context),
        body: Container(),
        floatingActionButton: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            buildSpeedDial(),
            FloatingActionButton(
              onPressed: (){},
              child: Icon(Icons.add),
              backgroundColor: Colors.green,
            ),
            FloatingActionButton(
              onPressed: (){},
              child: Icon(Icons.add),
              backgroundColor: Colors.green,
            ),
          ],
        )
    );
  }

I'm using the same buildSpeedDial() as in the example of flutter_speed_dial 1.2.5 page.

The console shows this:

Error: Cannot hit test a render box that has never been laid out.
The hitTest() method was called on this RenderBox: RenderFlex#aa804 NEEDS-LAYOUT NEEDS-PAINT:
  needs compositing
  creator: Column ← Container ← Positioned ← Stack ← SpeedDial ← Column ← Transform ← RotationTransition ← Transform ← ScaleTransition ← Stack ← _FloatingActionButtonTransition ← ⋯
  parentData: right=0.0; bottom=0.0; offset=Offset(0.0, 0.0)
  constraints: MISSING
  size: MISSING
  direction: vertical
  mainAxisAlignment: end
  mainAxisSize: max
  crossAxisAlignment: end
  textDirection: ltr
  verticalDirection: down
Unfortunately, this object's geometry is not known at this time, probably because it has never been laid out. This means it cannot be accurately hit-tested.
If you are trying to perform a hit test during the layout phase itself, make sure you only hit test nodes that have completed layout (e.g. the node's children, after their layout() method has been called).
Akif
  • 7,098
  • 7
  • 27
  • 53
  • If my answer is helped you, you can upvote and accept that :) https://stackoverflow.com/a/65171212/10659482 – Akif Dec 09 '20 at 12:34

3 Answers3

1

One solution would be to constrain the speedDial as follows:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Flutter Speed Dial')),
    body: buildBody(),
    floatingActionButton: Column(
      mainAxisAlignment: MainAxisAlignment.end,
      children: [
        SizedBox(
          height: 60,
          width: 60,
          child: buildSpeedDial(),
        ),
        SizedBox(height: 10),
        FloatingActionButton(
          onPressed: () {},
          child: Icon(Icons.add),
          backgroundColor: Colors.green,
        ),
        SizedBox(height: 10),
        FloatingActionButton(
          onPressed: () {},
          child: Icon(Icons.add),
          backgroundColor: Colors.green,
        ),
      ],
    ),
  );
}

Which would give this result:

example app

Stefano Amorelli
  • 4,553
  • 3
  • 14
  • 30
  • I tried it and it doesn't blur the background as in the "original". If that's not a problem for you, it works alright. I'm now checking a widget called BackdropFilter to see if I can make it blurry. Thank you! – pepe calero Dec 06 '20 at 19:13
1

SpeedDial is using the Stack widget while building itself. So, I have a dirty solution using Stack, too:


import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

import 'package:flutter_speed_dial/flutter_speed_dial.dart';

void main() {
  runApp(MaterialApp(home: MyApp(), title: 'Flutter Speed Dial Examples'));
}

class MyApp extends StatefulWidget {
  @override
  MyAppState createState() => MyAppState();
}

class MyAppState extends State<MyApp> with TickerProviderStateMixin {
  ScrollController scrollController;
  bool dialVisible = true;

  @override
  void initState() {
    super.initState();

    scrollController = ScrollController()
      ..addListener(() {
        setDialVisible(scrollController.position.userScrollDirection ==
            ScrollDirection.forward);
      });
  }

  void setDialVisible(bool value) {
    setState(() {
      dialVisible = value;
    });
  }

  Widget buildBody() {
    return ListView.builder(
      controller: scrollController,
      itemCount: 30,
      itemBuilder: (ctx, i) => ListTile(title: Text('Item $i')),
    );
  }

  SpeedDial buildSpeedDial() {
    return SpeedDial(
      animatedIcon: AnimatedIcons.menu_close,
      animatedIconTheme: IconThemeData(size: 22.0),
      // child: Icon(Icons.add),
      onOpen: () => print('OPENING DIAL'),
      onClose: () => print('DIAL CLOSED'),
      visible: dialVisible,
      curve: Curves.bounceIn,
      children: [
        SpeedDialChild(
          child: Icon(Icons.accessibility, color: Colors.white),
          backgroundColor: Colors.deepOrange,
          onTap: () => print('FIRST CHILD'),
          label: 'First Child',
          labelStyle: TextStyle(fontWeight: FontWeight.w500),
          labelBackgroundColor: Colors.deepOrangeAccent,
        ),
        SpeedDialChild(
          child: Icon(Icons.brush, color: Colors.white),
          backgroundColor: Colors.green,
          onTap: () => print('SECOND CHILD'),
          label: 'Second Child',
          labelStyle: TextStyle(fontWeight: FontWeight.w500),
          labelBackgroundColor: Colors.green,
        ),
        SpeedDialChild(
          child: Icon(Icons.keyboard_voice, color: Colors.white),
          backgroundColor: Colors.blue,
          onTap: () => print('THIRD CHILD'),
          labelWidget: Container(
            color: Colors.blue,
            margin: EdgeInsets.only(right: 10),
            padding: EdgeInsets.all(6),
            child: Text('Custom Label Widget'),
          ),
        ),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Flutter Speed Dial')),
      body: buildBody(),
      floatingActionButton: Stack(
        alignment: Alignment.bottomRight,
        fit: StackFit.expand,
        overflow: Overflow.visible,
        children: [
          Stack(
            alignment: Alignment.bottomRight,
            fit: StackFit.expand,
            overflow: Overflow.visible,
            children: [
              buildSpeedDial(),
            ],
          ),
          // Here is a FAB
          Stack(
            alignment: Alignment.bottomCenter,
            children: [
              FloatingActionButton(
                onPressed: () {
                  print("object");
                },
                child: Icon(Icons.add),
                backgroundColor: Colors.green,
              ),
            ],
          ),
          // Here, one more FAB!
          Stack(
            alignment: Alignment.bottomLeft,
            children: [
              Padding(
                padding: const EdgeInsets.fromLTRB(40, 0, 0, 0),
                child: FloatingActionButton(
                  onPressed: () {
                    print("object");
                  },
                  child: Icon(Icons.remove),
                  backgroundColor: Colors.red,
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}


It will look like this:

enter image description here

enter image description here

Akif
  • 7,098
  • 7
  • 27
  • 53
1

I modified the approach of Akif to make two buttons one on top of the other. Here's the result:


floatingActionButton: Stack(
          alignment: Alignment.bottomRight,
          children: [
            Positioned(
              bottom: 70,
              child: Container(
                child: FloatingActionButton(
                  onPressed: (){
                    Navigator.push(context, MaterialPageRoute(builder:(context)=> FormNewActivity()));
                  },
                  child: Icon(Icons.add,color:Colors.white,size: 30),
                  backgroundColor: Colors.green,
                )
              ),
            ),
            Stack(
              alignment: Alignment.bottomRight,
              children: [
                buildSpeedDial(),
              ],
            ),
          ],
        ),

The result looks like this:

Closed

Open