Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support extended FABs collapsing into a FAB #104393

Open
TahaTesser opened this issue May 23, 2022 · 9 comments
Open

Support extended FABs collapsing into a FAB #104393

TahaTesser opened this issue May 23, 2022 · 9 comments
Labels
f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. team-design Owned by Design Languages team triaged-design Triaged by Design Languages team

Comments

@TahaTesser
Copy link
Member

TahaTesser commented May 23, 2022

Reference: https://m3.material.io/components/floating-action-button/guidelines

part of #91605

l0js5c2i-efab_.scrolling_3P.mp4
@TahaTesser
Copy link
Member Author

cc: @guidezpl

@guidezpl guidezpl changed the title [Material 3] Support Extended FABs can collapse into a FAB Support Material 3 Extended FABs collapsing into a FAB May 23, 2022
@guidezpl
Copy link
Member

Added to the tracking issue.

@TahaTesser TahaTesser added in triage Presently being triaged by the triage team framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. and removed in triage Presently being triaged by the triage team labels May 23, 2022
@HansMuller
Copy link
Contributor

CC @darrenaustin

@guidezpl guidezpl changed the title Support Material 3 Extended FABs collapsing into a FAB Support extended FABs collapsing into a FAB May 24, 2022
@doppio
Copy link

doppio commented May 28, 2022

I wonder if this issue could be a bit broader so the M3 FAB can support a wider variety of transformations. The Material spec states:

The FAB can expand and adapt to any shape, including a surface that is part of the app structure, or a surface that spans the entire screen.

Maybe this should be a separate issue, but it seems like the extended/collapsed transition is just a subset of a broader need for FAB transformations and it might be better to tackle the whole problem all at once.

@peter100u
Copy link

247377707-373d9324-c495-41de-ba9c-6cf70975d783.mp4

Animating the bottom bar while scrolling is also very good, and it is recommended to consider adding this feature as well

@flutter-triage-bot flutter-triage-bot bot added multiteam-retriage-candidate team-design Owned by Design Languages team triaged-design Triaged by Design Languages team labels Jul 8, 2023
@alexanderameye
Copy link

247377707-373d9324-c495-41de-ba9c-6cf70975d783.mp4
Animating the bottom bar while scrolling is also very good, and it is recommended to consider adding this feature as well

Is this a video of an implementation of yours? I tried implementing it myself by switching between FloatingActionButton.extended and FloatingActionButton and an AnimatedContainer to animate the width. However I can't get the icon to stay at the same position relative to the FloatingActionButton container. It looks weird :/

@valentindrolet
Copy link

valentindrolet commented Nov 4, 2023

Here is the widget I use to be close to the material demo

class ExpandableFloatingActionButton extends StatefulWidget {
  final IconData icon;
  final String label;
  final ScrollController scrollController;
  final void Function() onPressed;

  const ExpandableFloatingActionButton(
      {super.key,
      required this.icon,
      required this.label,
      required this.scrollController,
      required this.onPressed});

  @override
  State<ExpandableFloatingActionButton> createState() =>
      _ExpandableFloatingActionButtonState();
}

class _ExpandableFloatingActionButtonState
    extends State<ExpandableFloatingActionButton> {
  bool _extended = true;

  @override
  void initState() {
    super.initState();
    widget.scrollController.addListener(_scrollListener);
  }

  @override
  void dispose() {
    widget.scrollController.removeListener(_scrollListener);
    super.dispose();
  }

  void _scrollListener() {
    bool maxScrollReached = widget.scrollController.position.maxScrollExtent ==
        widget.scrollController.position.pixels;
    bool scrollUp = widget.scrollController.position.userScrollDirection ==
        ScrollDirection.forward;

    setState(() => _extended = maxScrollReached || scrollUp);
  }

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton.extended(
      extendedIconLabelSpacing: _extended ? 10 : 0,
      extendedPadding:
          _extended ? null : const EdgeInsets.symmetric(horizontal: 16),
      onPressed: widget.onPressed,
      icon: Icon(widget.icon),
      label: AnimatedSize(
        duration: const Duration(milliseconds: 200),
        child: _extended ? Text(widget.label) : Container(),
      ),
    );
  }
}
Simulator.Screen.Recording.-.iPhone.15.Pro.Max.-.2023-11-04.at.16.10.05.mp4

@Mae623
Copy link

Mae623 commented Mar 25, 2024

Here is the widget I use to be close to the material demo

class ExpandableFloatingActionButton extends StatefulWidget {
  final IconData icon;
  final String label;
  final ScrollController scrollController;
  final void Function() onPressed;

  const ExpandableFloatingActionButton(
      {super.key,
      required this.icon,
      required this.label,
      required this.scrollController,
      required this.onPressed});

  @override
  State<ExpandableFloatingActionButton> createState() =>
      _ExpandableFloatingActionButtonState();
}

class _ExpandableFloatingActionButtonState
    extends State<ExpandableFloatingActionButton> {
  bool _extended = true;

  @override
  void initState() {
    super.initState();
    widget.scrollController.addListener(_scrollListener);
  }

  @override
  void dispose() {
    widget.scrollController.removeListener(_scrollListener);
    super.dispose();
  }

  void _scrollListener() {
    bool maxScrollReached = widget.scrollController.position.maxScrollExtent ==
        widget.scrollController.position.pixels;
    bool scrollUp = widget.scrollController.position.userScrollDirection ==
        ScrollDirection.forward;

    setState(() => _extended = maxScrollReached || scrollUp);
  }

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton.extended(
      extendedIconLabelSpacing: _extended ? 10 : 0,
      extendedPadding:
          _extended ? null : const EdgeInsets.symmetric(horizontal: 16),
      onPressed: widget.onPressed,
      icon: Icon(widget.icon),
      label: AnimatedSize(
        duration: const Duration(milliseconds: 200),
        child: _extended ? Text(widget.label) : Container(),
      ),
    );
  }
}

Simulator.Screen.Recording.-.iPhone.15.Pro.Max.-.2023-11-04.at.16.10.05.mp4

Your answer works, thank you

@dickermoshe
Copy link

dickermoshe commented Apr 15, 2024

To make this more stable I've added prevPixelPosition that tiny scroll events don't animate it.
To make the text animate from the left, instead of the center, I've added the alignment.
However, it really should animate at the same speed of the scrolling.

class ExpandableFloatingActionButton extends StatefulWidget {
  final IconData icon;
  final String label;
  final ScrollController scrollController;
  final void Function() onPressed;

  const ExpandableFloatingActionButton(
      {super.key, required this.icon,
      required this.label,
      required this.scrollController,
      required this.onPressed});

  @override
  State<ExpandableFloatingActionButton> createState() =>
      _ExpandableFloatingActionButtonState();
}

class _ExpandableFloatingActionButtonState
    extends State<ExpandableFloatingActionButton> {
  bool _extended = true;
  double prevPixelPosition = 0;

  @override
  void initState() {
    super.initState();
    widget.scrollController.addListener(_scrollListener);
  }

  @override
  void dispose() {
    widget.scrollController.removeListener(_scrollListener);
    super.dispose();
  }

  void _scrollListener() {
    if ((prevPixelPosition - widget.scrollController.position.pixels).abs() >
        10) {
      bool maxScrollReached =
          widget.scrollController.position.maxScrollExtent ==
              widget.scrollController.position.pixels;
      bool scrollUp = widget.scrollController.position.userScrollDirection ==
          ScrollDirection.forward;

      setState(() => _extended = maxScrollReached || scrollUp);
    }

    prevPixelPosition = widget.scrollController.position.pixels;
  }

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton.extended(
      extendedIconLabelSpacing: _extended ? 10 : 0,
      extendedPadding:
          _extended ? null : const EdgeInsets.symmetric(horizontal: 16),
      onPressed: widget.onPressed,
      icon: Icon(widget.icon),
      label: AnimatedSize(
        alignment: Alignment.centerLeft,
        duration: const Duration(milliseconds: 200),
        child: _extended ? Text(widget.label) : Container(),
      ),
    );
  }
}

Thanks so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. team-design Owned by Design Languages team triaged-design Triaged by Design Languages team
Projects
No open projects
Status: No status
Development

No branches or pull requests

10 participants