簡體   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