How to programmatically change Z-Index of widget Stack in Flutter - flutter

As you can see in this Stack the yellow cube is at the bellow of a purple cube.
when I click, I want to change the index of the yellow cube to transform it from index 0 to 1 and the purple cube from index 1 to 0, vice versa.
I tried IndexedStack but it's only showing a single child from a list of children.
class _FlipIndex extends State<FlipIndex> {
int currentIndex = 0;
#override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTap: (){
// Change Z-Index of widget
},
child: Stack(
alignment: Alignment.center,
children: [
Transform.translate(
offset: Offset(-30.0, 0.0),
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.yellow,
shape: BoxShape.rectangle,
),
),
),
Transform.translate(
offset: Offset(30.0, 0.0),
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.purple,
shape: BoxShape.rectangle,
),
),
),
],
),
),
);
}
}

Try this package https://pub.dev/packages/indexed
Example image:
This package allows you to order items inside the stack using index like z-index in CSS.
Easily you can change the order of items by change the index property
This is an example of how it works
Indexer(
children: [
Indexed(
index: 100,
child: Positioned(
//...
)
),
Indexed(
index: 1000,
child: Positioned(
//...
)
),
Indexed(
index: 3,
child: Positioned(
//...
)
),
],
);
if you are using bloc of some complex widget you can extands or implement the IndexedInterface class and override index getter:
class IndexedDemo extends IndexedInterface {
int index = 5;
}
or implements
class IndexedDemo extends AnimatedWidget implements IndexedInterface {
int index = 1000;
//...
//...
}
then use it just like Indexed class widget:
Indexer(
children: [
IndexedDemo(
index: 100,
child: Positioned(
//...
)
),
IndexedFoo(
index: 1000,
child: Positioned(
//...
)
),
],
);
Online demo
Video demo

try this:
class _FlipIndex extends State<FlipIndex> {
List<Widget> _stackChildren = [];
int currentIndex = 0;
#override
void initState() {
super.initState();
_stackChildren.add(_stackChild(Colors.yellow, 30));
_stackChildren.add(_stackChild(Colors.green, -30));
}
//call this function for swapping items
void _swapOrder() {
Widget _first = _stackChildren[0];
_stackChildren.removeAt(0);
_stackChildren.add(_first);
setState(() {});
}
Widget _stackChild(Color childColor, double xOffset) {
return Transform.translate(
key: UniqueKey(),
offset: Offset(xOffset, 0.0),
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: childColor,
shape: BoxShape.rectangle,
),
),
);
}
#override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTap: () {
_swapOrder();
},
child: Stack(
alignment: Alignment.center,
children: _stackChildren,
),
),
);
}
}

Related

Is there a listener that can call a function to move on to next set of data in Carousel (Page View)?

I've set up this Carousel using a PageView.builder. It displays 5 tiles at a time.
Once the user has swiped all the way over to the right & pulls on the last tile (see image)...I'd like to move onto the next set of 5 tiles in an array.
Is there an event handler for this? I've managed to set up a listener that can determine when the user has swiped to the last tile, but cannot figure out how to tell when they're pulling on this so it can be refreshed.
Appreciate any help I can get on this. Code below :)
import 'package:flutter/material.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
class RecommendationPanel extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _buildRecommendationPanel();
}
}
class _buildRecommendationPanel extends State<RecommendationPanel> {
PageController _pageController = PageController();
#override
void initState() {
_pageController = PageController(viewportFraction: 1.0);
_pageController.addListener(_scrollListener);
super.initState();
}
void dispose() {
_pageController.dispose();
super.dispose();
}
_scrollListener() {
if (_pageController.offset >= _pageController.position.maxScrollExtent &&
!_pageController.position.outOfRange) {
setState(() {
//This is working in the sense that it tells when they're on the final tile
//I want it so knows when you drag to the right
print('Final tile');
//I could refresh the list and then just move everything back to #1 in the view...i.e. the last card [index 4] can now shift to 5
//_pageController.jumpToPage(0);
});
}
if (_pageController.offset <= _pageController.position.minScrollExtent &&
!_pageController.position.outOfRange) {
setState(() {
//Need to figure out how to work this - there's going to have to be another variable checking what place in the top N recommended array it is, and then adjust accordingly
print('Back to first tile');
//_pageController.jumpToPage(3);
});
}
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
//You may want to use aspect ratio here for tablet support
height: 270.0,
child: PageView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: 5,
scrollDirection: Axis.horizontal,
controller: _pageController,
itemBuilder: (BuildContext context, int itemIndex) {
//I could potentially call something here to update the slider index
return _buildCarouselItem(context, itemIndex);
},
),
),
Container(
height: 30,
child: Center(
child: SmoothPageIndicator(
controller: _pageController,
count: 5,
effect: WormEffect(
spacing: 8.0,
dotHeight: 10,
dotWidth: 10,
activeDotColor: Colors.orange,
),
),
),
),
],
);
}
Widget _buildCarouselItem(BuildContext context, int itemIndex) {
List<String> labels = [
'Pub',
'Bar',
'Football Match',
'Nightclub',
'Book Festival',
'Six',
'Seven',
'Eight',
'Nine',
'Ten',
];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 2.0),
child: Container(
height: double.infinity,
//color: Colors.red,
child: Column(
children: [
Container(
child: Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0)),
child: Container(
// In here is where I should build each individual tile
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
child: Container(
width: double.infinity,
height: 260,
child: Text(labels[itemIndex]),
),
),
),
),
],
),
),
);
}
Widget buildIndicator(BuildContext context, int itemIndex, int count) {
return AnimatedSmoothIndicator(
activeIndex: itemIndex,
count: count,
effect: WormEffect(
dotColor: Colors.grey,
activeDotColor: Colors.orange,
),
);
}
}

Flutter replace widget with SlideTransition

I want to slide out my first widget from right and slide in second from left of screen.
I'm trying to use AnimatedSwitcher with SlideTransition
my current code bug is that first widget doesn't slide out and just vanishes
here is my complete code snippet.
Any help would be appriciated
class LoginPage extends StatefulWidget {
LoginPage({Key? key}) : super(key: key);
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage>
with SingleTickerProviderStateMixin {
static const int PIN_CODE_LENGTH = 4;
final TextEditingController _mobileController = TextEditingController();
final TextEditingController _pinController = TextEditingController();
final UniqueKey _mobileKey = UniqueKey();
final UniqueKey _pinKey = UniqueKey();
bool _submittable = false;
bool _isLoginStepOne = true;
String _buttonText = Strings.next;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Directionality(
textDirection: TextDirection.rtl,
child: SingleChildScrollView(
child: SizedBox(
height: SizePercentConfig.screenHeight,
child: Column(
children: [
_buildHeader(),
Expanded(
child: _buildForm(),
),
],
),
),
),
),
);
}
Widget _buildHeader() {
return Container(
height: SizePercentConfig.safeBlockVertical * 60,
child: Stack(
children: [
Positioned(
bottom: 0,
right: SizePercentConfig.blockSizeHorizontal * 30,
left: SizePercentConfig.blockSizeHorizontal * 30,
child: Image.asset(
Assets.logo,
fit: BoxFit.fitWidth,
),
),
Container(
height: SizePercentConfig.safeBlockVertical * 50,
child: Stack(
children: [
Positioned(
bottom: 0,
child: Image.asset(
Assets.loginHeader,
width: SizePercentConfig.screenWidth,
fit: BoxFit.fitWidth,
),
),
],
),
),
],
),
);
}
Widget _buildForm() {
return Form(
onChanged: _validate,
child: Padding(
padding: const EdgeInsets.all(Dimens.unitX2),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AnimatedSwitcher(
duration: const Duration(seconds: 1),
transitionBuilder: (Widget child, Animation<double> animation) {
final inAnimation = Tween<Offset>(
begin: Offset(1.0, 0.0), end: Offset(0.0, 0.0))
.animate(animation);
final outAnimation = Tween<Offset>(
begin: Offset(-1.0, 0.0), end: Offset(0.0, 0.0))
.animate(animation);
print('** child key: ${child.key}');
print('** mobile key: $_mobileKey');
print('** pin key: $_pinKey');
if (child.key == _mobileKey) {
// in animation
print('>>>>>>> first statement');
return ClipRect(
child: SlideTransition(
position: inAnimation,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: child,
),
),
);
} else {
// out animation
print('>>>>>>> second statement');
return ClipRect(
child: SlideTransition(
position: outAnimation,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: child,
),
),
);
}
},
layoutBuilder:
(Widget? currentChild, List<Widget> previousChildren) {
return currentChild!;
},
child: _isLoginStepOne
? AppTextField(
key: _mobileKey,
controller: _mobileController,
hint: Strings.mobileNumber,
textInputType: TextInputType.phone,
)
: _buildPinCode()),
SizedBox(height: Dimens.unitX2),
AppSolidButton(
onPressed: _buttonAction,
text: _buttonText,
width: SizePercentConfig.screenWidth,
enabled: _submittable,
),
SizedBox(height: Dimens.unitX2),
],
),
),
);
}
void _validate() {
if (_isLoginStepOne) {
if (Regex.mobileRegex.hasMatch(_mobileController.value.text) !=
_submittable)
setState(() {
print('--> setState called in _validate');
_submittable = !_submittable;
});
} else {
if ((_pinController.value.text.length == 4) != _submittable)
setState(() {
print('--> setState called in _validate');
_submittable = !_submittable;
});
}
}
void _buttonAction() {
if (_submittable) {
setState(() {
print('--> setState called in _buttonPressed');
_isLoginStepOne = false;
_submittable = false;
_buttonText = Strings.login;
});
} else {}
}
Widget _buildPinCode() {
return Directionality(
textDirection: TextDirection.ltr,
child: PinCodeTextField(
key: _pinKey,
controller: _pinController,
appContext: context,
length: PIN_CODE_LENGTH,
onChanged: (_) {},
enablePinAutofill: true,
enableActiveFill: true,
textStyle: TextStyle(color: Palette.scorpion),
pinTheme: PinTheme(
shape: PinCodeFieldShape.circle,
fieldHeight: SizePercentConfig.safeBlockHorizontal * 20,
fieldWidth: SizePercentConfig.safeBlockHorizontal * 20,
activeFillColor: Palette.concrete,
inactiveFillColor: Palette.concrete,
selectedFillColor: Palette.roseBud,
activeColor: Palette.concrete,
disabledColor: Palette.concrete,
inactiveColor: Palette.concrete,
selectedColor: Palette.roseBud,
),
cursorColor: Palette.transparent,
keyboardType: TextInputType.number,
),
);
}
}
Give your ClipRect widgets unique keys:
If the "new" child is the same widget type and key as the "old" child, but with different parameters, then AnimatedSwitcher will not do a transition between them, since as far as the framework is concerned, they are the same widget and the existing widget can be updated with the new parameters. To force the transition to occur, set a Key on each child widget that you wish to be considered unique (typically a ValueKey on the widget data that distinguishes this child from the others).

Flutter resolve multiple heroes that share the same tag within a subtree

In my simple part of mobile application i used Hero without any problem and that works fine, now when i try to add a class as Widget which named AnimatedFab in part of this class i get this error:
There are multiple heroes that share the same tag within a subtree.
i don't use any Hero in this class and i'm wondering why i get the error
i used Hero in Stack and implementation code is:
Positioned(
top: 259.0,
left: 6.0,
child: SizedBox(
key: _imageKey,
width: 43.0,
height: 43.0,
child: InkWell(onTap: () {
//...
},child: MyHero(hiveFeed: widget.hiveFeeds)),
)),
and in parent of Stack which above code is one child of that, i have this code:
Positioned(top: 140.0, right: -40.0, child: const AnimatedFab().pl(8.0)),
full Stack children:
return Stack(
children: [
Card(
child: Stack(
children: [
Positioned(top: 140.0, right: -40.0, child: const AnimatedFab().pl(8.0)),
],
),
),
Positioned(
top: 259.0,
left: 6.0,
child: SizedBox(
key: _imageKey,
width: 43.0,
height: 43.0,
child: InkWell(onTap: () {
//...
},child: MyHero(hiveFeed: widget.hiveFeeds)),
)),
],
);
UPDATED
i consider heroTag as a value into below class:
AnimatedFab class which i have problem with that is below code:
Positioned(top: 140.0, right: -40.0, child: AnimatedFab(key:_imageKey).pl(8.0)),
class AnimatedFab extends StatefulWidget {
final VoidCallback onPressed;
final Key _key;
const AnimatedFab({Key key, this.onPressed}) : _key = key;
#override
_AnimatedFabState createState() => _AnimatedFabState();
}
class _AnimatedFabState extends State<AnimatedFab> with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation<Color> _colorAnimation;
final double expandedSize = 160.0;
final double hiddenSize = 50.0;
#override
void initState() {
super.initState();
_animationController = AnimationController(vsync: this, duration: const Duration(milliseconds: 200));
_colorAnimation = ColorTween(begin: Colors.transparent, end: Colors.pink[800]).animate(_animationController);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: expandedSize,
height: expandedSize,
child: AnimatedBuilder(
animation: _animationController,
builder: (BuildContext context, Widget child) {
return Stack(
alignment: Alignment.center,
children: <Widget>[
_buildFabCore(widget.key),
],
);
},
),
);
}
Widget _buildOption(IconData icon, double angle) {
if (_animationController.isDismissed) {
return Container();
}
double iconSize = 0.0;
if (_animationController.value > 0.8) {
iconSize = 26.0 * (_animationController.value - 0.8) * 5;
}
return Transform.rotate(
angle: angle,
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: IconButton(
onPressed: _onIconClick,
icon: Transform.rotate(
angle: -angle,
child: Icon(
icon,
color: Colors.black54,
),
),
iconSize: iconSize,
alignment: Alignment.center,
padding: const EdgeInsets.all(0.0),
),
),
),
);
}
Widget _buildExpandedBackground() {
final double size = hiddenSize + (expandedSize - hiddenSize) * _animationController.value;
return AnimatedOpacity(
opacity: _animationController.value,
duration: const Duration(milliseconds: 300),
child: Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100.0)),
elevation: 4.0,
child: Container(
height: size,
width: size,
),
),
);
}
Widget _buildFabCore(Key key) {
final double scaleFactor = 2 * (_animationController.value - 0.5).abs();
return FloatingActionButton(
key: key,
elevation: 0.0,
mini: true,
onPressed: _onFabTap,
backgroundColor: _colorAnimation.value,
child: Transform(
alignment: Alignment.center,
transform: Matrix4.identity()..scale(1.0, scaleFactor),
child: Icon(
_animationController.value > 0.5 ? Icons.close : Icons.filter_list,
color: _animationController.value > 0.5 ? Colors.white:Colors.black54,
size: 26.0,
),
),
);
}
void open() {
if (_animationController.isDismissed) {
_animationController.forward();
}
}
void close() {
if (_animationController.isCompleted) {
_animationController.reverse();
}
}
void _onFabTap() {
if (_animationController.isDismissed) {
open();
} else {
close();
}
}
void _onIconClick() {
widget.onPressed();
close();
}
}
how can i solve this issue? i think main problem is in _buildFabCore(),, method which i have this in this class.
thanks in advance
Consider passing a value to heroTag for the FloatingActionButton inside _buildFabCore or simply pass null.
This may happen if you have another FloatingActionButton is used within the app so if you didn't pass different heroTag for each one of them you will get this error.

How to create a widget can swipe up/down/left/right in flutter?

How to create a card can swipe up/down/left/right in flutter?
I see can use PageView but it is just for one orientation up-down or left-right.
So how to combine all direction to detect swipe a Wigdet in flutter?
Thanks!
You can use 'PageView' as child of another 'PageView':
class _TrainigState extends State<TrainingPage> {
PageController hPagerController = PageController(keepPage: true);
PageController vPagerController = PageController(keepPage: true);
double mWidth;
double mHeight;
#override
Widget build(BuildContext context) {
mWidth = MediaQuery.of(context).size.width;
mHeight = MediaQuery.of(context).size.height;
return PageView(
controller: hPagerController,
children: [
_verticalPageView([Colors.blue, Colors.purpleAccent, Colors.pinkAccent, Colors.orangeAccent]),
_verticalPageView([Colors.yellow, Colors.orange, Colors.deepOrange, Colors.red]),
_verticalPageView([Colors.green, Colors.lightGreenAccent, Colors.greenAccent, Colors.lightBlueAccent]),
],
);
}
Widget _verticalPageView(List colors) {
return PageView(
controller: vPagerController,
scrollDirection: Axis.vertical,
children: [
Container(
width: mWidth,
height: mHeight,
color: colors[0],
),
Container(
width: mWidth,
height: mHeight,
color: colors[1],
),
Container(
width: mWidth,
height: mHeight,
color: colors[2],
),
Container(
width: mWidth,
height: mHeight,
color: colors[3],
),
],
);
}
}
I hope it is useful for you.

Flutter overflowed positioned Button is not clickable

I have a stack widget parenting a Positioned widget like this:
Stack(
overflow: Overflow.visible,
children: [
Container(
width: 150,
height: 150,
),
Positioned(
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('FAB tapped!');
},
backgroundColor: Colors.blueGrey,
),
right: 0,
left: 0,
bottom: -26,
),
],
),
That part of the fab which is placed outside the container is not clickable, what is the solution?
and here is a screenshot:
try this :
Stack(
overflow: Overflow.visible,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>
[
Container(width: 150, height: 150, color: Colors.yellow),
Container(width: 150, height: 28, color: Colors.transparent),
],
),
Positioned(
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('FAB tapped!');
},
backgroundColor: Colors.blueGrey,
),
right: 0,
left: 0,
bottom: 0,
),
],
)
you should keep button inside of stack if you want it to stay clickable
Providing an updated answer since overflow specification is deprecated after v1.22.0-12.0.pre. clipBehavior is the replacing property:
Stack(
clipBehavior: Clip.none,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>
[
Container(width: 150, height: 150, color: Colors.yellow),
Container(width: 150, height: 28, color: Colors.transparent),
],
),
Positioned(
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('FAB tapped!');
},
backgroundColor: Colors.blueGrey,
),
right: 0,
left: 0,
bottom: 0,
),
],
)
Note: credits to #Amir's answer
The problem is when a child overflows on Stack that has Clip.none behavior, the part that is outside of Stack would not be recognized to be clicked.
Solution :
Wrap the Stack with Column and add the space you want to be outside of Stack :
final _clipSpace = 30;
Stack(
clipBehavior: Clip.none,
children: [
Column(
children: [
DecoratedBox(
decoration: const BoxDecoration(// decorate the box //
),
child: Column(
children: [
// column's children
],
)
],
),
),
// clip space
const SizedBox(height: _clipSpace,)
],
),
const Positioned(
child: _ActionButton(),
left: 0,
right: 0,
bottom: 0,
),
],
);
Container(
width: 150,
height: 180,
child: Stack(
children: [
Container(
width: double.infinity,
height: 150,
child: Image.asset('assets/images/image.jpg', fit: BoxFit.cover,)
),
Container(
alignment: Alignment.bottomCenter,
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('FAB tapped!');
},
backgroundColor: Colors.blueGrey,
),
),
],
),
),
Fab button is not clickable because it renders outside of stack as you have given -ve bottom, Ideally, you should have parent container and inside it has all stack widget you should render it.
Here I have used hardcoded values, but you should use media query as per your requirement
Like:
Container(
width: MediaQuery.of(context).size.width * 0.3,
height: MediaQuery.of(context).size.height * 0.3,
child: Stack(
children: [
Container(
width: double.infinity,
height: MediaQuery.of(context).size.height * 0.26,
child: Image.asset('assets/images/jitesh.jpg', fit: BoxFit.cover,)
),
Container(
alignment: Alignment.bottomCenter,
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('FAB tapped!');
},
backgroundColor: Colors.blueGrey,
),
),
],
),
),
up until now, there is now solution from Flutter, I should make a simple trick to solve this issue, I need to make a layout like this
the workaround is by adding a SizedBox below your background widget, the height of the SizedBox should be the same as the height of the overlaping widget.
like this
Stack(
clipBehavior: Clip.none,
children: [
Column( // wrap the background in a column
children: [
const _HeaderBackground(),
SizedBox(height: 100), // add the SizedBox with height = 100.0
],
),
Positioned(
bottom: 16,
left: 4,
right: 4,
child: _ReferralCodeSection(customer), // the height of this widget is 100
),
],
),
You have to put the button in the last place of the Stack's children
Stack(children: [...., buttonWidget ])
Flutter does not officially plan to solve this problem, so we can only use some hacking methods.
Here is my resolution with an example, you can use the following OverflowWithHitTest Widget directlly:
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
/// Creates a widget that can check its' overflow children's hitTest
///
/// [overflowKeys] is must, and there should be used on overflow widget's outermost widget those' sizes cover the overflow child, because it will [hitTest] its' children, but not [hitTest] its' parents. And i cannot found a way to check RenderBox's parent in flutter.
///
/// The [OverflowWithHitTest]'s size must contains the overflow widgets, so you can use it as outer as possible.
///
/// This will not reduce rendering performance, because it only overcheck the given widgets marked by [overflowKeys].
///
/// Demo:
///
/// class _MyPageStore extends State<MyPage> {
///
/// var overflowKeys = <GlobalKey>[GlobalKey()];
///
/// Widget build(BuildContext context) {
/// return Scaffold(
/// body: OverflowWithHitTest(
///
/// overflowKeys: overflowKeys,
///
/// child: Container(
/// height: 50,
/// child: UnconstrainedBox(
/// child: Container(
/// width: 200,
/// height: 50,
/// color: Colors.red,
/// child: OverflowBox(
/// alignment: Alignment.topLeft,
/// minWidth: 100,
/// maxWidth: 200,
/// minHeight: 100,
/// maxHeight: 200,
/// child: GestureDetector(
/// key: overflowKeys[0],
/// behavior: HitTestBehavior.translucent,
/// onTap: () {
/// print('==== onTap;');
/// },
/// child: Container(
/// color: Colors.blue,
/// height: 200,
/// child: Text('aaaa'),
/// ),
/// ),
/// ),
/// ),
/// ),
/// ),
/// ),
/// );
/// }
/// }
///
///
class OverflowWithHitTest extends SingleChildRenderObjectWidget {
const OverflowWithHitTest({
required this.overflowKeys,
Widget? child,
Key? key,
}) : super(key: key, child: child);
final List<GlobalKey> overflowKeys;
#override
_OverflowWithHitTestBox createRenderObject(BuildContext context) {
return _OverflowWithHitTestBox(overflowKeys: overflowKeys);
}
#override
void updateRenderObject(
BuildContext context, _OverflowWithHitTestBox renderObject) {
renderObject.overflowKeys = overflowKeys;
}
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(
DiagnosticsProperty<List<GlobalKey>>('overflowKeys', overflowKeys));
}
}
class _OverflowWithHitTestBox extends RenderProxyBoxWithHitTestBehavior {
_OverflowWithHitTestBox({required List<GlobalKey> overflowKeys})
: _overflowKeys = overflowKeys,
super(behavior: HitTestBehavior.translucent);
/// Global keys of overflow children
List<GlobalKey> get overflowKeys => _overflowKeys;
List<GlobalKey> _overflowKeys;
set overflowKeys(List<GlobalKey> value) {
var changed = false;
if (value.length != _overflowKeys.length) {
changed = true;
} else {
for (var ind = 0; ind < value.length; ind++) {
if (value[ind] != _overflowKeys[ind]) {
changed = true;
}
}
}
if (!changed) {
return;
}
_overflowKeys = value;
markNeedsPaint();
}
#override
bool hitTest(BoxHitTestResult result, {required Offset position}) {
if (hitTestOverflowChildren(result, position: position)) {
result.add(BoxHitTestEntry(this, position));
return true;
}
bool hitTarget = false;
if (size.contains(position)) {
hitTarget =
hitTestChildren(result, position: position) || hitTestSelf(position);
if (hitTarget || behavior == HitTestBehavior.translucent)
result.add(BoxHitTestEntry(this, position));
}
return hitTarget;
}
bool hitTestOverflowChildren(BoxHitTestResult result,
{required Offset position}) {
if (overflowKeys.length == 0) {
return false;
}
var hitGlobalPosition = this.localToGlobal(position);
for (var child in overflowKeys) {
if (child.currentContext == null) {
continue;
}
var renderObj = child.currentContext!.findRenderObject();
if (renderObj == null || renderObj is! RenderBox) {
continue;
}
var localPosition = renderObj.globalToLocal(hitGlobalPosition);
if (renderObj.hitTest(result, position: localPosition)) {
return true;
}
}
return false;
}
}