如何在 Flutter 中实现这个 UI?

How to achieve this UI in Flutter?

我需要一个可重新排列的三角形作为容器小部件上方的指示器,它还应用了容器的渐变。

现在红色三角形是一种定制油漆,但我愿意用另一种方式来做。它的位置必须是可定制的。 在渐变发挥作用之前一切都很好,因为我也无法将它附加到三角形。

所以我需要的是一个小部件,它结合了两个元素(三角形和容器)并允许我为它定义装饰,就像为普通容器一样。

关于如何解决类似问题的任何想法?

使用 ClipPath() 和自定义形状按钮我建议检查 that

          ClipPath(
            clipper: CustomButton(),
            child: Container(
              height: 150,
              width: 600,
              decoration: const BoxDecoration(
                  gradient: LinearGradient(
                    colors: [
                      Colors.blue,
                      Colors.deepPurple,
                    ],
                    begin: FractionalOffset(0.0, 0.0),
                    end: FractionalOffset(1.0, 1.0),
                    stops: [0.0, 1.0],
                    tileMode: TileMode.clamp,
                  )
              ),
            ),
          )
...
class ArrowClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
final Path path = Path();
    path.moveTo(size.width*0.2788164,0);
    path.lineTo(size.width*0.2797843,0);
    path.cubicTo(size.width*0.2866704,size.height*0.03013193,size.width*0.2916482,size.height*0.06854881,size.width*0.2975996,size.height*0.1026649);
    path.cubicTo(size.width*0.2998894,size.height*0.1151187,size.width*0.3014602,size.height*0.1321108,size.width*0.3046405,size.height*0.1401319);
    path.cubicTo(size.width*0.3129093,size.height*0.1453562,size.width*0.3213164,size.height*0.1418997,size.width*0.3296460,size.height*0.1425330);
    path.cubicTo(size.width*0.5416704,size.height*0.1424011,size.width*0.7536947,size.height*0.1425594,size.width*0.9657190,size.height*0.1424538);
    path.cubicTo(size.width*0.9740321,size.height*0.1419789,size.width*0.9831139,size.height*0.1488391,size.width*0.9893418,size.height*0.1775462);
    path.cubicTo(size.width*0.9951493,size.height*0.1999208,size.width*0.9987611,size.height*0.2330343,size.width,size.height*0.2678628);
    path.lineTo(size.width,size.height*0.8773879);
    path.cubicTo(size.width*0.9988827,size.height*0.9051979,size.width*0.9963827,size.height*0.9319525,size.width*0.9923894,size.height*0.9530871);
    path.cubicTo(size.width*0.9878374,size.height*0.9787863,size.width*0.9811007,size.height*0.9936675,size.width*0.9742865,size.height);
    path.lineTo(size.width*0.02569690,size.height);
    path.cubicTo(size.width*0.01769358,size.height*0.9929551,size.width*0.01011615,size.height*0.9720317,size.width*0.005470133,size.height*0.9397361);
    path.cubicTo(size.width*0.002323009,size.height*0.9201319,size.width*0.001111726,size.height*0.8954881,0,size.height*0.8718734);
    path.lineTo(0,size.height*0.2732454);
    path.cubicTo(size.width*0.001299779,size.height*0.2307124,size.width*0.005945796,size.height*0.1888918,size.width*0.01383850,size.height*0.1663852);
    path.cubicTo(size.width*0.02036504,size.height*0.1438259,size.width*0.02874447,size.height*0.1420053,size.width*0.03649336,size.height*0.1422427);
    path.cubicTo(size.width*0.1065653,size.height*0.1429024,size.width*0.1766427,size.height*0.1420317,size.width*0.2467201,size.height*0.1427441);
    path.cubicTo(size.width*0.2495077,size.height*0.1397361,size.width*0.2535177,size.height*0.1469921,size.width*0.2556305,size.height*0.1350923);
    path.cubicTo(size.width*0.2632688,size.height*0.08978892,size.width*0.2701604,size.height*0.04110818,size.width*0.2788164,0);
    path.close();

    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}

作为 ClipPath 的替代方案,您可以在按钮容器后面使用带有旋转容器的 Stack。这应该为响应式 UI 提供一点误差余地。 (为简洁起见,我没有设计蓝色按钮容器的样式,因为您似乎觉得没问题。)

您可以使用第一个 Positioned left: 属性控制指针的位置。

您可以在 DartPad 中运行下面的代码。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 300,
      height: 150,
      child: Stack(
        children: [
          Positioned(
            top: 12.5,
            left: 100,
            child: Transform.rotate(
              angle: 0.7854,
              child: Container(
                width: 25,
                height: 25,
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    colors: [
                      Colors.red,
                      Colors.orange,
                    ],
                    begin: FractionalOffset(0.0, 0.0),
                    end: FractionalOffset(1.0, 1.0),
                    stops: [0.0, 1.0],
                    tileMode: TileMode.clamp,
                  )
                ),
              )
            ),
          ),
          Positioned(
            top: 20,
            child: Container(
              width: 300,
              height: 100,
              color: Colors.blue,
              child: const SizedBox(width: 300, height: 100)
            ),
          )
        ])
      );
  }
}