Я работаю над плавающей кнопкой действия с некоторыми особенностями: когда вы нажимаете на FloatingActionButton, некоторые виджеты InkWell становятся видимыми из Stack, где вы можете нажать на несколько вариантов. Когда я вставил его в свое приложение, я столкнулся с чем-то странным:
Если я добавлю уникальный виджет MyFAB в качестве опции home: в MaterialApp, анимация будет работать отлично, и вы сможете без проблем нажимать на маленькие InkWell виджеты:
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyFAB(),
);
}
}
class MyFAB extends StatefulWidget {
const MyFAB({Key? key}) : super(key: key);
@override
State<MyFAB> createState() => _MyFABState();
}
class _MyFABState extends State<MyFAB> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 300),
);
_animation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _toggle() {
if (_animationController.isDismissed) {
_animationController.forward();
} else {
_animationController.reverse();
}
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
_buildOption(Icons.mood, -0.2),
_buildOption(Icons.sentiment_satisfied, 0.27),
_buildOption(Icons.sentiment_dissatisfied, 0.72),
_buildOption(Icons.mood_bad, 1.2),
FloatingActionButton(
heroTag: "MyFAB",
onPressed: _toggle,
shape: const CircleBorder(),
child: AnimatedIcon(
icon: AnimatedIcons.menu_close,
progress: _animation,
),
),
],
);
}
Widget _buildOption(IconData icon, double index) {
final double angle = (index - 1.5) * 0.5 * pi;
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
final double offsetX = _animation.value * 70 * cos(angle);
final double offsetY = _animation.value * 70 * sin(angle);
return Transform.translate(
offset: Offset(offsetX, offsetY),
child: Transform.scale(
scale: _animation.value,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
print('Option tapped');
_toggle();
},
borderRadius: BorderRadius.circular(20),
splashColor: Colors.grey.withOpacity(0.5),
child: CircleAvatar(
radius: 20,
child: Icon(icon),
),
),
),
),
);
},
);
}
}
Но если я добавлю MyFAB в Scaffold как floatingActionButton:, маленькие значки станут бесполезными, вы больше не сможете на них нажимать.
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
floatingActionButton: MyFAB(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
),
);
}
}
class MyFAB extends StatefulWidget {
const MyFAB({Key? key}) : super(key: key);
@override
State<MyFAB> createState() => _MyFABState();
}
class _MyFABState extends State<MyFAB> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 300),
);
_animation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _toggle() {
if (_animationController.isDismissed) {
_animationController.forward();
} else {
_animationController.reverse();
}
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
_buildOption(Icons.mood, -0.2),
_buildOption(Icons.sentiment_satisfied, 0.27),
_buildOption(Icons.sentiment_dissatisfied, 0.72),
_buildOption(Icons.mood_bad, 1.2),
FloatingActionButton(
heroTag: "MyFAB",
onPressed: _toggle,
shape: const CircleBorder(),
child: AnimatedIcon(
icon: AnimatedIcons.menu_close,
progress: _animation,
),
),
],
);
}
Widget _buildOption(IconData icon, double index) {
final double angle = (index - 1.5) * 0.5 * pi;
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
final double offsetX = _animation.value * 70 * cos(angle);
final double offsetY = _animation.value * 70 * sin(angle);
return Transform.translate(
offset: Offset(offsetX, offsetY),
child: Transform.scale(
scale: _animation.value,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
print('Option tapped');
_toggle();
},
borderRadius: BorderRadius.circular(20),
splashColor: Colors.grey.withOpacity(0.5),
child: CircleAvatar(
radius: 20,
child: Icon(icon),
),
),
),
),
);
},
);
}
}
Я застрял на этом этапе. Что вызывает такое поведение? Как я могу без проблем использовать MyFAB внутри Scaffold?
Вы можете опробовать мой код в https://dartpad.dev/
Я перекодировал весь виджет MyFAB, чтобы не использовать Stack, удалил опцию floatingActionButtonLocation из Scaffold, ничего не помогло.
@Иво, это определенно выглядит многообещающе, я проверю. Я никогда раньше не проводил тест на попадание в VS Code. Не могли бы вы предоставить мне несколько статей об этом?





Проблема, с которой вы столкнулись, когда виджеты InkWell не доступны для щелчка, когда MyFAB() используется в качестве плавающей кнопки ActionButton в Scaffold, вероятно, связана с позиционированием и тестированием попадания виджетов в стек. При использовании floatActionButton в Scaffold стек плавающей кнопки действия может не охватывать весь экран, или виджеты в стеке могут быть неправильно расположены в области касания.
Чтобы решить эту проблему, вы можете убедиться, что стек, содержащий виджеты InkWell, занимает весь экран и расположен правильно. Один из подходов — использовать виджет «Позиционированный», чтобы правильно разместить кнопку плавающего действия и параметры в стеке.
Эй, можешь просто проверить это !!!
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Stack(
children: [
// Your main content goes here
],
),
floatingActionButton: MyFAB(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
),
);
}
}
class MyFAB extends StatefulWidget {
const MyFAB({Key? key}) : super(key: key);
@override
State<MyFAB> createState() => _MyFABState();
}
class _MyFABState extends State<MyFAB> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 300),
);
_animation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _toggle() {
if (_animationController.isDismissed) {
_animationController.forward();
} else {
_animationController.reverse();
}
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
_buildOption(Icons.mood, -0.2),
_buildOption(Icons.sentiment_satisfied, 0.27),
_buildOption(Icons.sentiment_dissatisfied, 0.72),
_buildOption(Icons.mood_bad, 1.2),
Positioned(
bottom: 16.0, // Adjust the position as needed
child: FloatingActionButton(
heroTag: "MyFAB",
onPressed: _toggle,
shape: const CircleBorder(),
child: AnimatedIcon(
icon: AnimatedIcons.menu_close,
progress: _animation,
),
),
),
],
);
}
Widget _buildOption(IconData icon, double index) {
final double angle = (index - 1.5) * 0.5 * pi;
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
final double offsetX = _animation.value * 70 * cos(angle);
final double offsetY = _animation.value * 70 * sin(angle);
return Transform.translate(
offset: Offset(offsetX, offsetY),
child: Transform.scale(
scale: _animation.value,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
print('Option tapped');
_toggle();
},
borderRadius: BorderRadius.circular(20),
splashColor: Colors.grey.withOpacity(0.5),
child: CircleAvatar(
radius: 20,
child: Icon(icon),
),
),
),
),
);
},
);
}
}
Стек внутри Scaffold гарантирует, что виджеты InkWell и FloatingActionButton расположены правильно и охватывают весь экран, делая их кликабельными. Свойство clipBehavior: Clip.none стека позволяет при необходимости рисовать виджеты InkWell за пределами стека. Виджет «Позиционирование» обеспечивает правильное размещение плавающей кнопки действия в нижней части экрана. Отрегулируйте нижнее свойство виджета «Позиционировано» по мере необходимости, чтобы получить желаемое положение.
Спасибо за Ваш ответ. К сожалению, ваш код не работает с dartpad. Я проверю это с помощью VS Code, как только закончу сегодняшнюю работу. :)
Это потому, что тапируемая область в floatingActionButton привязана к дочернему виджету, а не к дополнительной кнопке 4, это иллюстрация, если дочерний виджет floatingActionButton обернут контейнером для увеличения области макета, отмеченной желтым цветом:
MaterialApp(
home: Scaffold(
floatingActionButton: Container(
width: 120,
height: 120,
color: Colors.yellow,
child: MyFAB(),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
),
);
Результат:
Мой предлагаемый шаг ответа приведен ниже:
Container или SizedBox, чтобы изменить размер области касания.MyFAB, чтобы выровнять кнопку по низу внутри контейнера.Ниже приведен окончательный код, изменения отмечены комментариями:
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
floatingActionButton: Container( // <- wrap to resize area
width: 175,
height: 115,
color: Colors.yellow, // <- remove the color later
child: MyFAB(),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
),
);
}
}
class MyFAB extends StatefulWidget {
const MyFAB({Key? key}) : super(key: key);
@override
State<MyFAB> createState() => _MyFABState();
}
class _MyFABState extends State<MyFAB> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 300),
);
_animation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _toggle() {
if (_animationController.isDismissed) {
_animationController.forward();
} else {
_animationController.reverse();
}
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.bottomCenter, // <- align to bottom
children: [
_buildOption(Icons.mood, -0.2),
_buildOption(Icons.sentiment_satisfied, 0.27),
_buildOption(Icons.sentiment_dissatisfied, 0.72),
_buildOption(Icons.mood_bad, 1.2),
FloatingActionButton(
heroTag: "MyFAB",
onPressed: _toggle,
shape: const CircleBorder(),
child: AnimatedIcon(
icon: AnimatedIcons.menu_close,
progress: _animation,
),
),
],
);
}
Widget _buildOption(IconData icon, double index) {
final double angle = (index - 1.5) * 0.5 * pi;
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
final double offsetX = _animation.value * 70 * cos(angle);
final double offsetY = _animation.value * 70 * sin(angle);
return Transform.translate(
offset: Offset(offsetX, offsetY),
child: Transform.scale(
scale: _animation.value,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
print('Option tapped');
_toggle();
},
borderRadius: BorderRadius.circular(20),
splashColor: Colors.grey.withOpacity(0.5),
child: CircleAvatar(
radius: 20,
child: Icon(icon),
),
),
),
),
);
},
);
}
}
И вот результат, цвет можно убрать:
Надеюсь, это решит вашу проблему 😉
Вы, сэр, не что иное, как гений! Огромное спасибо, теперь все работает отлично. 😊 Это какое-то требование самого виджета Stack()?
Отвечает ли это на ваш вопрос? Как во Flutter позиционированный виджет может ощущать касания за пределами родительской области стека?