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;
}
}
Related
I am building a horizontal ScrollSnapList. My problem is that I cannot shrink it on the y-axis to the size of the elements height. I tried to give a maxHeight with LimitedBox but the ScrollSnapList takes alle the vertical space available. I have also tried for test purposes to build it with a ListView.builder() - same result:
Hers is my Code:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const AppBarProfile(),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(height: 20,),
LimitedBox(
maxHeight: 140,
child:
ScrollSnapList(
shrinkWrap: true,
key: sslKey,
initialIndex: 0,
//shrinkWrap: true,,
//duration: 1,
scrollDirection: Axis.horizontal,
onItemFocus: (index){
_currentIndex = index;
},
itemSize: MediaQuery. of(context). size. width - 32,
itemBuilder: _buildItem,
itemCount: 10,
scrollPhysics: ClampingScrollPhysics(),
dynamicItemSize: true,
dynamicItemOpacity: 0.7,
dynamicSizeEquation: (distance) {
if (distance > 0){
return 1 - 0.1*distance /MediaQuery. of(context). size. width / 2;
}else{
return 1 + 0.1*distance /MediaQuery. of(context). size. width / 2 ;
}
},
),
),
Expanded(child: ListView(
children: [
Container(height: 200, color: Colors.black,),
],
))
],
),
);
}
Widget _buildItem(BuildContext context, int index) {
return
SizedBox(
width: MediaQuery. of(context). size. width - 32,
child: Item(),
);
}
}
Here is the code of the element being called:
class Item extends StatelessWidget {
const Item({Key? key,}) : super(key: key);
#override
Widget build(BuildContext context) {
return InkWell(
splashColor: Colors.transparent,
onTap: () {
},
child: Column(
children: [
const SizedBox(
height: 8,
),
Container(height:10, width: 20, color: Colors.black)
],
),
);
}
}
Result of code above
As you can see, the space around the items of ScrollSnapList is expanded to the size of the maxHeight of LimitedBox(). I placed another black container beneath to show that it is fully expanding. How do I fix this?
You can do a trick wrapping with Center widget.
Widget _buildEmployeeItem(BuildContext context, int index) {
return SizedBox(
width: MediaQuery.of(context).size.width - 32,
child: Center(
child: Container(
height: 10,
width: 20,
color: Colors.black,
),
),
);
}
You can find more about constraints
I have an app where I have a Scaffold with an AppBar and a bottom Ads Banner.
In between, there is the CameraPreview from the camera plugin in Flutter.
As the CameraPreview is made to take the aspect ratio of the device/camera, the CameraPreview doesn't take the entire available space, leaving extra space on most devices.
I tried to crop the CameraPreview to show only whatever fits in the available space. It worked, but now the preview is stretched out
LayoutBuilder(
builder: (context, constraints) {
final cameraController = controller.cameraController!;
if(cameraController.value.previewSize != null) {
return ClipRect(
child: OverflowBox(
alignment: Alignment.center,
child: FittedBox(
fit: BoxFit.fitWidth,
child: SizedBox(
width: constraints.maxWidth,
height: constraints.maxWidth,
child: AspectRatio(
aspectRatio: cameraController.value.aspectRatio,
child: CameraPreview(cameraController),
),
),
),
),
);
} else {
return const SizedBox.shrink();
}
},
)
I tried other solutions like Transform.scale, but that only zooms into the preview, it doesn't change the ratio or the stretching.
Looking solutions in the package itself doesn't help either, most similar issues are stalling or already closed for stalling.
What am I supposed to do here? Am I supposed to manually clip the preview's value?
check this below code,
Use get screen size by MediaQuery & calculate scale for aspect ratio widget and add CameraPreview() to it like below
// get screen size
final size = MediaQuery.of(context).size;
// calculate scale for aspect ratio widget
var scale = cameraController.value.aspectRatio / size.aspectRatio;
// check if adjustments are needed...
if (cameraController.value.aspectRatio < size.aspectRatio) {
scale = 1 / scale;
}
return Transform.scale(
scale: scale,
child: Center(
child: AspectRatio(
aspectRatio: cameraController.value.aspectRatio,
child: CameraPreview(cameraController),
),
),
);
Complete code
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
if (controller != null && controller.value.isRecordingVideo) {
//stop video
}
},
child: Scaffold(
resizeToAvoidBottomInset: false,
key: _scaffoldKey,
body: Container(
child: Stack(
children: [
Positioned(
child: Container(
alignment: Alignment.center,
child: cameraScreen(),
),
),
Positioned(
child: Container(
alignment: Alignment.bottomCenter,
child: Container(
height: MediaQuery.of(context).size.height * .1,
color: Colors.black54,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
//can add Controls
],
),
),
),
)
],
),
),
),
);
}
Widget cameraScreen() {
final CameraController cameraController = controller;
if (cameraController == null || !cameraController.value.isInitialized) {
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.black,
child: Center(
child: Text(
"Loading Camera...",
style: CameraTextStyle.cameraUtilLoadingStyle(),
),
),
);
} else {
return cameraWidget(context, cameraController);
}
}
Widget cameraWidget(context, cameraController) {
// get screen size
final size = MediaQuery.of(context).size;
// calculate scale for aspect ratio widget
var scale = cameraController.value.aspectRatio / size.aspectRatio;
// check if adjustments are needed...
if (cameraController.value.aspectRatio < size.aspectRatio) {
scale = 1 / scale;
}
return Transform.scale(
scale: scale,
child: Center(
child: AspectRatio(
aspectRatio: cameraController.value.aspectRatio,
child: CameraPreview(cameraController),
),
),
);
}
Widget cameraSwitch() {
final CameraController cameraController = controller;
return Container(
child: InkWell(
onTap: () {
if (cameraController != null &&
cameraController.value.isInitialized &&
!cameraController.value.isRecordingVideo) {
if (cameras.isNotEmpty) {
if (selectedCamera == cameras[0]) {
selectedCamera = cameras[1];
onNewCameraSelected(selectedCamera);
} else {
selectedCamera = cameras[0];
onNewCameraSelected(selectedCamera);
}
}
}
setState(() {});
},
child: Icon(
Icons.switch_camera,
size: 30,
color: Colors.white,
),
),
);
}
I tried using global variables to reach this:
mainly I have a web app with a sidebar that appear and disappear pressing a button.
I'm trying to use the global variable globals.sidebarWidth. Than the main layout left padding should be the globals.sidebarWidth , that take the value when I press a sidebar button that make the sidebar disappear
This is how it works:
import 'globals.dart' as globals;
....
body: Stack(
children: [
// if ((screenSize.width > 800) /*&& (menuVisibility)*/)
Container(
child: SizedBox(
height: screenSize.height,
width: screenSize.width,
child: Image.asset('sfondo-home1.jpg', fit: BoxFit.cover),
),
),
if (screenSize.width > 800) SideBar(),
Padding(
padding: EdgeInsets.only(
top: 50, left: globals.sidebarWidth.toDouble()),
child: SizedBox(
height: 100,
child: Navigator(
key: locator<NavigationService>().navigatorKey,
onGenerateRoute: generateRoute,
initialRoute: HomeRoute,
),
),
),
],
),
Then I want that if I press the arrow button in my SideBar class, all the content float to left "following" the sidebar that is retiring to left and the left padding of my page.
This is from my SideBar class:
Padding(
padding: menuVisib
? EdgeInsets.only(left: 180, top: 400)
: EdgeInsets.only(left: 30, top: 400),
child: Container(
child: CircleAvatar(
backgroundColor: Colors.blueGrey.shade800,
child: InkWell(
onTap: () {
if (menuVisib) {
setState(() {
globals.sidebarWidth = 0;
menuVisib = false;
});
} else {
setState(() {
menuVisib = true;
globals.sidebarWidth = 200;
});
setState(() {});
}
},
child:
Icon(menuVisib ? Icons.arrow_back : Icons.arrow_forward)),
),
),
But it doesn't work. How should I do? Hope I explained well.
Many thanks for your help.
I'm using the PieChart of fl_chart to display the distribution of locally saved documents. The percentages displayed in the chart are the result of the length of the two document type lists (See image below).
But when one List is empty I have a weird bug were my custom Legend gets pushed downwards. The PieChart and the Legend are positioned inside of a Row with flex factors on each children (2 for the PieChart and 4 for the Legend).
I really don't understand what pushes the Legend downwards because my Expanded widgets are always positioned inside of Rows so that the PieChart and Legend only take up the available, horizontal space and not the vertical space which happens in the bug (image 2).
PieChart widget:
class PersonalFilesCircularGraph extends StatefulWidget {
const PersonalFilesCircularGraph();
#override
_PersonalFilesCircularGraphState createState() =>
_PersonalFilesCircularGraphState();
}
class _PersonalFilesCircularGraphState
extends State<PersonalFilesCircularGraph> {
late List<FileTypeData> data;
List<PieChartSectionData> getSections() => data
.asMap()
.map<int, PieChartSectionData>((index, data) {
final value = PieChartSectionData(
color: data.color,
value: data.percent,
showTitle: false,
radius: 3,
);
return MapEntry(index, value);
})
.values
.toList();
#override
void initState() {
/* Example getFileTypeData result
[
FileTypeData(
"Patient Questionnaire",
patientQuestionnaires.length /
(patientQuestionnaires.length +
receivedPatientQuestionnaires.length) *
100,
const Color(0xFF3861FB),
),
FileTypeData(
"Received Patient Questionnaire",
receivedPatientQuestionnaires.length /
(receivedPatientQuestionnaires.length +
patientQuestionnaires.length) *
100,
Colors.teal.shade400,
),
];
*/
data = context.read<SessionBloc>().state.getFileTypeData;
super.initState();
}
#override
Widget build(BuildContext context) {
return BlocConsumer<SessionBloc, SessionState>(
listenWhen: (previous, current) {
final bool listenWhen = previous.patientQuestionnaires.length !=
current.patientQuestionnaires.length ||
previous.receivedPatientQuestionnaires.length !=
current.receivedPatientQuestionnaires.length;
return !listenWhen;
},
listener: (context, state) {
data = context.read<SessionBloc>().state.getFileTypeData;
},
builder: (context, state) {
return Row(
children: [
Expanded(
flex: 2,
child: Container(
constraints: const BoxConstraints(
maxWidth: 60,
maxHeight: 60,
),
child: PieChart(
PieChartData(
sections: getSections(),
),
),
),
),
const SizedBox(
width: kMediumPadding,
),
Expanded(
flex: 4,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: data
.map(
(data) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: buildLegend(
percent: data.percent,
text: data.fileName == "Patient Questionnaire"
? L.of(context).patientQuestionnaires
: L.of(context).receivedPatientQuestionnaire,
color: data.color,
),
),
)
.toList(),
),
),
],
);
},
);
}
Widget buildLegend({
required double percent,
required String text,
required Color color,
}) =>
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Row(
children: [
Container(
width: 10,
height: 10,
color: color,
),
const SizedBox(
width: kSmallPadding,
),
Expanded(
child: Text(
text,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
Text(
"${percent.toStringAsFixed(0)}%",
overflow: TextOverflow.ellipsis,
)
],
);
}
I display the chart widget inside a CustomScrollView, wrapped with a SliverToBoxAdapter inside of my home screen:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: <Widget>[
SliverAppBar(
elevation: 0.0,
floating: true,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
title: Text(
"Home",
style: Theme.of(context).textTheme.headline5,
),
centerTitle: true,
),
const SliverPadding(
padding: EdgeInsets.symmetric(
vertical: kSmallPadding,
horizontal: kMediumPadding,
),
sliver: SliverToBoxAdapter(
child: PersonalFilesCircularGraph(),
),
)
],
);
}
}
EDIT:
I just did some more investigation on this bug and placed a colored Container in my CustomScrollView, below the SliverPadding of the CircularGraph to check if the Column of labels expands downwards. But as you can see the colored Container is not effected. It just looks like the Legend is inside a Stack and positioned without effecting other widgets above and below.
const SliverPadding(
padding: EdgeInsets.symmetric(
vertical: kSmallPadding,
horizontal: kMediumPadding,
),
sliver: SliverToBoxAdapter(
child: PersonalFilesCircularGraph(),
),
),
SliverToBoxAdapter(
child: Container(
width: double.infinity,
height: 60,
color: Colors.green,
),
)
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,
),
),
);
}
}