Flutter: InkWell completely non-functional in Card - flutter

Pasted below is the build method for a widget based on Card that serves as a list element in a ListWheelScrollView. The TweenAnimationBuilder is simply to animate a background color change in the Card widget whenever it's the currently selected list item.
Widget build(BuildContext context) {
Color primary = Theme
.of(context)
.primaryColor;
Color secondary = Colors.white;
return new TweenAnimationBuilder(
tween: new ColorTween(
begin: secondary, end: selected ? primary : secondary),
duration: new Duration(milliseconds: 300),
builder: (BuildContext context, Color color, Widget child) {
return new Card(
color: color,
child: new InkWell(
splashColor: Colors.blue,
child: new Container(
height: 75,
width: 400,
child: new Center(
child: new Text(quiz.title)
)
),
onTap: () => print("Does nothing")
)
);
}
);}
No matter what I do, there are no visual splashes on the Card nor does the onTap handler ever execute.
I've tried every solution I've seen here on SO. Really confused on this one.

I don't know how you're doing it, try this code:
Widget build(BuildContext context) {
var primary = Theme.of(context).primaryColor;
var secondary = Colors.white;
var selected = true;
return Scaffold(
body: TweenAnimationBuilder(
tween: ColorTween(begin: secondary, end: selected ? primary : secondary),
duration: Duration(milliseconds: 300),
builder: (BuildContext context, Color color, Widget child) {
return Card(
color: color,
child: InkWell(
splashColor: Colors.blue,
onTap: () => print('Does nothing'),
child: Container(
height: 75,
width: 400,
child: Center(child: Text('Title')),
),
),
);
},
),
);
}

Unfortunately after getting a little more creative with my search queries, I discovered the answer is that this is simply how Flutter works. Children of a ListWheelScrollView cannot receive gesture input. I suppose this shouldn't be that surprising given how the widget is intended to function. To save others the frustration, please see the duplicate SO question and discussion on Google's Flutter Github linked below. Also linked is a Pub workaround package found via the Flutter Github discussion I'm looking into now.
ListTile OnTap is working when I use ListView. But when i use ListWheelScrollView it doesnt work
https://github.com/flutter/flutter/issues/38803
https://pub.dev/packages/clickable_list_wheel_view

Related

Flutter AnimatedSwitcher jumps between children

I am trying to implement some custom design in an expasion panel list. Therefore, I wanted to create some kind of animation that animates smoothly from one view (e.g. header) to another view (e.g. full info of the tile) that has other dimensions (obviously, full info will be higher than just the header). This is quite easy to implement with an AnimatedContainer. However, I would need the height of the header widget and the full info widget in order to animate between these two heigths. As these values differ between tiles (other info -> maybe other height) and tracking height via global keys is not my preferred solution, I decided to use the much simpler AnimatedSwitcher instead. However, the behavior of my AnimatedSwitcher is quite strange. At first, the other tiles in the ListView (in my example the button) move down instantly and subsequently the tile expands. Has anyone an idea of how I could implement some code in order to achieve the same animation that I would get from AnimatedContainer(button/other tiles moving down simultaniously with the tile expanding)? Thanks in advance for any advice. Here is my code:
class MyPage extends State {
List _items;
int pos;
#override
void initState() {
pos = 0;
_items = [
Container(
color: Colors.white,
width: 30,
key: UniqueKey(),
child: Column(
children: <Widget>[Text('1'), Text('2')], //example that should visualise different heights
),
),
Container(
width: 30,
color: Colors.white,
key: UniqueKey(),
child: Column(
children: <Widget>[Text('1'), Text('2'), Text('44534'), Text('534534')],
),
)
];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
padding: EdgeInsets.only(top: 100),
children: <Widget>[
AnimatedSwitcher(
duration: Duration(seconds: 1),
transitionBuilder: (child, animation) => ScaleTransition(
child: child,
scale: animation,
),
child: _items[pos],
),
RaisedButton(
child: Text('change'),
onPressed: pos == 0
? () {
setState(() => pos = 1);
}
: () {
setState(() => pos = 0);
})
],
),
);
}
}
The solution was quite simple. Just found out that there exists an AnimatedSize Widget that finds out the size of its children automatically.
I stumbled on this post and since I had a similar problem I decided to create a tutorial here on how to mix AnimatedSwitcher and AnimatedSize to solve this issue. Animations do not happen at the same time but the advantage is that you have full control on the animation provided to the switcher.
I ended up doing this in the end (please note that I'm using BlocBuilder and that AnimatedSizeWidget is a basic implementation of AnimatedSize:
AnimatedSizeWidget(
duration: const Duration(milliseconds: 250),
child: BlocBuilder<SwapCubit, bool>(
builder: (context, state) {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 1000),
child: state
? Icon(Icons.face, size: 80, key: Key("80"))
: Icon(Icons.face, size: 160, key: Key("160")),
);
},
),
),
var isWidgetA = true;
final Widget widgetA = Container(
key: const ValueKey(1),
color: Colors.red,
width: 100,
height: 100,
);
final Widget widgetB = Container(
key: const ValueKey(2),
color: Colors.green,
width: 50,
height: 50,
);
...
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
return SizeTransition(
sizeFactor: animation,
child: ScaleTransition(
child: child,
scale: animation,
alignment: Alignment.center,
),
);
},
child: isWidgetA
? widgetA
: widgetB,
),

How to reload specific widgets in flutter without its parent?

I am building an app in Flutter,that supposed to be a social network app-similar to faceboook.
I have implemented a like button-when pressed is sending the server the request,and then depending on the status code it sets the state.My problem begins when the setState() is rendering again the avatar picture,or creating it again from scratch(the avatar is stored in a 64base String).
the likePress is a future that sends the request and then sets the boolean isLiked accordingly.
this is the creating of the like button:
buildLikeButton(int ownerId, int postId) {
return RepaintBoundary(
child: FutureBuilder<bool>(
future: getLike(ownerId, postId),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
IconButton likeButton;
if (snapshot.hasData) {
isLiked = snapshot.data;
likeButton = createLikeButton(ownerId, postId);
} else if (snapshot.hasError) {
isLiked = false;
likeButton = createLikeButton(ownerId, postId);
print('the snapshot has an error ${snapshot.error}');
} else {
isLiked = false;
likeButton = createLikeButton(ownerId, postId);
}
return likeButton;
}));
}
createLikeButton(int ownerId, int postId) {
return IconButton(
icon: returnLikeIcon(isLiked),
color: Theme.of(context).accentColor,
onPressed: () async {
if (this.mounted) {
setState(() {
Future lol = likePress(ownerId, postId).then((onValue) {});
});
}
},
);
}
and this is the creation of the avatar:
createAvatar(BuildContext context, avatar_base64, int ownerId) {
Uint8List bytes = base64Decode(avatar_base64.split(',').last);
return RepaintBoundary(
child: CircleAvatar(
radius: 25.0,
backgroundImage: MemoryImage(bytes),
backgroundColor: Colors.transparent,
));
}
The widget that displays them together is the Post widget which i have created for this project,and this is it's build function:
Widget build(BuildContext context) {
return InkWell(
borderRadius: BorderRadius.circular(0.2),
child: Container(
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: Theme.of(context).primaryColor,
blurRadius: 1.0,
spreadRadius: 1.0, // has the effect of extending the shadow
offset: Offset(
5.0, // horizontal, move right 10
5.0, // vertical, move down 10
),
),
]),
child: Card(
elevation: 10.0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Flexible(
fit: FlexFit.loose,
child: postInfo(context, time, ownerId)),
Divider(
thickness: 1.0,
height: 10.0,
indent: 10.0,
endIndent: 10.0,
),
postContent(content),
Divider(
thickness: 1.0,
height: 10.0,
indent: 10.0,
endIndent: 10.0,
),
createButtonBar(ownerId, postId),
],
)),
));
}
postInfo is just a FutureBuilder that builds the ListTile that adds up the avatar and the name, and createButtonBar creates the like button and 2 more buttons.
I would like to change the icon when the user presses the like button,but only if the server has responded with the right status code and without rendering and creating the whole Post widget over again.Thank you for the trouble!
This means that the avatar is beneath the point where you are calling setState(() {}). In your case, the method is probably inside that particular widget and the widget is being rebuilt.
I suggest for you to solve the problem to move the creation of the avatar above. In this way, if you need to rebuild the object the avatar will not be created anew but simply placed within the new widget. Place some debugPrints around to speed up the process and try to refactor the code to see if you missed something.
After taking a better look at my code,I decided to create a different Widget for each part of the post,so I can initialize everything that will not be built again outside of the build method.
so if you want to exclude a widget from the setState() method, you need to move it outside the current widget(by creating a widget for it) and just create an instance of it as a parameter in the constructor.
In more detail,I created a class named PostHeader and there i created the avatar and the ListTile containing it,then i created an instance of it inside the Post class,so it is not created inside the build method of the Post class.

Flutter - Screen focus on a certain widget

I need help to do the following: when I press List 1, the screen focuses on List 1; I need the same for the rest of the options
This is the code for the example:
code
This behavior already exists in web pages but I haven't found this same behavior at the mobile app level. Thank you
Here is a small code snippet of something similar which might help you achieve you desired results.
By clicking the fab icon it will scroll down to item 35 within the ListView.
class MyHomePage extends StatelessWidget {
final _scrollController = ScrollController();
final _cardHeight = 200.0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.orange,
onPressed: () => _animateToIndex(35),
child: Icon(Icons.add),
),
body: ListView.builder(
controller: _scrollController,
itemCount: 100,
itemBuilder: (_, i) => Container(
height: _cardHeight,
child: Card(
color: Colors.lightBlue,
child: Center(
child: Text("Scroll Item $i", style: TextStyle(fontSize: 28.0),),
),
),
),
),
);
}
_animateToIndex(index) {
_scrollController.animateTo(_cardHeight * index,
duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn);
}
}
You'll need to have a scrollable Widget (like ListView, SingleScrollableWidget) instead of a Column in ListSecondPage.
Then add a ScrollController to it and ListSecondPage should receive which button was tapped. Based on that selection you can scroll to the desired location with the ScrollController

Flutter Showing Snackbar On Top of Bottom Sheet

In my code I call a bottom sheet to display a list of tiles. These tiles contain buttons that display a snackbar. That functionality works fine, the only issue is that the snackbar is displayed behind the bottom sheet so you can only see it if you close the bottom sheet. Each of them are called with the following code:
1. Bottom Sheet:
void _settingModalBottomSheet(context, stream, scaffoldKey ) {
if (_availableRides.length == 0) {
return null;
} else {
return scaffoldKey.currentState.showBottomSheet((context) {
return Column(
children: Widgets;
});
}
}
2. Snackbar
widget.scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text("Created", textAlign:
TextAlign.center,),),
Does anyone know how I can position the snackbar in front of the bottom sheet
So I was able to solve this by just adding another Scaffold() to my Bottom sheet and passing it a new scaffold key
SnackBar has a property for this. It's called behavior, you could do this:
SnackBar(
behavior: SnackBarBehavior.floating,
...
SnackBarBehavior enum
floating → const SnackBarBehavior
This behavior will cause SnackBar to be shown above other widgets in
the Scaffold. This includes being displayed above a
BottomNavigationBar and a FloatingActionButton.
See material.io/design/components/snackbars.html for more details.
I solved by Set (padding from bottom to SnackBar) As much as the height of the (bottomSheet : height) .
In This Case I Have This bottomSheet:
bottomSheet: Container(
child: RaisedButton(...),
width: MediaQuery.of(context).size.width,
height: AppBar().preferredSize.height * 0.85,
),
And This snackBar:
_scaffoldKey.currentState.showSnackBar(SnackBar(
padding:EdgeInsetsDirectional.only(
bottom:AppBar().preferredSize.height * 0.85),
backgroundColor: Colors.red,
duration: new Duration(milliseconds: 3000),
content: Text('ETC..'),
));
You can achieve this Simply by wrapping your BottomSheet widget with a Scaffold.
eg:
void _settingModalBottomSheet(context, stream, scaffoldKey ) {
if (_availableRides.length == 0) {
return null;
} else {
return scaffoldKey.currentState.showBottomSheet((context) {
return Scaffold(
body: Column(
children: Widgets;
})
);
}
}
I arrived on pretty decent solution. I wrapped my bottom sheet in a Scaffold, but I added it as the bottomSheet parameter. While adding the Scaffold, some trailing background will be added, so I just made its background transparent.
Scaffold(
backgroundColor: Colors.transparent,
bottomSheet: ...,
)
This is a working solution according to documentation.
https://docs.flutter.dev/cookbook/design/snackbars
This example works with bottom sheets as well.
Initialize ScaffoldMessengerKey.
Wrap your component widget with Scaffold.
Wrap Scaffold with ScaffoldMessenger.
Add key scaffoldMessengerKey to ScaffoldMessenger
Call method scaffoldMessengerKey.currentState?.showSnackBar(SnackBar());
Example:
final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
// Any widget with button.
// (Bottom sheet also) - root widget must be ScaffoldMessenger.
ScaffoldMessenger(
key: scaffoldMessengerKey,
child: Scaffold(
body: Container(
child: ElevatedButton(
style: raisedButtonStyle,
onPressed: () {
scaffoldMessengerKey.currentState?.showSnackBar(
//SnackBar design.
SnackBar(
backgroundColor: Colors.white,
elevation: 8,
content: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: Text(
'Simple snackbar text.',
style: FlutterFlowTheme.of(context).bodyText1
.override(fontFamily: 'Rubik',
fontWeight: FontWeight.w300,
lineHeight: 1.5,
),
),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
// Some code to undo the change.
},
),
duration: Duration(seconds: 5),
behavior: SnackBarBehavior.floating,
},
child: Text('Open snackbar over bottom sheet!'),
); //ElevatedButton
); //Container
); //Scaffold
); //ScaffoldMessenger
Note:
With this approach you don't need to pass BuildContext.
If you don't want to register ScaffoldMessengerKey.
You can show SnackBar like this: ScaffoldMessenger.of(context).showSnackBar(SnackBar());
I solved this by changing bottomSheet to bottomNavigationBar since the floating snack bar solution didn't work for me.
you can use flushbar package. I think this is the better option if need to use with bottomSheet.
context should be your page's context, not bottomsheet context
any event inside bottomSheet
CustomFlushBar().flushBar(text: 'Thank you for your payment!', context: context,duration: 2);
CustomFlushBar class
class CustomFlushBar {
void flushBar({int duration, #required String text, Color iconColor, IconData iconData, Color backgroundColor, #required BuildContext context}) async {
await dismiss();
Flushbar(
margin: EdgeInsets.all(8),
borderRadius: 8,
backgroundColor: backgroundColor ?? Palette.greenButton,
icon: Icon(iconData ?? Icons.done, color: iconColor ?? Palette.white),
flushbarStyle: FlushbarStyle.FLOATING,
message: text,
duration: Duration(seconds: duration ?? 3),
)..show(context);
}
Future<void> dismiss() async {
if (!Flushbar().isDismissed()) {
await Flushbar().dismiss();
}
}
}

How to customize a dialog's position, size and background?

I have a home screen with a FAB and when it's pressed I want to display a dialog for user to input.
Currently I am using showDialog() with SimpleDialog.
showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: NormalText('New Counter Item'),
contentPadding: EdgeInsets.fromLTRB(24.0, 0.0, 24.0, 24.0),
children: <Widget>[
Container(
...
)
],
);
}
);
But, I can't seem to customise almost anything with it (smaller, corner-curved and positioned lower on the screen). AlertDialog seems to be the same.
Is there anyway to customise those attributes?
return showDialog<void>(
barrierDismissible: true,
context: context,
builder: (BuildContext context) {
return new Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(0),
child: new Container(
height: 100,
width: MediaQuery.of(context).size.width,
color: Colors.purple,
child: new Column(
children: <Widget>[
new Text(
'custom dialog text',
style: new TextStyle(
fontSize: 14,
color: Colors.white,
),
),
],
),
),
)
],
);
},
);
hope this helps,
thanks
SimpleDialog and AlertDialog are meant to cater to a specific set of needs. They have certain levels of customization, but ultimately they are meant to just show simple pop-up dialogs that give the user some information and prompt for a dialog response (i.e. "Yes", "No", "Cancel", "Save", etc.).
If you want to have a more customizable pop-up dialog, you will need to create your own. On the plus side, though, it's really quite simple. Have the builder in showDialog return a Dialog widget with a child set to whatever you want:
showDialog(
context: context,
builder: (BuildContext cxt) {
return Dialog(
child: Container(
...
),
);
},
);
Obviously you are going to need to recreate things like the title bar and action bar, but you can either crack open the source code of SimpleDialog and AlertDialog and copy what they have there or you can roll your own solution.
Despite what the accepted answer says; there are ways to customise the SimpleDialog in the ways that you have requested.
Size
The SimpleDialog will grow in width/height depending on the child and how it is padded. The below code will generate a dialog of width 336 and height 300:
showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
insetPadding: EdgeInsets.zero,
titlePadding: EdgeInsets.zero,
contentPadding: EdgeInsets.zero,
children: [
Container(
width: 300,
height: 300,
),
],
);
},
);
Not sure what the added 36 pixels to the width is; but this demonstrates that size can be customised.
Corners
Corners can be customised using the shape property. The below code shows a green border with a rounded edge of 4 pixels:
showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0),
side: BorderSide(
color: Colors.green,
),
),
/* ... */
);
},
);
Position
I've found that I can nudge the dialog up and down the page by using the insetPadding property. This defines the minimum amount of space required between the edge of the screen and the dialog. Although it's a bit cumbersome, if you knew the size of the screen and the size of the dialog, you could nudge the dialog's position with some simple math.
Given a screen height of 1920px, and a dialog height of 300px, the below code should place your dialog 100px from the top edge of your screen, rather than bang in the centre:
showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
insetPadding: EdgeInsets.only(bottom: 1520),
/* ... */
);
},
);
This is because I've requested a minimum padding between the bottom edge of the screen and the dialog; and the only position for the dialog to exist under this stipulation is nearer the top.
No. These are not designed to be customizable. They were made to respect Material Design principles in mind.
If you want a custom design, you can make your own modal based of Align and/or DecoratedBox
It's not as scary as you might expect.
You only need to clone Dialog.dart, and
replace the Center widget with an Align.
Of course also rename stuff; e.g. MyDialog, myShowDialog, MySimpleDialog.
Yep, it's that easy.
And if you're on a roll, how about adding the Align widget's alignment parameter as an extra...
You can actually modify it's position (only height) by adding a SingleChildScrollView() in the builder + a padding (for the offset from the top) as follows:
showDialog<String>(
context: context,
builder: (ctxDialog) =>
SingleChildScrollView(
child: Padding(
padding: EdgeInsets.only(top: 150.0),
child: AlertDialog()
)
)
);
Here is a clean solution, If for some reason you have a TextField/TextFormField and in there you have a onsubmit property then you can follow:
onSubmit: () {
showDialog(
context: context,
builder: (context){
return WillPopScope(
onWillPop: () { return Future(() => false); }, // this will prevent going back
child: AlertDialog(
content: Row(
children: [
progressWidget(),
Container(margin: EdgeInsets.only(left: 10.0),child:Text("Loading" )),
],),
)
);
}
);
yourCallToMethod.whenComplete((){
Navigator.of(context).pop();
});
},
BENEFIT?
When the showAlert is shown if someone tap on the screen randomly or aggressively then the screen goes back. This prevents that behavior and only closes the showAlert when your function completes.
:- we can change vertical position of dialog boxes by give bottom
insetPadding like
insetPadding: EdgeInsets.only(bottom: XX);
:- And horizontal position by Horizontal padding or left
insetPadding: EdgeInsets.symmetric(horizontal:XX);
or
insetPadding: EdgeInsets.only(left: XX,right:YY);
I found the answer to it here. Thanks to #CopsOnRoad
Using showGeneralDialog
Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
showGeneralDialog(
context: context,
barrierColor: Colors.black54,
barrierDismissible: true,
barrierLabel: 'Label',
pageBuilder: (_, __, ___) {
return Align(
alignment: Alignment.bottomLeft,
child: Container(
color: Colors.blue,
child: FlutterLogo(size: 150),
),
);
},
);
},
),
)
Solved that with wrapping dialog body in Stack and Positioned
Stack(
children: [
Positioned(
left: 100, // left coordinate
top: 100, // top coordinate
child: YOUR_DIALOG....