Floating Action Button over a ListView Flutter

9,488

Solution 1

Updated

This appears to work fine for me on an Android device. Here is an image of it.

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    theme: ThemeData(accentColor: Colors.black87),
    home: HomePage(),
  ));
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Overview'),
        backgroundColor: Color.fromRGBO(53, 73, 94, 0.9),
        elevation: 0.0,
        leading: Container(),
      ),
      body: HomePageBody(),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          print("pressed it");
        },
      ),
    );
  }
}

class HomePageBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Color.fromRGBO(53, 73, 94, 0.9),
      child: CustomScrollView(
        scrollDirection: Axis.vertical,
        slivers: <Widget>[
          SliverPadding(
            padding: const EdgeInsets.symmetric(vertical: 0.0),
            sliver: SliverFixedExtentList(
              itemExtent: 152.0,
              delegate: SliverChildBuilderDelegate(
                (context, index) => PlanetRow(planets[index]),
                childCount: planets.length,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class PlanetRow extends StatelessWidget {
  final Planet planet;

  PlanetRow(this.planet);

  Widget _planetValue({String value, String image}) {
    return Row(children: <Widget>[
      Image.asset(image, height: 12.0),
      Container(width: 8.0),
      Text(
        planet.gravity,
        style: Style.smallTextStyle,
      )
    ]);
  }

  @override
  Widget build(BuildContext context) {
    final planetThumbnail = Container(
      margin: EdgeInsets.symmetric(
        vertical: 16.0,
      ),
      alignment: FractionalOffset.centerLeft,
      child: Image(
        image: AssetImage(planet.image),
        height: 92.0,
        width: 92.0,
      ),
    );

    final planetCardContent = Container(
      margin: EdgeInsets.fromLTRB(76.0, 16.0, 16.0, 16.0), // left, top, right, bottom
      constraints: BoxConstraints.expand(),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Container(height: 4.0),
          Text(
            planet.name,
            style: Style.titleTextStyle,
          ),
          Container(height: 5.0),
          Text(
            planet.location,
            style: Style.commonTextStyle,
          ),
          Container(
              margin: EdgeInsets.symmetric(vertical: 8.0), height: 2.0, width: 18.0, color: new Color(0xff00c6ff)),
          Row(
            children: <Widget>[
              Expanded(child: _planetValue(value: planet.distance, image: 'assets/img/ic_distance.png')),
              Expanded(child: _planetValue(value: planet.gravity, image: 'assets/img/ic_gravity.png'))
            ],
          )
        ],
      ),
    );

    final planetCard = Container(
      child: planetCardContent,
      height: 124.0,
      margin: EdgeInsets.only(
        left: 46.0,
      ),
      decoration: BoxDecoration(
        color: Color(0xFF333366),
        shape: BoxShape.rectangle,
        borderRadius: BorderRadius.circular(8.0),
        boxShadow: <BoxShadow>[
          BoxShadow(
            color: Colors.black12,
            blurRadius: 10.0,
            offset: Offset(0.0, 10.0),
          ),
        ],
      ),
    );

    return Container(
      height: 120.0,
      margin: const EdgeInsets.symmetric(
        vertical: 12.0,
        horizontal: 24.0,
      ),
      child: Stack(
        children: <Widget>[
          planetCard,
          planetThumbnail,
        ],
      ),
    );
  }
}

class Planet {
  final String id;
  final String name;
  final String location;
  final String distance;
  final String gravity;
  final String description;
  final String image;
  final String picture;

  const Planet(
      {this.id, this.name, this.location, this.distance, this.gravity, this.description, this.image, this.picture});
}

List<Planet> planets = [
  const Planet(
      id: "1",
      name: "Mars",
      location: "Milkyway Galaxy",
      distance: "54.6m Km",
      gravity: "3.711 m/s ",
      description:
          "Mars is the fourth planet from the Sun and the second-smallest planet in the Solar System after Mercury. In English, Mars carries a name of the Roman god of war, and is often referred to as the 'Red Planet' because the reddish iron oxide prevalent on its surface gives it a reddish appearance that is distinctive among the astronomical bodies visible to the naked eye. Mars is a terrestrial planet with a thin atmosphere, having surface features reminiscent both of the impact craters of the Moon and the valleys, deserts, and polar ice caps of Earth.",
      image: "assets/img/mars.png",
      picture: "https://www.nasa.gov/sites/default/files/thumbnails/image/pia21723-16.jpg"),
  const Planet(
      id: "2",
      name: "Neptune",
      location: "Milkyway Galaxy",
      distance: "54.6m Km",
      gravity: "3.711 m/s ",
      description:
          "Neptune is the eighth and farthest known planet from the Sun in the Solar System. In the Solar System, it is the fourth-largest planet by diameter, the third-most-massive planet, and the densest giant planet. Neptune is 17 times the mass of Earth and is slightly more massive than its near-twin Uranus, which is 15 times the mass of Earth and slightly larger than Neptune. Neptune orbits the Sun once every 164.8 years at an average distance of 30.1 astronomical units (4.50×109 km). It is named after the Roman god of the sea and has the astronomical symbol ♆, a stylised version of the god Neptune's trident",
      image: "assets/img/neptune.png",
      picture:
          "https://www.nasa.gov/sites/default/files/styles/full_width_feature/public/images/110411main_Voyager2_280_yshires.jpg"),
  const Planet(
      id: "3",
      name: "Moon",
      location: "Milkyway Galaxy",
      distance: "54.6m Km",
      gravity: "3.711 m/s ",
      description:
          "The Moon is an astronomical body that orbits planet Earth, being Earth's only permanent natural satellite. It is the fifth-largest natural satellite in the Solar System, and the largest among planetary satellites relative to the size of the planet that it orbits (its primary). Following Jupiter's satellite Io, the Moon is second-densest satellite among those whose densities are known.",
      image: "assets/img/moon.png",
      picture: "https://farm5.staticflickr.com/4086/5052125139_43c31b7012.jpg"),
  const Planet(
      id: "4",
      name: "Earth",
      location: "Milkyway Galaxy",
      distance: "54.6m Km",
      gravity: "3.711 m/s ",
      description:
          "Earth is the third planet from the Sun and the only object in the Universe known to harbor life. According to radiometric dating and other sources of evidence, Earth formed over 4 billion years ago. Earth's gravity interacts with other objects in space, especially the Sun and the Moon, Earth's only natural satellite. Earth revolves around the Sun in 365.26 days, a period known as an Earth year. During this time, Earth rotates about its axis about 366.26 times.",
      image: "assets/img/earth.png",
      picture:
          "https://www.nasa.gov/sites/default/files/styles/full_width_feature/public/thumbnails/image/iss042e340851_1.jpg"),
  const Planet(
      id: "5",
      name: "Mercury",
      location: "Milkyway Galaxy",
      distance: "54.6m Km",
      gravity: "3.711 m/s ",
      description:
          "Mercury is the smallest and innermost planet in the Solar System. Its orbital period around the Sun of 88 days is the shortest of all the planets in the Solar System. It is named after the Roman deity Mercury, the messenger to the gods.",
      image: "assets/img/mercury.png",
      picture: "https://c1.staticflickr.com/9/8105/8497927473_2845ae671e_b.jpg"),
];

class Style {
  static final baseTextStyle = const TextStyle(fontFamily: 'Poppins');
  static final smallTextStyle = commonTextStyle.copyWith(
    fontSize: 9.0,
  );
  static final commonTextStyle =
      baseTextStyle.copyWith(color: const Color(0xffb6b2df), fontSize: 14.0, fontWeight: FontWeight.w400);
  static final titleTextStyle =
      baseTextStyle.copyWith(color: Colors.white, fontSize: 18.0, fontWeight: FontWeight.w600);
  static final headerTextStyle =
      baseTextStyle.copyWith(color: Colors.white, fontSize: 20.0, fontWeight: FontWeight.w400);
}

Original

You could just wrap your whole widget in a Scaffold, which has support for floating action buttons.

Here is a simple example of one in action. In your case, I think you'd want your expanded to be the body property of the Scaffold. Like so:

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      floatingActionButton: new FloatingActionButton(
        child: new Icon(Icons.add),
        onPressed: () {
          // TODO add your logic here to add stuff
        },
      ),
      body: new Expanded(
        child: new Container(
          color: Color.fromRGBO(53, 73, 94, 0.9),
          child: new CustomScrollView(
            scrollDirection: Axis.vertical,
            slivers: <Widget>[
              new SliverPadding(
                padding: const EdgeInsets.symmetric(vertical: 0.0),
                sliver: new SliverFixedExtentList(
                  itemExtent: 152.0,
                  delegate: new SliverChildBuilderDelegate(
                    (context, index) => new PlanetRow(planets[index]),
                    childCount: planets.length,
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

Let me know if that works. Because of the custom nature of you scroll view, it's hard to know for sure if you'll run into some more issues.

Also, keep in mind that if you want the UI to update when you tap the floating action button, you'll likely need to update the state by converting your home page to a StatefulWidget.

Solution 2

The most convenient way is to use floatingActionButton as @DerekFredrickson said.
An alternative would be to use Stack as @HaironChaviano said.

Here is an example using Stack:

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  List<int> planets = [];
  int _number = 1;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: <Widget>[
          new Container(
            color: new Color(0xFF736AB7),
            child: new CustomScrollView(
              scrollDirection: Axis.vertical,
              slivers: <Widget>[
                new SliverPadding(
                  padding: const EdgeInsets.symmetric(vertical: 24.0),
                  sliver: new SliverFixedExtentList(
                    itemExtent: 152.0,
                    delegate: new SliverChildBuilderDelegate(
                      (context, index) => Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: new Container(
                          color: Colors.red,
                          child: Center(
                            child: Text('$index'),
                          ),
                        ),
                      ),
                      childCount: planets.length,
                    ),
                  ),
                ),
              ],
            ),
          ),
          Positioned(
            right: 20,
            bottom: 20,
            child: Align(
              alignment: Alignment.bottomRight,
              child: FloatingActionButton(
                child: Icon(Icons.add),
                onPressed: () {
                  setState(() {
                    planets.add(_number++);
                  });
                },
              ),
            ),
          ),
        ],
      ),
    );
  }
}
Share:
9,488
Jack
Author by

Jack

Updated on December 15, 2022

Comments

  • Jack
    Jack over 1 year

    I would like to have a floating action button over my current ListView in the lower right corner but can't figure out how to do so since all my attempts gave me errors. The function of the button would be to add items to the current list of items. A screenshot of the page can be found here.

    The following code snippet is where it all begins (Homepage). From there, it refers to HomePageBody which then refers to PlanetRow. Sorry for the confusion it may cause.

    void main() {
      runApp(MaterialApp(
        theme:
        ThemeData(accentColor: Colors.black87),
        home: HomePage(),
      ));
    }
    
    class HomePage extends StatelessWidget {
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Column(
            children: <Widget>[
              AppBar(
                title: Text('Overview'),
                backgroundColor: Color.fromRGBO(53, 73, 94, 0.9),
                elevation: 0.0,
                leading: Container(),
                ),
              new HomePageBody()
            ],
          ),
        );
      }
    }
    

    HomePageBody:

    class HomePageBody extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new Expanded(
          child: new Container(
            color: Color.fromRGBO(53, 73, 94, 0.9),
            child: new CustomScrollView(
              scrollDirection: Axis.vertical,
              slivers: <Widget>[
                new SliverPadding(
                  padding: const EdgeInsets.symmetric(vertical: 0.0),
                  sliver: new SliverFixedExtentList(
                    itemExtent: 152.0,
                    delegate: new SliverChildBuilderDelegate(
                          (context, index) => new PlanetRow(planets[index]),
                      childCount: planets.length,
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    

    PlanetRow:

    class PlanetRow extends StatelessWidget {
    
      final Planet planet;
    
      PlanetRow(this.planet);
    
      Widget _planetValue({String value, String image}) {
        return new Row(
            children: <Widget>[
              new Image.asset(image, height: 12.0),
              new Container(width: 8.0),
              new Text(planet.gravity, style: Style.smallTextStyle),
            ]
        );
      }
    
      @override
      Widget build(BuildContext context) {
    
        final planetThumbnail = new Container(
          margin: new EdgeInsets.symmetric(
            vertical: 16.0,
          ),
          alignment: FractionalOffset.centerLeft,
          child: new Image(
            image: new AssetImage(planet.image),
            height: 92.0,
            width: 92.0,
          ),
        );
    
        final planetCardContent = new Container(
          margin: new EdgeInsets.fromLTRB(76.0, 16.0, 16.0, 16.0), // left, top, right, bottom
          constraints: new BoxConstraints.expand(),
          child: new Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              new Container(height: 4.0),
              new Text(planet.name,
                style: Style.titleTextStyle,
              ),
              new Container(height: 5.0),
              new Text(planet.location,
                  style: Style.commonTextStyle
    
              ),
              new Container(
                  margin: new EdgeInsets.symmetric(vertical: 8.0),
                  height: 2.0,
                  width: 18.0,
                  color: new Color(0xff00c6ff)
              ),
              new Row(
                children: <Widget>[
                  new Expanded(
                      child: _planetValue(
                          value: planet.distance,
                          image: 'assets/img/ic_distance.png')
    
                  ),
                  new Expanded(
                      child: _planetValue(
                          value: planet.gravity,
                          image: 'assets/img/ic_gravity.png')
                  )
                ],
              )
            ],
          ),
        );
    
        final planetCard = new Container(
          child: planetCardContent,
          height: 124.0,
          margin: EdgeInsets.only(
            left: 46.0,
          ),
          decoration: new BoxDecoration(
            color: new Color(0xFF333366),
            shape: BoxShape.rectangle,
            borderRadius: new BorderRadius.circular(8.0),
            boxShadow: <BoxShadow> [
              new BoxShadow(
                color: Colors.black12,
                blurRadius: 10.0,
                offset: new Offset(0.0, 10.0),
              ),
            ],
          ),
        );
    
        return new Container(
          height: 120.0,
          margin: const EdgeInsets.symmetric(
            vertical: 12.0,
            horizontal: 24.0,
          ),
          child: new Stack(
            children: <Widget>[
              planetCard,
              planetThumbnail,
            ],
          ),
        );
      }
    }
    

    This code snippet is from this tutorial: https://github.com/sergiandreplace/flutter_planets_tutorial. Any help would be greatly appreciated!