Skip to content

Support for MaterialTapTargetSize within ToggleButtons - #93259

Merged
fluttergithubbot merged 8 commits into
flutter:masterfrom
rami-a:toggle_buttons_tap_target
Nov 19, 2021
Merged

fluttergithubbot merged 8 commits into
flutter:masterfrom
rami-a:toggle_buttons_tap_target

Conversation

@rami-a

@rami-a rami-a commented Nov 8, 2021

Copy link
Copy Markdown
Contributor

This change adds a MaterialTapTargetSize param to the ToggleButtons widget (and also hooks into the ThemeData.materialTapTargetSize property) so that ToggleButtons can benefit from the same behavior as regular buttons (standard tap target size while being visibly smaller on screen).

Fixes #93249

Screen.Recording.2021-11-08.at.11.56.27.AM.mov

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@flutter-dashboard flutter-dashboard Bot added p: material_ui material_ui package in flutter/packages framework flutter/packages/flutter repository. See also f: labels. labels Nov 8, 2021
@google-cla google-cla Bot added the cla: yes label Nov 8, 2021
@rami-a
rami-a requested a review from HansMuller November 8, 2021 16:59
switch (resolvedTapTargetSize) {
case MaterialTapTargetSize.padded:
return ConstrainedBox(
constraints: const BoxConstraints(),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was necessary for some reason. @HansMuller thoughts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. The implication is that the _InputPadding class is not respecting its incoming constraints, so adding this class forces that to happen.

Comment thread packages/flutter/lib/src/material/toggle_buttons.dart
switch (resolvedTapTargetSize) {
case MaterialTapTargetSize.padded:
return ConstrainedBox(
constraints: const BoxConstraints(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. The implication is that the _InputPadding class is not respecting its incoming constraints, so adding this class forces that to happen.

@HansMuller

Copy link
Copy Markdown
Contributor

I removed the ConstrainedBox noted in #93259 (comment)

      case MaterialTapTargetSize.padded:
        return _InputPadding(
          minSize: const Size(kMinInteractiveDimension, kMinInteractiveDimension),
          direction: direction,
          child: result,
        );

The demo below, which defines the height of the ToggleButtons with its constraints parameter, seems to work as expected.

Working Demo
import 'package:flutter/material.dart';

class Home extends StatefulWidget {
  const Home({ Key? key }) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  final List<bool> isSelected = <bool>[false, false, false];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ToggleButtons(
          constraints: BoxConstraints(minWidth: 48.0, minHeight: 32.0),
          tapTargetSize: MaterialTapTargetSize.padded,
          isSelected: isSelected,
          onPressed: (int index) {
            setState(() {
              isSelected[index] = !isSelected[index];
            });
          },
          children: <Widget>[
            Icon(Icons.ac_unit),
            Icon(Icons.call),
            Icon(Icons.cake),
          ],
        ),
      ),
    );
  }
}

void main() {
  runApp(const MaterialApp(home: Home()));
}

A version of the demo that constrains the height SizedBox to be 32 using a SizedBox above the ToggleButtons does fail, but that's to be expected, since the ToggleButtons widget has to respect to the incoming constraints. This happens with or without the extra BoxConstraints widget.

Failing Demo
import 'package:flutter/material.dart';

class Home extends StatefulWidget {
  const Home({ Key? key }) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  final List<bool> isSelected = <bool>[false, false, false];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SizedBox(
          height: 32,
          child: ToggleButtons(
            tapTargetSize: MaterialTapTargetSize.padded,
            isSelected: isSelected,
            onPressed: (int index) {
              setState(() {
                isSelected[index] = !isSelected[index];
              });
            },
            children: <Widget>[
              Icon(Icons.ac_unit),
              Icon(Icons.call),
              Icon(Icons.cake),
            ],
          ),
        ),
      ),
    );
  }
}

void main() {
  runApp(const MaterialApp(home: Home()));
}

What's the test case that caused you to add the extra BoxConstraints widget?

@rami-a

rami-a commented Nov 18, 2021

Copy link
Copy Markdown
Contributor Author

Hmm when I tried just removing the constrained box, the demo you defined still allowed the buttons to be tapped even when tapping way outside the bounds (see attached video). I'm tapping way above/below the widget and still triggered the buttons.

Screen.Recording.2021-11-18.at.8.48.59.AM.mov

@HansMuller

Copy link
Copy Markdown
Contributor

Sorry, I didn't realize that was the problem we were trying to resolve. Will investigate further.

@HansMuller

Copy link
Copy Markdown
Contributor

The problem with the original version of the code was that _InputPadding.hitTest() was deferring to super.hitTest().

  @override
  bool hitTest(BoxHitTestResult result, { required Offset position }) {
    // The super.hitTest() method also checks hitTestChildren(). We don't
    // want that in this case because we've padded around the children per tapTargetSize.
    if (!size.contains(position))
      return false;

    // Only adjust one axis to ensure the correct button is tapped.
    Offset center;
    if (direction == Axis.horizontal) {
      center = Offset(position.dx, child!.size.height / 2);
    } else {
      center = Offset(child!.size.width / 2, position.dy);
    }
    return result.addWithRawTransform(
      transform: MatrixUtils.forceToPoint(center),
      position: center,
      hitTest: (BoxHitTestResult result, Offset position) {
        assert(position == center);
        return child!.hitTest(result, position: center);
      },
    );
  }

@rami-a

rami-a commented Nov 19, 2021

Copy link
Copy Markdown
Contributor Author

The problem with the original version of the code was that _InputPadding.hitTest() was deferring to super.hitTest().

Ah that worked, thanks!

@rami-a
rami-a requested a review from HansMuller November 19, 2021 14:33

@guidezpl guidezpl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  _    ___ _____ __  __ 
 | |  / __|_   _|  \/  |
 | |_| (_ | | | | |\/| |
 |____\___| |_| |_|  |_|
                        

/// If the [tapTargetSize] is larger than [constraints], the buttons will
/// include a transparent margin that responds to taps.
///
/// Always defaults to [ThemeData.materialTapTargetSize].

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Always defaults to [ThemeData.materialTapTargetSize].
/// Defaults to [ThemeData.materialTapTargetSize].

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


/// A widget to pad the area around a [ToggleButtons]'s children.
///
/// This widget is based on a similar one using in [ButtonStyleButton] but it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's an issue with this sentence. Overall I don't understand this comment. What does "tap target" in quotes mean? Which widget does "size of the widget" refer to?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded the sentence, hopefully thats better.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// This widget is based on a similar one using in [ButtonStyleButton] but it
/// This widget is based on a similar one used in [ButtonStyleButton] but it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, fixed

@HansMuller HansMuller left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a few small tweaks

}) : super(key: key, child: child);

final Size minSize;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra blank line here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed


@override
void updateRenderObject(BuildContext context, covariant _RenderInputPadding renderObject) {
renderObject.minSize = minSize;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about direction?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, added


/// A widget to pad the area around a [ToggleButtons]'s children.
///
/// This widget is based on a similar one using in [ButtonStyleButton] but it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

framework flutter/packages/flutter repository. See also f: labels. p: material_ui material_ui package in flutter/packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ToggleButtons should support MaterialTapTargetSize just like other buttons

4 participants