Flutter 颤振:如何创建xy栅格以将图标精确定位在需要的位置

Flutter 颤振:如何创建xy栅格以将图标精确定位在需要的位置,flutter,flutter-layout,Flutter,Flutter Layout,我想创建一个移动的水平滑块,跟踪任务的“进度”,并在滑块上方放置图标,指示进度点的某些里程碑。到目前为止,我已尝试使用TimeLineFile小部件移动滑块: Container( margin: const EdgeInsets.all(8), constraints: const BoxConstraints( maxHeight: 50, ), width: screenWidth, child: TimelineTile(

我想创建一个移动的水平滑块,跟踪任务的“进度”,并在滑块上方放置图标,指示进度点的某些里程碑。到目前为止,我已尝试使用TimeLineFile小部件移动滑块:

Container(
    margin: const EdgeInsets.all(8),
    constraints: const BoxConstraints(
        maxHeight: 50,
    ),
    width: screenWidth,
    child: TimelineTile(
        axis: TimelineAxis.horizontal,
        alignment: TimelineAlign.end,
        indicatorStyle: IndicatorStyle(
        indicatorXY: currProgress / 1.0, //currProgress is a double from 0.0 to 1.0 updated by a setState elsewhere in the code
        ),
    ),
),
我知道我可以将其包装在一列中,并在其上方放置一行,其中包含一系列大小的框和图标,但如果能够从数据库中定位动态数量的图标及其位置,这是不准确和非常不混乱的,例如

child: Row(
    children: [
        SizedBox(
            width: screenWidth * .2,
        ),
        Icon(Icon(Icons.email,
            color: Colors.purple),
        ),
        SizedBox(width: screenWidth * .35),
        Icon(Icon(Icons.email,
            color: Colors.purple),
        ),
        SizedBox(width: screenWidth * .2),
    ],
),
我已经查看了gridview,但它似乎需要填充每个网格,所以这不起作用。 我研究过谷歌图表和syncfusion笛卡尔图表,它们可以让我在两行双基网格上定位点,比如(0.2,0.0)和(0.35,1.0),但它们非常笨重,过于复杂,除了简单的圆圈之类的图标,似乎不允许使用,但有人可能比我更了解

最后,我尝试使用FixedTrackSize选项创建一个分辨率非常小的网格,如果用户感觉不到图标的位置是0.3或0.4(让我们将分辨率设为十分之一),而数据实际显示为0.34或0.41,我可以接受它

我希望这是有道理的。要说我只想要两行信息,需要写很多文章: A.一个沿屏幕水平移动的点,该点由变量的设置状态递增(在上面的TimeLineFile示例中为currogress,但是如果某种网格可以工作,我可能不需要使用timelineTime)。以及, B另一行显示从数据库动态读取的图标列表,在该行中,只有数据读取该列表时,您才知道该列表是什么。因此,数据的示例列表可以是: //图标、位置 Icons.email,x=0.35 图标。飞机,x=0.54 等 其中x=0.0到1.0,但可以使用一些合理的分辨率水平,而不是精确到两倍精度,只要它在应用程序屏幕上看起来非常接近


谢谢你们,你们都很好

如果我理解正确,你想要这样的东西

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage();
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  List<IconData> stageIcons = [
    Icons.send,
    Icons.email,
    Icons.done
  ];

  double curProgress = -1.0;
  final iconSize = 20.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Demo"),
      ),
      body: Padding(
        padding: const EdgeInsets.all(32.0),
        child: Stack(
          children: [
            Container(
              padding: EdgeInsets.symmetric(horizontal: iconSize, vertical: iconSize),
              child: LinearProgressIndicator(
                minHeight: 5,
                value: curProgress,
                backgroundColor: Colors.blue[100],
                valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
              ),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: 
                List.generate(stageIcons.length, (index) { // hack, like list.map((element, index) {...})
                  double stageProgress;
                  if (index == 0) {
                    stageProgress = 0.0;
                  } else if (index == stageIcons.length) {
                    stageProgress = 1.0;
                  } else {
                    stageProgress = (1 / (stageIcons.length - 1)) * index;
                  }
                  print("$curProgress $stageProgress ${(stageProgress >= curProgress)}");
                  final iconColor = (stageProgress <= curProgress) ? Colors.blue : Colors.blue[100];
                  return CircleAvatar(
                    radius: iconSize,
                    backgroundColor: iconColor,
                    child: Icon(stageIcons[index], color: Colors.white, size: iconSize),
                  );
                }),
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: 
          (curProgress > 0.0 && curProgress < 1.0) ? null // disable button if in progress
          : () async {
            for (var i = 1; i < 11; i++) {
              await Future.delayed(Duration(milliseconds: 500));
              setState(() {
                curProgress = i / 10;
              });
            }
        },
        child: Icon(Icons.play_arrow),
      ),
    );
  }
}

导入“包装:颤振/材料.省道”;
void main(){
runApp(MyApp());
}
类MyApp扩展了无状态小部件{
@凌驾
小部件构建(构建上下文){
返回材料PP(
标题:“演示”,
主题:主题数据(
主样本:颜色。蓝色,
视觉密度:视觉密度。自适应平台密度,
),
主页:MyHomePage(),
);
}
}
类MyHomePage扩展StatefulWidget{
我的主页();
@凌驾
_MyHomePageState createState()=>\u MyHomePageState();
}
类_MyHomePageState扩展状态{
列表阶段图标=[
Icons.send,
Icons.email,
完成
];
双curProgress=-1.0;
最终iconSize=20.0;
@凌驾
小部件构建(构建上下文){
返回脚手架(
appBar:appBar(
标题:文本(“演示”),
),
主体:填充物(
填充:常数边集全部(32.0),
子:堆栈(
儿童:[
容器(
填充:边集。对称(水平:图标大小,垂直:图标大小),
子对象:线性表达式指示器(
身高:5,,
价值:进步,
背景颜色:颜色。蓝色[100],
valueColor:AlwaysStoppedAnimation(颜色.蓝色),
),
),
划船(
mainAxisAlignment:mainAxisAlignment.spaceBetween,
儿童:
generate(stageIcons.length,(index){//hack,像List.map((element,index){…})
双级推进;
如果(索引==0){
阶段进度=0.0;
}else if(索引==stageIcons.length){
阶段进度=1.0;
}否则{
阶段进度=(1/(stageIcons.length-1))*指数;
}
打印($curProgress$stageProgress${(stageProgress>=curProgress)});
最终iconColor=(stageProgress 0.0&&curProgress<1.0)?null//如果正在进行,则禁用按钮
:()异步{
对于(变量i=1;i<11;i++){
等待未来。延迟(持续时间(毫秒:500));
设置状态(){
curProgress=i/10;
});
}
},
子:图标(图标。播放箭头),
),
);
}
}

更新。自定义进度/位置

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}

class Stage {
  final double progress;
  final IconData icon;
  
  Stage(this.progress, this.icon);
}

class MyHomePage extends StatefulWidget {
  MyHomePage();
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  List<Stage> stages = [
    Stage(0.0, Icons.send),
    Stage(0.71, Icons.email),
    Stage(1.0, Icons.done)
  ];

  double curProgress = -1.0;
  final iconSize = 20.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Demo"),
      ),
      body: Padding(
        padding: const EdgeInsets.all(32.0),
        child: Stack(
          alignment: Alignment.center,
          children: [
            [Container(
              padding: EdgeInsets.symmetric(horizontal: iconSize, vertical: iconSize),
              child: LinearProgressIndicator(
                minHeight: 5,
                value: curProgress,
                backgroundColor: Colors.blue[100],
                valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
              ),
            )],
            List.generate(stages.length, (index) { // hack, like list.map((element, index) {...})
              final stage = stages[index];
              final iconColor = (stage.progress <= curProgress) ? Colors.blue : Colors.blue[100];
              final position = stage.progress * 2 - 1; // progress from 0.0 to 1.0 and Align.position from -1.0 to 1.0 from center
              return Align(
                alignment: Alignment(position, 0.0),
                child: CircleAvatar(
                  radius: iconSize,
                  backgroundColor: iconColor,
                  child: Icon(stage.icon, color: Colors.white, size: iconSize),
                )
              );
            })
          ].expand((el) => el).toList(),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: 
          (curProgress > 0.0 && curProgress < 1.0) ? null // disable button if in progress
          : () async {
            for (var i = 1; i < 11; i++) {
              await Future.delayed(Duration(milliseconds: 500));
              setState(() {
                curProgress = i / 10;
              });
            }
        },
        child: Icon(Icons.play_arrow),
      ),
    );
  }
}
导入“包装:颤振/材料.省道”;
void main(){
runApp(MyApp());
}
类MyApp扩展了无状态小部件{
@凌驾
小部件构建(构建上下文){
返回材料PP(
标题:“演示”,
主题:主题数据(
主样本:颜色。蓝色,
视觉密度:视觉密度。自适应平台密度,
),
主页:MyHomePage(),
);
}
}
班级阶段{
最终双倍进展;
最终的Iconda图标;
阶段(this.progress,this.icon);
}
类MyHomePage扩展StatefulWidget{
我的主页();
@凌驾
_MyHomePageState createState()=>\u MyHomePageState();
}
类_MyHomePageState扩展状态{
列出阶段=[
阶段(0.0,图标。发送),
阶段(0.71,图标。电子邮件),
阶段(1.0,图标。完成)
];
双curProgress=-1.0;
最终iconSize=20.0;
@凌驾
小部件构建(构建上下文){
返回脚手架(
appBar:appBar(
标题:文本(“演示”),
),
主体:填充物(
填充:常数边集全部(32.0),
子:堆栈(
对齐:对齐.center,
儿童:[
[集装箱(
填充:边集。对称(水平:图标大小,垂直:图标大小),
子对象:线性表达式指示器(
身高:5,,
价值:进步,
背景颜色:颜色。蓝色[100],
valueColor:始终停止动画(