Skip to content

Instantly share code, notes, and snippets.

@rohan20
Created December 27, 2021 09:26
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save rohan20/d12a709a27511304c853334dd636ba63 to your computer and use it in GitHub Desktop.
Save rohan20/d12a709a27511304c853334dd636ba63 to your computer and use it in GitHub Desktop.
Colored rounded border on some sides of a Flutter widget
/// Rounded border on some sides of a widget is tricky to achieve since Flutter's [BorderRadius] and [Border] don't
/// work together when the border is "not" on all sides.
///
/// The initial logic was found here: https://stackoverflow.com/a/61613471/5066615.
///
/// The way it's done here is to wrap the [child] in 2 [Container] widgets:
/// The outer [Container] has its background color set to [borderColor] i.e. what we want the border color to be.
/// The inner [Container] has its background color set to the background color of the widget behind the [child] so
/// that it appears to just have a border of [borderColor].
/// The inner [Container] also has a margin that is the same size as the [borderWidth].
class RoundedBorderOnSomeSidesWidget extends StatelessWidget {
/// Color of the content behind this widget
final Color contentBackgroundColor;
final Color borderColor;
final Widget child;
final double borderRadius;
final double borderWidth;
/// The sides where we want the rounded border to be
final bool topLeft;
final bool topRight;
final bool bottomLeft;
final bool bottomRight;
const RoundedBorderOnSomeSidesWidget({
required this.borderColor,
required this.contentBackgroundColor,
required this.child,
required this.borderRadius,
required this.borderWidth,
this.topLeft = false,
this.topRight = false,
this.bottomLeft = false,
this.bottomRight = false,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: borderColor,
borderRadius: BorderRadius.only(
topLeft: topLeft ? Radius.circular(borderRadius) : Radius.zero,
topRight: topRight ? Radius.circular(borderRadius) : Radius.zero,
bottomLeft: bottomLeft ? Radius.circular(borderRadius) : Radius.zero,
bottomRight: bottomRight ? Radius.circular(borderRadius) : Radius.zero,
),
),
child: Container(
margin: EdgeInsets.only(
top: topLeft || topRight ? borderWidth : 0,
left: topLeft || bottomLeft ? borderWidth : 0,
bottom: bottomLeft || bottomRight ? borderWidth : 0,
right: topRight || bottomRight ? borderWidth : 0,
),
decoration: BoxDecoration(
color: contentBackgroundColor,
borderRadius: BorderRadius.only(
topLeft: topLeft ? Radius.circular(borderRadius - borderWidth) : Radius.zero,
topRight: topRight ? Radius.circular(borderRadius - borderWidth) : Radius.zero,
bottomLeft: bottomLeft ? Radius.circular(borderRadius - borderWidth) : Radius.zero,
bottomRight: bottomRight ? Radius.circular(borderRadius - borderWidth) : Radius.zero,
),
),
child: child,
),
);
}
}
@rohan20
Copy link
Author

rohan20 commented Dec 27, 2021

Example:

The Reset Changes and Apply Changes buttons use this widget internally:

image

Here's the 1 rounded corner version where I added extra margins so that you can clearly see the borders:

image

2 rounded corners variation:

image

3 rounded corners variation:

image

4 rounded corners variation:

You don't really need this widget for this case. Flutter's BorderRadius + Border should work perfectly here.

image

Since I had two similar buttons at the edges of the page as you see in the screenshot, I created _EdgeOfThePageButton to extract some more common logic but you can choose to skip it entirely if you like:

const double _roundedButtonTextPadding = 12;
const double _roundedButtonBorderRadius = 8;
const double _borderWidth = 1;

class _EdgeOfThePageButton extends StatelessWidget {
  final String text;
  final VoidCallback? onTap;

  /// The sides where we want the rounded border to be
  final bool topLeft;
  final bool topRight;
  final bool bottomLeft;
  final bool bottomRight;

  const _EdgeOfThePageButton({
    required this.text,
    this.onTap,
    this.topLeft = false,
    this.topRight = false,
    this.bottomLeft = false,
    this.bottomRight = false,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      behavior: HitTestBehavior.opaque,
      onTap: onTap,
      child: RoundedBorderOnSomeSidesWidget(
        borderColor: Colors.white,
        contentBackgroundColor: Colors.teal.shade800,
        borderWidth: _borderWidth,
        borderRadius: _roundedButtonBorderRadius,
        topLeft: topLeft,
        topRight: topRight,
        bottomLeft: bottomLeft,
        bottomRight: bottomRight,
        child: Padding(
          padding: const EdgeInsets.all(_roundedButtonTextPadding),
          child: Text(text, style: const TextStyle(color: Colors.white, fontSize: 12), textAlign: TextAlign.center),
        ),
      ),
    );
  }
}
class _ResetPickTeamChangesButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return _EdgeOfThePageButton(
      text: "Reset\nChanges",
      onTap: () => print("Reset Changes tapped"),
      bottomRight: true,
    );
  }
}
class _ApplyPickTeamChangesButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return _EdgeOfThePageButton(
      text: "Apply\nChanges",
      onTap: () => print("Apply Changes tapped"),
      bottomLeft: true,
    );
  }
}

Here's a DartPad demo version that you can play around with: https://dartpad.dev/?id=b95177ad365c4c0e2f48625f27996d52

@OFD16
Copy link

OFD16 commented May 22, 2023

Excellent work 👯

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