Last active
August 31, 2021 12:44
-
-
Save rydmike/691628e75729aa75d3143740f87e91b3 to your computer and use it in GitHub Desktop.
Flutter Rounded Border Solutions to Tweet: https://twitter.com/RydMike/status/1431999590547476483
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/material.dart'; | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
debugShowCheckedModeBanner: false, | |
title: 'Border Fun', | |
theme: ThemeData(primarySwatch: Colors.blue), | |
home: const HomePage(), | |
); | |
} | |
} | |
class HomePage extends StatelessWidget { | |
const HomePage({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
const double width = 220; | |
const double height = 150; | |
const double borderWidth = 20; | |
const double borderRadius = 40; | |
return Scaffold( | |
appBar: AppBar(title: const Text('Rounded Border Fun')), | |
body: Center( | |
child: ConstrainedBox( | |
// | |
// RydMike: | |
// Constrain the parent width to width of rounded box, this will also | |
// reveal if the solutions stays inside width constraints. | |
// | |
// When possible or obvious how to do it (did not put a lot of effort | |
// in to, further teaks probably possible if needed), the Tweeted | |
// solutions were slightly revised so they stay within parent | |
// constraints and use a corner radius with the actual provided | |
// radius and so that the border width remains uniform in the corners. | |
// | |
constraints: const BoxConstraints(maxWidth: width), | |
child: ListView( | |
children: const <Widget>[ | |
SizedBox(height: 20), | |
Divider(), | |
Text('1) Alois Version'), | |
AloisVersion( | |
width: width, | |
height: height, | |
borderWidth: borderWidth, | |
borderRadius: borderRadius, | |
child: Center(child: Text('@aloisdeniel')), | |
), | |
Divider(), | |
Text('2) Chinmay Version'), | |
ChinmayVersion( | |
width: width, | |
height: height, | |
borderWidth: borderWidth, | |
borderRadius: borderRadius, | |
child: Center(child: Text('@ChinuKabi')), | |
), | |
Divider(), | |
Text('3) Mike Version'), | |
MikeVersion( | |
width: width, | |
height: height, | |
borderWidth: borderWidth, | |
borderRadius: borderRadius, | |
child: Center(child: Text('@RydMike')), | |
), | |
Divider(), | |
Text('4) Pankaj Version'), | |
PankajVersion( | |
width: width, | |
height: height, | |
borderWidth: borderWidth, | |
borderRadius: borderRadius, | |
child: Center(child: Text('@koiralapankaj7')), | |
), | |
Divider(), | |
Text('5) Paras Version'), | |
ParasVersion( | |
width: width, | |
height: height, | |
borderWidth: borderWidth, | |
borderRadius: borderRadius, | |
child: Center(child: Text('@theretroportal')), | |
), | |
Divider(), | |
Text('6) Romain Version'), | |
RomainVersion( | |
width: width, | |
height: height, | |
borderWidth: borderWidth, | |
borderRadius: borderRadius, | |
child: Center(child: Text('@lets4r')), | |
), | |
Divider(), | |
Text('7) Vladimir Version'), | |
SizedBox(height: 20), | |
VladimirVersion( | |
width: width, | |
height: height, | |
borderWidth: borderWidth, | |
borderRadius: borderRadius, | |
child: Center(child: Text('@romashkin_dev')), | |
), | |
SizedBox(height: 20), | |
Divider(), | |
], | |
), | |
), | |
), | |
); | |
} | |
} | |
// ***************************************************************************** | |
// Alois version. Twitter @aloisdeniel | |
// https://twitter.com/aloisdeniel/status/1432210349546221568?s=20 | |
// | |
// Same concept, but a bit different from Mike version tweeted before this. | |
class AloisVersion extends StatelessWidget { | |
const AloisVersion({ | |
Key? key, | |
required this.width, | |
required this.height, | |
required this.borderWidth, | |
required this.borderRadius, | |
this.child, | |
}) : super(key: key); | |
final double width; | |
final double height; | |
final double borderWidth; | |
final double borderRadius; | |
final Widget? child; | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
decoration: BoxDecoration( | |
color: Colors.black, | |
borderRadius: BorderRadius.only( | |
topRight: Radius.circular(borderRadius), | |
bottomRight: Radius.circular(borderRadius), | |
), | |
), | |
padding: EdgeInsets.only( | |
top: borderWidth, | |
right: borderWidth, | |
bottom: borderWidth, | |
), | |
child: Container( | |
width: width - borderWidth, | |
height: height - 2 * borderWidth, | |
decoration: BoxDecoration( | |
color: Colors.blue[100], | |
borderRadius: BorderRadius.only( | |
topRight: Radius.circular(borderRadius - borderWidth), | |
bottomRight: Radius.circular(borderRadius - borderWidth), | |
), | |
), | |
child: child, | |
), | |
); | |
} | |
} | |
// ***************************************************************************** | |
// Chinmay version. Twitter @ChinuKabi | |
// https://twitter.com/ChinuKabi/status/1432032337601052677?s=20 | |
class ChinmayVersion extends StatelessWidget { | |
const ChinmayVersion({ | |
Key? key, | |
required this.width, | |
required this.height, | |
required this.borderWidth, | |
required this.borderRadius, | |
this.child, | |
}) : super(key: key); | |
final double width; | |
final double height; | |
final double borderWidth; | |
final double borderRadius; | |
final Widget? child; | |
@override | |
Widget build(BuildContext context) { | |
return ClipRRect( | |
borderRadius: BorderRadius.only( | |
topRight: Radius.circular(borderRadius), | |
bottomRight: Radius.circular(borderRadius), | |
), | |
child: Container( | |
width: width, | |
height: height, | |
color: Colors.black, | |
child: Padding( | |
padding: EdgeInsets.only( | |
top: borderWidth, | |
bottom: borderWidth, | |
right: borderWidth, | |
), | |
child: ClipRRect( | |
borderRadius: BorderRadius.only( | |
topRight: Radius.circular(borderRadius - borderWidth), | |
bottomRight: Radius.circular(borderRadius - borderWidth), | |
), | |
child: Container( | |
color: Colors.blue[100], | |
child: child, | |
), | |
), | |
), | |
), | |
); | |
} | |
} | |
// ***************************************************************************** | |
// Mike version. Twitter @RydMike | |
// https://twitter.com/RydMike/status/1432028503843745802?s=20 | |
class MikeVersion extends StatelessWidget { | |
const MikeVersion({ | |
Key? key, | |
required this.width, | |
required this.height, | |
required this.borderWidth, | |
required this.borderRadius, | |
this.child, | |
}) : super(key: key); | |
final double width; | |
final double height; | |
final double borderWidth; | |
final double borderRadius; | |
final Widget? child; | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
width: width, | |
height: height, | |
decoration: BoxDecoration( | |
color: Colors.black, | |
borderRadius: BorderRadius.only( | |
topRight: Radius.circular(borderRadius), | |
bottomRight: Radius.circular(borderRadius), | |
), | |
), | |
child: Container( | |
margin: EdgeInsets.only( | |
top: borderWidth, | |
right: borderWidth, | |
bottom: borderWidth, | |
), | |
decoration: BoxDecoration( | |
color: Colors.blue[100], | |
borderRadius: BorderRadius.only( | |
topRight: Radius.circular(borderRadius - borderWidth), | |
bottomRight: Radius.circular(borderRadius - borderWidth), | |
), | |
), | |
child: child, | |
), | |
); | |
} | |
} | |
// ***************************************************************************** | |
// Pankaj version. Twitter @koiralapankaj7 | |
// https://twitter.com/koiralapankaj7/status/1432354767347470342?s=20 | |
// | |
// This is a variation of Mike version, tweeted before this version. | |
class PankajVersion extends StatelessWidget { | |
const PankajVersion({ | |
Key? key, | |
required this.width, | |
required this.height, | |
required this.borderRadius, | |
required this.borderWidth, | |
this.borderColor, | |
this.backgroundColor, | |
this.child, | |
}) : super(key: key); | |
final double width; | |
final double height; | |
final double borderRadius; | |
final double borderWidth; | |
final Color? borderColor; | |
final Color? backgroundColor; | |
final Widget? child; | |
Widget _container({ | |
required Color color, | |
required double radius, | |
double borderWidth = 0.0, | |
Widget? child, | |
}) { | |
return Container( | |
width: width, | |
height: height, | |
margin: EdgeInsets.only( | |
top: borderWidth, | |
right: borderWidth, | |
bottom: borderWidth, | |
), | |
decoration: BoxDecoration( | |
color: color, | |
borderRadius: BorderRadius.only( | |
topRight: Radius.circular(radius), | |
bottomRight: Radius.circular(radius), | |
), | |
), | |
child: child, | |
); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return _container( | |
color: borderColor ?? Colors.black, | |
radius: borderRadius, | |
child: _container( | |
borderWidth: borderWidth, | |
radius: borderRadius - borderWidth, | |
color: backgroundColor ?? Colors.blue[100]!, | |
child: child, | |
), | |
); | |
} | |
} | |
// ***************************************************************************** | |
// Paras version. Twitter: @theretroportal | |
// https://twitter.com/theretroportal/status/1432025705836003331?s=20 | |
class ParasVersion extends StatelessWidget { | |
const ParasVersion({ | |
Key? key, | |
required this.width, | |
required this.height, | |
required this.borderWidth, | |
required this.borderRadius, | |
this.child, | |
}) : super(key: key); | |
final double width; | |
final double height; | |
final double borderWidth; | |
final double borderRadius; | |
final Widget? child; | |
@override | |
Widget build(BuildContext context) { | |
return SizedBox( | |
width: width, | |
height: height, | |
child: Stack( | |
children: [ | |
Positioned( | |
left: -borderWidth, // Border Width | |
top: 0, | |
child: Container( | |
width: width + borderWidth, // + border width | |
height: height, | |
decoration: BoxDecoration( | |
color: Colors.blue[100], | |
border: Border.all( | |
color: Colors.black, | |
width: borderWidth, | |
), | |
borderRadius: BorderRadius.only( | |
topRight: Radius.circular(borderRadius), | |
bottomRight: Radius.circular(borderRadius), | |
), | |
), | |
child: child, | |
), | |
), | |
], | |
), | |
); | |
} | |
} | |
// ***************************************************************************** | |
// Romain version. Twitter: @lets4r | |
// https://twitter.com/lets4r/status/1432051560645857289?s=20 | |
class RomainVersion extends StatelessWidget { | |
const RomainVersion({ | |
Key? key, | |
required this.width, | |
required this.height, | |
required this.borderWidth, | |
required this.borderRadius, | |
this.child, | |
}) : super(key: key); | |
final double width; | |
final double height; | |
final double borderWidth; | |
final double borderRadius; | |
final Widget? child; | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
width: width, | |
height: height, | |
decoration: ShapeDecoration( | |
color: Colors.blue[100], | |
shape: ThreeRoundSidesShape( | |
side: BorderSide(color: Colors.black, width: borderWidth), | |
borderRadius: BorderRadius.only( | |
topRight: Radius.circular(borderRadius), | |
bottomRight: Radius.circular(borderRadius), | |
), | |
), | |
), | |
child: child, | |
); | |
} | |
} | |
class ThreeRoundSidesShape extends OutlinedBorder { | |
const ThreeRoundSidesShape({ | |
BorderSide side = BorderSide.none, | |
this.borderRadius = BorderRadius.zero, | |
}) : super(side: side); | |
/// The radii for each corner. | |
final BorderRadiusGeometry borderRadius; | |
@override | |
EdgeInsetsGeometry get dimensions { | |
return EdgeInsets.all(side.width); | |
} | |
@override | |
ShapeBorder scale(double t) { | |
return ThreeRoundSidesShape( | |
side: side.scale(t), | |
borderRadius: borderRadius * t, | |
); | |
} | |
@override | |
ShapeBorder? lerpFrom(ShapeBorder? a, double t) { | |
// Not implemented. | |
return super.lerpFrom(a, t); | |
} | |
@override | |
ShapeBorder? lerpTo(ShapeBorder? b, double t) { | |
// Not implemented. | |
return super.lerpTo(b, t); | |
} | |
@override | |
ThreeRoundSidesShape copyWith( | |
{BorderSide? side, BorderRadius? borderRadius}) { | |
return ThreeRoundSidesShape( | |
side: side ?? this.side, | |
borderRadius: borderRadius ?? this.borderRadius, | |
); | |
} | |
@override | |
Path getInnerPath(Rect rect, {TextDirection? textDirection}) { | |
final double width = side.width; | |
final BorderRadius radius = borderRadius.resolve(textDirection); | |
final RRect outer = radius.toRRect(rect); | |
final RRect inner = RRect.fromLTRBAndCorners( | |
outer.left, | |
outer.top + width, | |
outer.right - width, | |
outer.bottom - width, | |
topLeft: radius.topLeft.deflate(width), | |
topRight: radius.topRight.deflate(width), | |
bottomRight: radius.bottomRight.deflate(width), | |
bottomLeft: radius.bottomLeft.deflate(width), | |
); | |
return Path()..addRRect(inner); | |
} | |
@override | |
Path getOuterPath(Rect rect, {TextDirection? textDirection}) { | |
return Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect)); | |
} | |
@override | |
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) { | |
switch (side.style) { | |
case BorderStyle.none: | |
break; | |
case BorderStyle.solid: | |
final double width = side.width; | |
if (width == 0.0) { | |
canvas.drawRRect(borderRadius.resolve(textDirection).toRRect(rect), | |
side.toPaint()); | |
} else { | |
final BorderRadius radius = borderRadius.resolve(textDirection); | |
final RRect outer = radius.toRRect(rect); | |
final RRect inner = RRect.fromLTRBAndCorners( | |
outer.left, | |
outer.top + width, | |
outer.right - width, | |
outer.bottom - width, | |
topLeft: radius.topLeft.deflate(width), | |
topRight: radius.topRight.deflate(width), | |
bottomRight: radius.bottomRight.deflate(width), | |
bottomLeft: radius.bottomLeft.deflate(width), | |
); | |
final Paint paint = Paint()..color = side.color; | |
canvas.drawDRRect(outer, inner, paint); | |
} | |
} | |
} | |
@override | |
bool operator ==(Object other) { | |
if (other.runtimeType != runtimeType) return false; | |
return other is ThreeRoundSidesShape && | |
other.side == side && | |
other.borderRadius == borderRadius; | |
} | |
@override | |
int get hashCode => hashValues(side, borderRadius); | |
@override | |
String toString() { | |
return '${objectRuntimeType(this, 'MyRoundedRectangleBorder')}($side, $borderRadius)'; | |
} | |
} | |
extension on Radius { | |
Radius deflate(double delta) { | |
return Radius.elliptical(x - delta, y - delta); | |
} | |
} | |
// ***************************************************************************** | |
// Vladimir version. Twitter: @romashkin_dev | |
// https://twitter.com/romashkin_dev/status/1432027676055908353?s=20 | |
class VladimirVersion extends StatelessWidget { | |
const VladimirVersion({ | |
Key? key, | |
required this.width, | |
required this.height, | |
required this.borderWidth, | |
required this.borderRadius, | |
this.child, | |
}) : super(key: key); | |
final double width; | |
final double height; | |
final double borderWidth; | |
final double borderRadius; | |
final Widget? child; | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
width: width - borderWidth, | |
height: height - 2 * borderWidth, | |
decoration: BoxDecoration( | |
color: Colors.blue[100], | |
boxShadow: [ | |
BoxShadow( | |
color: Colors.black, | |
offset: Offset(borderWidth, 0), | |
blurRadius: 0, | |
spreadRadius: 0, | |
), | |
BoxShadow( | |
color: Colors.black, | |
offset: Offset(0, borderWidth), | |
blurRadius: 0, | |
spreadRadius: 0, | |
), | |
BoxShadow( | |
color: Colors.black, | |
offset: Offset(0, -borderWidth), | |
blurRadius: 0, | |
spreadRadius: 0, | |
), | |
BoxShadow( | |
color: Colors.black, | |
offset: Offset(borderWidth, -borderWidth), | |
blurRadius: 0, | |
spreadRadius: 0, | |
), | |
BoxShadow( | |
color: Colors.black, | |
offset: Offset(borderWidth, borderWidth), | |
blurRadius: 0, | |
spreadRadius: 0, | |
), | |
], | |
borderRadius: BorderRadius.only( | |
topRight: Radius.circular(borderRadius), | |
bottomRight: Radius.circular(borderRadius), | |
), | |
), | |
child: child, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Responses to Flutter Rounded Border Challenge
In this Tweet: https://twitter.com/RydMike/status/1431999590547476483 a question to draw the shape shown in Flutter was raised.
This public Gist contains a collection of received solution.
They all have their own pros and cons. Which one do you like best?