I'd like to display Instagram-like stories on my Flutter app and want to show the number of stories a user has uploaded by using borders around the user's avatar.
Say a user has uploaded 3 stories, i'll show 3 rounded border lines around the avatar image separated by equal number of spaces & if a user uploads 80 stories, i'll show 80 tiny round border lines separated by equal number of spaces.
I tried using plugins from pub.dev for this, like
just to name a few, but i can't seem to get an accurate count of spaces & dashes to fulfill the requirements above.
Below is an example:
FDottedLine(
color: Colors.black,
strokeWidth: 2.0,
dottedLength: 30,
space: 4,
corner: FDottedLineCorner.all(100.0),
child: Padding(
padding: const EdgeInsets.all(3.0),
child: SizedBox.square(
dimension: 0.055.h,
child: ClipRRect(
borderRadius: BorderRadius.circular(100),
child: ImageBox.network(
photo: user.photo.getOrEmpty,
elevation: 2,
replacement: Image.asset(AppAssets.defaultUserImage(user.gender.getOrNull)),
borderRadius: BorderRadius.circular(100),
),
),
),
),
),
No matter how i tweak the dottedLength
& space
params, i can't get equal number of spaces nor dashes.
I also tried using Path()
, CustomPainter()
but i barely know much about how to use it.
Any idea how i can achieve this using either CustomPainter()
or a plugin?
Thanks for posting all your tries as it made me jump to CustomPaint
directly to try
the approach that (could) work (but not tested well) is drawArc
the logic is simply to draw an arc based on the number of stories and start the next arc after leaving some space
the below code is looping for the number of stories to draw every story arc and start the next story arc (if stories > 1) after adding some value (the space between the stories) to the start of the next arc location (on the circle).
for(int i =0;i<numberOfStories;i++){
canvas.drawArc(
rect,
inRads(startOfArcInDegree),
inRads(arcLength),
false,
Paint()
..color = i==0||i==1?Colors.grey:Colors.teal
..strokeWidth =14.0
..style = PaintingStyle.stroke
);
startOfArcInDegree += arcLength + spaceLength;
}
full code with detailed explanation:
import 'dart:math';
import 'package:flutter/material.dart';
class DottedBorder extends CustomPainter {
//number of stories
final int numberOfStories;
//length of the space arc (empty one)
final int spaceLength;
//start of the arc painting in degree(0-360)
double startOfArcInDegree = 0;
DottedBorder({required this.numberOfStories, this.spaceLength = 10});
//drawArc deals with rads, easier for me to use degrees
//so this takes a degree and change it to rad
double inRads(double degree){
return (degree * pi)/180;
}
@override
bool shouldRepaint(DottedBorder oldDelegate) {
return true;
}
@override
void paint(Canvas canvas, Size size) {
//circle angle is 360, remove all space arcs between the main story arc (the number of spaces(stories) times the space length
//then subtract the number from 360 to get ALL arcs length
//then divide the ALL arcs length by number of Arc (number of stories) to get the exact length of one arc
double arcLength = (360 - (numberOfStories * spaceLength))/numberOfStories;
//be careful here when arc is a negative number
//that happens when the number of spaces is more than 360
//feel free to use what logic you want to take care of that
//note that numberOfStories should be limited too here
if(arcLength<=0){
arcLength = 360/spaceLength -1;
}
Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
//looping for number of stories to draw every story arc
for(int i =0;i<numberOfStories;i++){
//printing the arc
canvas.drawArc(
rect,
inRads(startOfArcInDegree),
//be careful here is: "double sweepAngle", not "end"
inRads(arcLength),
false,
Paint()
//here you can compare your SEEN story index with the arc index to make it grey
..color = i==0||i==1?Colors.grey:Colors.teal
..strokeWidth =14.0
..style = PaintingStyle.stroke
);
//the logic of spaces between the arcs is to start the next arc after jumping the length of space
startOfArcInDegree += arcLength + spaceLength;
}
}
}
class DottedBorderExample extends StatelessWidget {
const DottedBorderExample({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Arcs etc')),
body:Center(
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 300,height: 300,
child: CustomPaint(
painter: DottedBorder(numberOfStories: 13,spaceLength:4 ),
),),
Container(child:const Center(child: Text("Some Image",style: TextStyle(fontSize: 18,color: Colors.black),)),width: 270,height: 270,decoration: const BoxDecoration(color: Colors.purple,shape: BoxShape.circle),)
],
),
),
);
}
}
void main() {
runApp(
const MaterialApp(
home: DottedBorderExample(),
),
);
}