[英]Custom shape tappable area with CustomPaint widget on Flutter
我看過一些與這個問題類似的帖子,但它們不是我想要的。 我想在 Flutter 中創建一個具有自定義形狀的按鈕。 為此,我在 GestureDetector 小部件中使用了 CustomPaint 小部件。 問題是我不希望不可見的區域可以點擊。 這正是 GestureDetector 發生的事情。 換句話說,我只希望我創建的形狀是可點擊的。 但是現在我的自定義形狀似乎有一個看不見的正方形,而且它也是可以點擊的。 我不想那樣。 我在這篇文章中發現的最相似的問題:
但是,就我而言,我正在處理自定義形狀而不是正方形或圓形。
讓我與您分享一個可能的按鈕的代碼和示例圖像。 您可以將其復制並直接粘貼到您的主目錄上。 復制我的問題應該很容易。
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
屬性)。 要指定何時點擊CustomPaint
, CustomPainter
有一個可以覆蓋的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.