如何创建周围的圆形头像在Flutter虚线边框

soat7uwm  于 2023-06-07  发布在  Flutter
关注(0)|答案(3)|浏览(590)

我想在我的Flutter应用程序上显示类似Instagram的故事,并希望通过使用用户头像周围的边框来显示用户上传的故事数量。
假设一个用户上传了3个故事,我将在头像图像周围显示3条圆形边框线,由相等数量的空格分隔&如果一个用户上传了80个故事,我将显示80条微小的圆形边框线,由相等数量的空格分隔。
我尝试使用pub.dev的插件来实现这一点,例如

仅举几个例子,但我似乎不能得到一个准确的计数空格和破折号,以满足上述要求。
下面是一个例子:

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),
        ),
      ),
    ),
  ),
),

无论我如何调整dottedLengthspace参数,我都不能得到相等数量的空格和破折号。
我也试过使用Path()CustomPainter(),但我几乎不知道如何使用它。

任何想法,我可以实现这一点使用CustomPainter()或插件?

sbdsn5lh

sbdsn5lh1#

感谢您发布所有尝试,因为它使我直接跳转到CustomPaint尝试
可以工作(但测试不好)方法是drawArc
其逻辑是简单地根据楼层的数量画一个弧,并在留出一些空间后开始下一个弧
下面的代码是循环故事的数量,以绘制每个故事弧,并在添加一些值(故事之间的空间)到下一个弧位置的开始(在圆上)之后开始下一个故事弧(如果故事> 1)。

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;
}

完整的代码和详细的解释:

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(),
    ),
  );
}
lokaqttq

lokaqttq2#

点击查看图片:

我们应该确定两件事

1.色宽
1.分离宽度

颜色宽度可通过以下功能测量

double colorWidth(double radius, int statusCount, double separation) 
{
return ((2 * pi * radius) - (statusCount * separation)) / statusCount;
}

2 * PI * 半径>>圆的周长

SO >>周长减去所需的总分离像素,然后将结果除以总状态计数。

现在我们有每个状态的宽度相等,以适应圆边界

测量分离像素宽度

取决于状态编号,以更好地增强WhatsApp

double separation(int statusCount) {
if (statusCount <= 20)
  return 3.0;
else if (statusCount <= 30)
  return 1.8;
else if (statusCount <= 60)
  return 1.0;
else
  return 0.3;
}

现在我们将dotted_border包添加到我们的项目中并导入它
https://pub.dev/packages/dotted_border

import 'package:dotted_border/dotted_border.dart';

假设我们上面有一些声明,它们是:

//each digit express a status number
  List status = [1, 2, 5, 4, 9, 13, 15, 20, 30, 40, 80];

  //circle radius
  double radius = 27.0;

dashPattern:

我们有两种状态,一种状态或多种状态(多个状态)

dashPattern: status[index] == 1
                      ? [
                          //one status
                          (2 * pi * (radius + 2)), // take all border
                          0, //zere separators
                        ]
                      : [
                          //multiple status
                          colorWidth(radius + 2, status[index],
                              separation(status[index])), 

                          separation(status[index]), 
                        ],

完整代码:

import 'dart:math';

import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'STATUS',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  List status = [1, 2, 5, 4, 9, 13, 15, 20, 30, 40, 80];

  double radius = 27.0;

  double colorWidth(double radius, int statusCount, double separation) {
    return ((2 * pi * radius) - (statusCount * separation)) / statusCount;
  }

  double separation(int statusCount) {
    if (statusCount <= 20)
      return 3.0;
    else if (statusCount <= 30)
      return 1.8;
    else if (statusCount <= 60)
      return 1.0;
    else
      return 0.3;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: ListView.separated(
          itemCount: status.length,
          separatorBuilder: (context, index) => Divider(
            color: Colors.black,
            height: 15,
          ),
          itemBuilder: ((context, index) => Row(
                children: [
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child:

                        /// Creating a circle with a dotted border.
                        DottedBorder(
                      color: Colors.teal.shade300,
                      borderType: BorderType.Circle,
                      radius: Radius.circular(radius),
                      dashPattern: status[index] == 1
                          ? [
                              //one status
                              (2 * pi * (radius + 2)),
                              0,
                            ]
                          : [
                              //multiple status
                              colorWidth(radius + 2, status[index],
                                  separation(status[index])),
                              separation(status[index]),
                            ],
                      strokeWidth: 3,
                      child: CircleAvatar(
                        radius: radius,
                        backgroundColor: Colors.transparent,
                        child: CircleAvatar(
                          radius: radius - 2,
                        ),
                      ),
                    ),
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Text(
                    '${status[index]}',
                    style: TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ],
              )),
        ),
      ),
    );
  }
}
ih99xse1

ih99xse13#

您可以使用名为status_view的包:
友情链接:status_view

官方文档中的用法

StatusView(
        radius: 40,
        spacing: 15,
        strokeWidth: 2,
        indexOfSeenStatus: 2,
        numberOfStatus: 5,
        padding: 4,
        centerImageUrl: "https://picsum.photos/200/300",
        seenColor: Colors.grey,
        unSeenColor: Colors.red,
      ),

相关问题