How can I detect if widgets overlap in Stack? - flutter

I’m planing to make a simple game in Flutter and in order to do so, I need to detect if some Widgets in Stack overlap(their parts are above each other). Widgets will be moving all the time, so I need to run that check in some kind of a timer.
Any ideas? Nothing crosses my mind.

The timer
The timer problem you can solve with Timer class from dart-async native package. The snippet below creates a timer that at each 750 milliseconds will call a function named _myCallbackFunction. As an example the callback function can check if there is widget overlap.
Timer.periodic(Duration(milliseconds: 750), _myCallbackFunction);
The Stack overlap layout
You can use Positioned widget from flutter framework to control where your widgets will be placed on the stack.
Stack(
children: <Widget>[
Positioned(left: 5, top: 0, right: 0, bottom: 10, child: MyCustomWidget(), );
Positioned(left: 10, top: 10, right: 0, bottom: 0, child: MyCustomWidget(), );
]
);
How to check if there is overlap
Well the trick is state management. Assuming that we're using MyCustomWidget will be needed store the positional parameters of each MyCustomWidget on the stack and eventually check if there is widgets in the same position top, right, bottom, left.You can define your logic and I advise you to user MobX library to do a easy and powerful state management.

Related

Reducing Jank in Flutter progressive web app

Woah! I've spent several hours refactoring nested ListViews to a parent CustomScrollView & child Slivers. The errors Slivers produced were opaque and frightening, with nothing illuminating in Logcat; sleuthing devoured much of the time.
Anyway, that's solved. I find I still have jank scrolling a 15-item list. OK, each item can involve further numerous widgets {Padding, Alignment, Elevated button, Row, Text, SizedBox, Icon}. So my 15-item list ends up being multiple more Widgets.
I've now swapped out my SliverChildListDelegate for SliverChildBuilderDelegates, so a Builder builds the Widget List lazily. Having done this, it seems quite inefficient because it's increased the Widgets in the Widget tree. Each of the builders' buildItem() calls needs an extra Column Widget to wrap that sub-chunk of the total list.
It may be a lot of Widgets scrolling but it's only a 15 item list. I wish I knew what to optimise. Any thoughts on how to best reduce jank on Lists for mobile web?
The Flutter team says Flutter works best for computational-centred apps rather than text heavy informational apps. In future would it be better just to use webView Widgets? I always thought embedding Webviews would be clunky and slow but Lists of native Flutter Widgets, even as SliverLists, give jank.
Here is the janky list complete with builder:
Widget buildLocationDescriptionWidgets(LocationDetails presentLocation) {
print(LOG + "buildLocationDescriptionWidgets");
if (presentLocation.descriptionLinkUrls.isEmpty)
return SliverToBoxAdapter(child:
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Text(presentLocation.description[0])));
int numDescriptionBlocks = presentLocation.description.length;
double paddingBottom = 16;
if (presentLocation.descriptionLinkUrls.length >= numDescriptionBlocks) {
paddingBottom = 0;
}
return SliverPadding(
padding: EdgeInsets.fromLTRB(16, 16, 16, paddingBottom), sliver:
SliverList(
key: Key("descriptionSliverList"),
delegate: SliverChildBuilderDelegate((context, index) =>
buildDescriptionBlock(context, index),
childCount: presentLocation.description.length
),
));
}
Column buildDescriptionBlock(BuildContext context, int index) {
List<Widget> colChildWidget = [];
colChildWidget.add(Text(
widget.presentLocation.description[index],
textAlign: TextAlign.left,
));
if (index < widget.presentLocation.descriptionLinkUrls.length) {
colChildWidget.add(Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Align(
alignment: Alignment.center,
child: index >=
widget.presentLocation.descriptionButtonIcons.length
? ElevatedButton(
child: Text(
widget.presentLocation.descriptionButtonText[index]),
onPressed: () {
_launchURL(
widget.presentLocation.descriptionLinkUrls[index]);
})
: ElevatedButton(
child:
Row(mainAxisSize: MainAxisSize.min, children: [
Text(
widget.presentLocation.descriptionButtonText[index]),
SizedBox(width: 8),
FaIcon(
buttonIconMap[widget.presentLocation
.descriptionButtonIcons[index]],
size: 16)
]),
onPressed: () {
_launchURL(
widget.presentLocation.descriptionLinkUrls[index]);
}))));
}
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: colChildWidget);
}
Should I regress from a builder to a conventional SliverList?
Other things I've tried: I eliminated jank in my app Drawer list by putting const everywhere possible, lots of Dividers were made Const. But when you style text using Theme.of(context).textTheme.bodyText2 etc. it doesn't allow you to set textboxes to const. If you want to use const you can't style the app globally, you'd have to hard code. Is it worth forsaking abstraction for hard coding Text widget styles?
Here is the web app: Love Edinburgh
For the clearest example of jank
Open the App Drawer
Scroll to WONDER
Tap Arthur's Seat
Open the panel to full screen - slide it up
Scroll down the panel.
It doesn't show on a desktop browser which is compiled using Skia / Webkit. It's a bit fiddly to get scroll working on a desktop browser, you need to click amongst the text, then attempt to scroll. It's meant for mobile use so I'm resigned to that.
Not sure how to help you out. Would rather put this on a comment rather than answer but I don't have the points to do a comment.
Anyway, I wish I could replicate your problem. On my personal experience, for a 15 item list with numerous child widgets, it shouldn't be janky unless it has probably big sized images or really too much extra widgets.
On my case, I made sure to "isolate" / "compute" my heavy computations and showed a loading screen while preparing the list.
You may read on:
Isolates : https://dart.dev/guides/language/concurrency
Compute:
https://api.flutter.dev/flutter/foundation/compute-constant.html
Hope that helped!

What is the exact position of the BackButton in an AppBar? Is kToolbarHeight 4px off?

Description
How do you position a custom back button on the exact location where the back button usually would be in an app bar?
Is kToolbarHeight 4px off?
Goal
I fade in the original app bar as the bottom sheet is pushed to the top.
Currently I'm using a Stack widget that positions the back button with kToolbarHeight from the top, but apparently that doesn't really match (It's rather kToolbarHeight - 4px).
I use a custom implementation of a back button, but this problem persists with the original backButton widget too.
Stack(
children: [
Map(...),
const Positioned(
top: kToolbarHeight,
left: 4.0,
child: CustomBackButton(),
),
],
);
Groundwork
I've looked it up in the dev tools and the original source code but couldn't find a reliable constant nor anything helpful.
Try like this:
Give your CustomBackButton() widget the dimensions of a square of kToolbarHeight side.
Inside your Stack, add a SafeArea child, into which you position the CustomBackButton() in the top left corner. That should place it in the right position.

Best way to allow a bit of overscroll in a CustomScrollView

The UI I'm making usually starts with the bottom sliver scrolled all the way in, so that its top is at the top of the view. But also:
Needs an extra empty space at the top, in case the user wants to pull the content down so that they can reach it without moving their hand from the bottom of the phone (this is a really basic ergonomic feature and I think we should expect to see it become pretty common soon, first led by apps moving more of their key functionality to the bottom of the screen, EG, Firefox's url bar.) (Currently, I'm using the appBar sliver for this, but I can imagine a full solution not using that)
Might need extra empty space at the bottom, whenever the content in that bottom sliver wont be long enough to allow it to be scrolled in all the way. It will seem buggy and irregular otherwise. Ideally I'd impose a minHeight so that the bottom sliver will always at least as tall as the screen, but it's a sliver, so I'm pretty sure that's not possible/ugly-difficult.
The avenue I'm considering right now is, ScrollPhysics wrapper that modifies its ScrollMetrics so that maxExtent and minExtent are larger. As far as I can tell, this will allow the CustomScrollView (given this ScrollPhysics) to overscroll. It feels kinda messy though. It would be nice to know what determines maxExtent and minExtent in the first place and alter that.
Lacking better options, I went ahead with the plan, and made my own custom ScrollPhysics class that allows overscroll by the given amount, extra.
return CustomScrollView(
physics: _ExtraScrollPhysics(extra: 100 * MediaQuery.of(context).devicePixelRatio),
...
And _ExtraScrollPhysics is basically just an extended AlwaysScrollable with all of the methods that take ScrollMetrics overloaded to copy its contents into a ScrollMetric with a minScrollExtent that has been decreased by -extra, then passing it along to the superclass's version of the method. It turns out that adjusting the maxScrollExtent field wasn't necessary for the usecase I described!
This has one drawback, the overscroll glow indicator, on top, appears at the top of the content, rather than the top of the scroll view, which looks pretty bad. It looks like this might be fixable, but I'd far prefer a method where this wasn't an issue.
mako's solution is a good starting point but it does not work for mouse wheel scrolling, only includes overscroll at the top, and did not implement the solution to the glow indicator problem.
A more general solution
For web, use a Listener to detect PointerSignalEvents, and manually scroll the list with a ScrollController.
For mobile, listening for events is not needed.
Extend a ScrollPhysics class as mako suggested but use NeverScrollableScrollPhysics for web to prevent the physics from interfering with the manual scrolling. To fix the glow indicator problem for mobile, wrap your CustomScrollView in a ScrollConfiguration as provided by nioncode.
Add overscroll_physics.dart from the gist.
Add custom_glowing_overscroll_indicator.dart from the other gist.
GestureBinding.instance.pointerSignalResolver.register is used to prevent the scroll event from propogating up the widget tree.
Example
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:my_project/custom_glowing_overscroll_indicator.dart';
import 'package:my_project/overscroll_physics.dart';
class OverscrollList extends StatelessWidget {
final ScrollController _scrollCtrl = ScrollController();
final double _topOverscroll = 200;
final double _bottomOverscroll = 200;
void _scrollList(Offset offset) {
_scrollCtrl.jumpTo(
_scrollCtrl.offset + offset.dy,
);
}
#override
Widget build(BuildContext context) {
return Container(
height: 300,
decoration: BoxDecoration(border: Border.all(width: 1)),
child: Listener(
onPointerSignal: (PointerSignalEvent event) {
if (kIsWeb) {
GestureBinding.instance.pointerSignalResolver.register(event, (event) {
_scrollList((event as PointerScrollEvent).scrollDelta);
});
}
},
child: ScrollConfiguration(
behavior: OffsetOverscrollBehavior(
leadingPaintOffset: -_topOverscroll,
trailingPaintOffset: -_bottomOverscroll,
),
child: CustomScrollView(
controller: _scrollCtrl,
physics: kIsWeb
? NeverScrollableOverscrollPhysics(
overscrollStart: _topOverscroll,
overscrollEnd: _bottomOverscroll,
)
: AlwaysScrollableOverscrollPhysics(
overscrollStart: _topOverscroll,
overscrollEnd: _bottomOverscroll,
),
slivers: [
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.blue),
),
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.yellow),
),
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.red),
),
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.orange),
),
],
),
),
),
);
}
}
dartpad demo
Mobile result:

How to make hero widget fly below other widget in route transition

How to make hero widget fly below appBar and bottomBar widgets in route transition.
I had tried to warp other widgets with hero also but no luck.
During a transition, Flutter places the Hero's in the order they are found in the widget (element) tree; last one on top. So you might be able to organise your screen in such a way that, the app bar and bottom bar heros are found later in the tree than the body containing other heros. For example with a Stack:
Stack(
children: [
Body(),
Positioned(
top: 0,
left: 0,
right: 0,
child: AppBar(),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: BottomBar(),
),
],
)
This relies on the implementation details of Flutter's heros, so it is rather hacky.
Another approach would be to copy Flutter's HeroController, Hero and related code and modify it to order the Hero OverlayEntries by a new "z-index" property of the Hero widget.
The Heros "fly" in the Navigator's Overlay. So yet another approach could be to place your app bar and bottom bar on top of the Navigator (for example with a Stack like above).
None of these are great of course.

Is using AnimatedOpacity to hide and show complex widgets not effective in Flutter?

I'm using AnimatedOpacity to hide some widgets in Stack between various states in my app.
For instance I have Stack with ListView and animating loading indicator (shimmer) below, so that when list is populated the animated background opacity is gradually set to 0.
Stack:
- AnimatedOpacity (
opacity: _populated ? 0.0 : 1.0,
child: AnimatedLoadingWidget,
),
- ListView
Do I understand correctly that the animation in AnimatedLoadingWidget continues and is rendered even if opacity is set to 0.0? Does it have a performance impact on the app?
If you're wondering whether or not the widget is still rendered when an opacity of 0, then no.
Opacity and its animated variant is clever enough to not render the child if the opacity is strictly equal to 0.
But that's not enough. Even with an opacity of 0, your loading widget is still in the widget tree, and its animation continues to play.
To be more specific, with typical:
Opacity(
opacity: 0,
child: const CircularProgressIndicator(),
)
the spinner will not be visible, but it will still endlessly request new frames.
To fix that, we can use TickerMode widget like so:
Opacity(
opacity: 0,
child: TickerMode(
enabled: false,
child: const CircularProgressIndicator(),
),
)
Doing so will "mute" animations played using the animation framework, and as such, the spinner will stop requesting for new frames.
Alternatively, you can use AnimatedCrossFade with a custom layoutBuilder to achieve a similar effect.