【问题标题】:How to create dotted border around circular avatar in flutter如何在颤动中围绕圆形头像创建虚线边框
【发布时间】:2022-06-21 11:04:40
【问题描述】:

我想在我的 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() 或插件来实现这一点吗?

【问题讨论】:

    标签: flutter user-interface border flutter-dependencies


    【解决方案1】:

    感谢您发布所有尝试,因为它让我直接跳转到 CustomPath() 尝试

    (可能)(未经过良好测试)有效的方法是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(),
        ),
      );
    }
    

    【讨论】:

    • 感谢@Zaid Alazwi。您的答案看起来更容易理解和修改。 cmets 提供了帮助。
    【解决方案2】:

    点击查看图片:

    我们应该确定两件事

    1. 颜色宽度
    2. 分隔宽度\

    可以通过下面的函数测量颜色宽度

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

    【讨论】:

      【解决方案3】:

      您可以使用名为 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,
            ),
      

      【讨论】:

        猜你喜欢
        • 2019-08-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-17
        相关资源
        最近更新 更多