繁体   English   中英

Flutter 上带有 CustomPaint 小部件的自定义形状可点击区域

[英]Custom shape tappable area with CustomPaint widget on Flutter

我看过一些与这个问题类似的帖子,但它们不是我想要的。 我想在 Flutter 中创建一个具有自定义形状的按钮。 为此,我在 GestureDetector 小部件中使用了 CustomPaint 小部件。 问题是我不希望不可见的区域可以点击。 这正是 GestureDetector 发生的事情。 换句话说,我只希望我创建的形状是可点击的。 但是现在我的自定义形状似乎有一个看不见的正方形,而且它也是可以点击的。 我不想那样。 我在这篇文章中发现的最相似的问题:

Flutter - 自定义按钮点击区域

但是,就我而言,我正在处理自定义形状而不是正方形或圆形。

让我与您分享一个可能的按钮的代码和示例图像。 您可以将其复制并直接粘贴到您的主目录上。 复制我的问题应该很容易。

import 'package:flutter/material.dart';
import 'dart:ui' as ui;

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Custom Shapes',
      theme: ThemeData.dark(),
      home: MyHomePage(title: 'Custom Shapes App'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      backgroundColor: Colors.white24,
      body: Center(
        child: GestureDetector(
          child: CustomPaint(
            size: Size(300,300), //You can Replace this with your desired WIDTH and HEIGHT
            painter: RPSCustomPainter(),
          ),
          onTap: (){
            print("Working?");
          },
        ),
      ),
    );
  }
}
class RPSCustomPainter extends CustomPainter{

  @override
  void paint(Canvas canvas, Size size) {



    Paint paint_0 = new Paint()
      ..color = Color.fromARGB(255, 33, 150, 243)
      ..style = PaintingStyle.fill
      ..strokeWidth = 1;
    paint_0.shader = ui.Gradient.linear(Offset(0,size.height*0.50),Offset(size.width,size.height*0.50),[Color(0xffffed08),Color(0xffffd800),Color(0xffff0000)],[0.00,0.34,1.00]);

    Path path_0 = Path();
    path_0.moveTo(0,size.height*0.50);
    path_0.lineTo(size.width*0.33,size.height*0.33);
    path_0.lineTo(size.width*0.50,0);
    path_0.lineTo(size.width*0.67,size.height*0.33);
    path_0.lineTo(size.width,size.height*0.50);
    path_0.lineTo(size.width*0.67,size.height*0.67);
    path_0.lineTo(size.width*0.50,size.height);
    path_0.lineTo(size.width*0.33,size.height*0.67);
    path_0.lineTo(0,size.height*0.50);
    path_0.close();

    canvas.drawPath(path_0, paint_0);


  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }

}

我会尝试星星是唯一可点击的东西,屏幕上没有其他不可见的地方。

星形按钮

提前致谢!

在这个问题https://github.com/flutter/flutter/issues/60143的帮助下,并提示我应该使用带有自定义形状的 RaisedButton,我能够解决问题。 我对 github 上发布的代码进行了一些更改。 我的不是最好的开始。 除了使用带有 GestureDetector 的 CustomPaint 小部件之外,还有其他更好的选择。

这里有代码。 您应该能够看到,如果您点击给定形状之外的任何位置,将不会触发打印语句。

import 'package:flutter/material.dart';

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

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(body: Center(child: BuyTicketButton(100.0, ()=>{})))
    );
  }
}

class BuyTicketButton extends StatelessWidget {
  final double cost;
  final Function onTap;

  const BuyTicketButton(this.cost, this.onTap, {Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        height: 400,
        child: RaisedButton(
          shape: CustomBorder(),
          onPressed: (){
            print("this works");
          },
        ),
      ),
    );
  }
}


class CustomBorder extends OutlinedBorder {

  const CustomBorder({
    BorderSide side = BorderSide.none
  }) : assert(side != null), super(side: side);

  Path customBorderPath(Rect rect) {
    Path path = Path();
    path.moveTo(0, 0);
    path.lineTo(rect.width, 0);
    path.lineTo(rect.width, rect.height);
    path.lineTo(0, rect.height);

    double diameter = rect.height / 3;
    double radius = diameter / 2;

    path.lineTo(0, diameter * 2);
    path.arcToPoint(
      Offset(0, diameter),
      radius: Radius.circular(radius),
      clockwise: false,
    );
    path.lineTo(0, 0);
    return path;
  }


  @override
  OutlinedBorder copyWith({BorderSide side}) {
    return CustomBorder(side: side ?? this.side);
  }

  @override
  EdgeInsetsGeometry get dimensions => EdgeInsets.all(side.width);

  @override
  Path getInnerPath(Rect rect, {TextDirection textDirection}) {
    return customBorderPath(rect);
  }

  @override
  Path getOuterPath(Rect rect, {TextDirection textDirection}) {
    return customBorderPath(rect);
  }

  @override
  void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
    switch (side.style) {
      case BorderStyle.none:
        break;
      case BorderStyle.solid:
        canvas.drawPath(customBorderPath(rect), Paint()
          ..style = PaintingStyle.stroke
          ..color = Colors.black
          ..strokeWidth = 1.0
        );
    }
  }

  @override
  ShapeBorder scale(double t) => CustomBorder(side: side.scale(t));

}

这是您将看到的图像。 在那里,如果你现在点击半空的圆圈,你会发现什么都不会发生。 这就是我所期待的。

测试攻丝的图像

尽管如此,我还是建议阅读我的另一个答案,这对我来说比这个要好得多。

到目前为止有两种解决方案,第一个只是通过覆盖 CustomPainter class 的 hitTest,但是这种行为并不是最理想的。 因为你在点击时没有任何splashcolor或类似的东西。 所以这是第一个:

import 'package:flutter/material.dart';
import 'dart:ui' as ui;

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

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.black54,
        body: Center(
          child: TappableStarButton(),
        ),
      ),
    );
  }
}

class TappableStarButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        child: CustomPaint(
          size: Size(300, 300),
          painter: RPSCustomPainter(),
        ),
        onTap: () {
          print("This works");
        },
      ),
    );
  }
}

class RPSCustomPainter extends CustomPainter {
  Path path_0 = Path();

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint_0 = new Paint()
      ..color = Color.fromARGB(255, 33, 150, 243)
      ..style = PaintingStyle.fill
      ..strokeWidth = 1;
    paint_0.shader = ui.Gradient.linear(
        Offset(10, 150),
        Offset(290, 150),
        [Color(0xffff1313), Color(0xffffbc00), Color(0xffffca00)],
        [0.00, 0.69, 1.00]);

    path_0.moveTo(150, 10);
    path_0.lineTo(100, 100);
    path_0.lineTo(10, 150);
    path_0.lineTo(100, 200);
    path_0.lineTo(150, 290);
    path_0.lineTo(200, 200);
    path_0.lineTo(290, 150);
    path_0.lineTo(200, 100);
    canvas.drawPath(path_0, paint_0);
  }

  @override
  bool hitTest(Offset position) {
    bool hit = path_0.contains(position);
    return hit;
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
} 

它有效。 问题是当您点击“按钮”时看不到任何行为。

另一种解决方案,更好的方法是使用带有 Inkwell 的 Material 作为按钮。 对于您的形状,ShapeBorder class。

这里是:

import 'package:flutter/material.dart';
import 'dart:ui' as ui;

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

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.black38,
        body: StarButton(),
      ),
    );
  }
}

class StarButton extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        height: 300,
        width: 300,
        child: Material(
          shape: StarShape(),
          color: Colors.orange,
          child: InkWell(
            splashColor: Colors.yellow,
            onTap: () => print('it works'),
          ),
        ),
      ),
    );
  }
}

class StarShape extends ShapeBorder {
  @override
  EdgeInsetsGeometry get dimensions => null;

  @override
  Path getInnerPath(Rect rect, {ui.TextDirection textDirection}) => null;

  @override
  void paint(Canvas canvas, Rect rect, {ui.TextDirection textDirection}) =>
      null;

  @override
  ShapeBorder scale(double t) => null;

  @override
  Path getOuterPath(Rect rect, {ui.TextDirection textDirection}) {
    return Path()
      ..moveTo(150, 10)
      ..lineTo(100, 100)
      ..lineTo(10, 150)
      ..lineTo(100, 200)
      ..lineTo(150, 290)
      ..lineTo(200, 200)
      ..lineTo(290, 150)
      ..lineTo(200, 100)
      ..close();
  }
}

当一个GestureDetector的孩子说它被击中时,它被击中(并开始检测)(除非你碰巧它的behavior属性)。 要指定何时点击CustomPaintCustomPainter有一个可以覆盖的hitTest(Offset)方法。 它应该返回是否应在您的形状内考虑Offset 不幸的是,该方法没有大小参数。 (这是解决方案遇到一些惯性的错误,请参阅https://github.com/flutter/flutter/issues/28206 )唯一好的解决方案是制作自定义渲染 object ,您可以在其中覆盖 paint 和 hitTestSelf 方法(在后者,您可以使用对象的size属性)。

例如:

class MyCirclePainter extends LeafRenderObjectWidget {
  const MyCirclePainter({@required this.radius, Key key}) : super(key: key);

  // radius relative to the widget's size
  final double radius;

  @override
  RenderObject createRenderObject(BuildContext context) => RenderMyCirclePainter()..radius = radius;

  @override
  void updateRenderObject(BuildContext context, RenderMyCirclePainter renderObject) => renderObject.radius = radius;
}

class RenderMyCirclePainter extends RenderBox {
  double radius;

  @override
  void performLayout() {
    size = constraints.biggest;
  }

  @override
  void performResize() {
    size = constraints.biggest;
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    final center = size.center(offset);
    final r = 1.0 * radius * size.width;
    final backgroundPaint = Paint()
      ..color = const Color(0x88202020)
      ..style = PaintingStyle.fill;
    context.canvas.drawCircle(center, r, backgroundPaint);
  }

  @override
  bool hitTestSelf(Offset position) {
    final center = size.center(Offset.zero);
    return (position - center).distance < size.width * radius;
  }
}

请注意,小部件的左上角位于paint方法中的offset参数处,而不是位于CustomPainter中的Offset.zero

您可能想构建一次路径并在hitTestSelf中使用path_0.contains(position)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM