3

I'm working on a Flutter app and trying to replicate a Figma design for a container with an angular (conic) gradient border. Despite multiple attempts, I can't achieve the exact look specified in the Figma design. Below are the details of the Figma specs, my current code, and the issue I'm facing.

Figma Specifications Container Dimensions:

Width: 176px Height: 65px Border Radius: 16px Gradient Border: Border Width: 1px Border Style: Solid Border Image Source: conic-gradient(from 90deg at 50% 50%, rgba(255, 255, 255, 0) -37.26deg, #FFFFFF 50.99deg, rgba(255, 255, 255, 0) 143.54deg, #FFFFFF 235.25deg, rgba(255, 255, 255, 0) 322.74deg, #FFFFFF 410.99deg) Angle: 0 deg Opacity: 1 Backdrop Filter: Blur(5px) 

Figma Design Figma design

My Current Code Below is the Flutter code I tried to replicate the design:

import 'dart:math' as math; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:gradient_borders/gradient_borders.dart'; import 'package:luma_active/core/responsive.dart'; class FrostedConicBox extends StatelessWidget { final String imagePath; final String label; const FrostedConicBox({ super.key, required this.imagePath, required this.label, }); @override Widget build(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(16), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), child: Container( width: ResponsiveHelper.wp(context, 176), height: ResponsiveHelper.hp(context, 65), padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( color: Colors.white.withOpacity(0.1), borderRadius: BorderRadius.circular(16), border: GradientBoxBorder( width: 1, gradient: SweepGradient( center: Alignment.center, startAngle: 0, endAngle: 2 * math.pi, tileMode: TileMode.clamp, colors: [ Colors.white.withOpacity(1.0), // 1st stop: 14% #FFFFFF 100% Colors.white.withOpacity(0.0), // 2nd stop: 40% #FFFFFF 0% Colors.white.withOpacity(1.0), // 3rd stop: 65% #FFFFFF 100% Colors.white.withOpacity(0.0), // 4th stop: 90% #FFFFFF 0% Colors.white.withOpacity(1.0), // wrap to start for smoothness ], stops: const [ 0.14, // 14% 0.40, // 40% 0.65, // 65% 0.90, // 90% 1.0, // wrap ], ), ), ), child: Row( children: [ ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.asset( imagePath, width: 45, height: 45, fit: BoxFit.cover, ), ), const SizedBox(width: 10), Expanded( child: Text( label, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w600, fontSize: 15, ), overflow: TextOverflow.ellipsis, ), ), ], ), ), ), ); } } 

Flutter's Border or BoxDecoration doesn't seem to support conic gradients natively. I tried using LinearGradient and RadialGradient, but they don't match the angular gradient effect from Figma.

0

2 Answers 2

0

You can make you own border implementation, and in this case, it's not even that hard. The only thing you need to keep in mind is that the canvas can overpaint any part of the view (window).

In this example I used linear instead of a conic gradient because I think it replicates better the image and because there is no conic gradient (yet), if you want a conic gradient you will need to use a shader or a image shader with the gradient.

Most of RoundedRectangleGradientBorder is boilerplate, the only required override is paint().

import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; // objectRuntimeType() / listEquals() void main() => runApp( const MaterialApp(home: App()), // ); class App extends StatefulWidget { const App({super.key}); @override AppState createState() => AppState(); } class AppState extends State<App> { final base_color = Color(0xFF27425E); final mix_color = Color(0xFFA7C5E2); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Color(0xFF3F3F3F), body: Center( child: Container( decoration: ShapeDecoration( color: base_color, shape: RoundedRectangleGradientBorder( side: BorderSide(width: 3.0), borderRadius: BorderRadius.all(Radius.circular(30)), gradient: LinearGradient( begin: Alignment(-0.2, -1.0), end: Alignment(0.2, 1.0), colors: <Color>[ Color.lerp(base_color, mix_color, 0.5)!, Color.lerp(base_color, mix_color, 0.1)!, Color.lerp(base_color, mix_color, 0.1)!, Color.lerp(base_color, mix_color, 0.5)!, ], ), ), ), child: Container(width: 300, height: 150), ), ), ); } } // Overrides the border paint color with a [Gradient] class RoundedRectangleGradientBorder extends RoundedRectangleBorder { const RoundedRectangleGradientBorder({ super.side, super.borderRadius, required this.gradient, }); final Gradient gradient; @override ShapeBorder scale(double t) { return RoundedRectangleGradientBorder( side: side.scale(t), borderRadius: borderRadius * t, gradient: gradient.scale(t), ); } @override ShapeBorder? lerpFrom(ShapeBorder? a, double t) { if (a is RoundedRectangleGradientBorder) { return RoundedRectangleGradientBorder( side: BorderSide.lerp(a.side, side, t), borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!, gradient: Gradient.lerp(a.gradient, null, t)!, ); } return super.lerpFrom(a, t); } @override ShapeBorder? lerpTo(ShapeBorder? b, double t) { if (b is RoundedRectangleGradientBorder) { return RoundedRectangleGradientBorder( side: BorderSide.lerp(side, b.side, t), borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!, gradient: Gradient.lerp(null, b.gradient, t)!, ); } return super.lerpTo(b, t); } @override RoundedRectangleGradientBorder copyWith({ BorderSide? side, BorderRadiusGeometry? borderRadius, Gradient? gradient, }) { return RoundedRectangleGradientBorder( side: side ?? this.side, borderRadius: borderRadius ?? this.borderRadius, gradient: gradient ?? this.gradient, ); } @override void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) { final border_paint = Paint(); border_paint.shader = gradient.createShader(rect); switch (side.style) { case BorderStyle.none: break; case BorderStyle.solid: final border_rrect = borderRadius.resolve(textDirection).toRRect(rect); if (side.width == 0.0) { canvas.drawRRect(border_rrect, border_paint); } else { final inner_rrect = border_rrect.deflate(side.strokeInset); final outer_rrect = border_rrect.inflate(side.strokeOutset); canvas.drawDRRect(outer_rrect, inner_rrect, border_paint); } } } @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } return other is RoundedRectangleGradientBorder && other.side == side && other.borderRadius == borderRadius && other.gradient == gradient; } @override int get hashCode => Object.hash(side, borderRadius, gradient); @override String toString() { return '${objectRuntimeType(this, 'RoundedRectangleGradientBorder')} ($side, $borderRadius, $gradient)'; } } 
Sign up to request clarification or add additional context in comments.

Comments

-2

You should paint border with gradient on canvas. I think it will be possible to do it without canvas.

You could do something like this:

class GradientBorderContainer extends StatelessWidget { GradientBorderContainer({ required Gradient gradient, required this.child, this.backgroundDecoration, double strokeWidth = 1.0, BorderRadius borderRadius = AppTheme.borderRadiusCircular16, super.key, }) : painter = CustomGradient( gradient: gradient, strokeWidth: strokeWidth, borderRadius: borderRadius, ); final Decoration? backgroundDecoration; final CustomGradient painter; final Widget child; @override Widget build(BuildContext context) { Widget widget = RepaintBoundary( child: CustomPaint(painter: painter, child: child), ); if (backgroundDecoration != null) { widget = DecoratedBox(decoration: backgroundDecoration!, child: widget); } return widget; } } 
class CustomGradient extends CustomPainter { CustomGradient({ required this.borderRadius, required this.gradient, required this.strokeWidth, }) : p = Paint() ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth; final Gradient gradient; final double strokeWidth; final Paint p; final BorderRadius borderRadius; @override void paint(Canvas canvas, Size size) { final rect = Rect.fromLTRB( strokeWidth / 2, strokeWidth / 2, size.width - strokeWidth / 2, size.height - strokeWidth / 2, ); final rRect = RRect.fromRectAndCorners( rect, topLeft: borderRadius.topLeft, topRight: borderRadius.topRight, bottomLeft: borderRadius.bottomLeft, bottomRight: borderRadius.bottomRight, ); p.shader = gradient.createShader(rect); canvas.drawRRect(rRect, p); } @override bool shouldRepaint(covariant CustomGradient oldDelegate) => oldDelegate.gradient != gradient || oldDelegate.borderRadius != borderRadius || oldDelegate.strokeWidth != strokeWidth; } 

then in widget somewhere you need it:

GradientBorderContainer( gradient: AppGradient.firstGradient, borderRadius: AppTheme.borderRadiusCircular20, strokeWidth: 1.4, backgroundDecoration: const BoxDecoration( borderRadius: AppTheme.borderRadiusCircular20, gradient: AppGradient.secondGradient, ), child: Padding( padding: allPadding20, ...... .... static const firstGradient = LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomCenter, colors: [ ColorPalette.white, ColorPalette.whiteWithOpacity01, ColorPalette.whiteWithOpacity01, ColorPalette.whiteWithOpacity40, ], stops: [0.0212, 0.66, 0.7, 0.9302], ); static const secondGradient = LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ ColorPalette.granitWithOpacity30, ColorPalette.granitWithOpacity30, ColorPalette.ebony, ], stops: [0, 0.4, 1], ); 

3 Comments

How does this address the issue? Please edit your answer and explain the changes you've made to solve the problem. Code-only answers are not good answers.
Your opinion "Code-only answers are not good answers" is very debatable. If the code is readable, not complicated, solves the problem, then as a rule it does not require explanations. If you did not manage to understand the connection between the code and the problem, you could ask a clarifying question.
Please keep in mind, you are not only writing answers for OP and yourself, but also for future, maybe less experienced developers.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.