Related
I'm stuck with making a scrollable list like Google Task app when you reach end of the list if any task is completed it shown in another list with custom header as you can see here, I'm using sliver
Widget showTaskList() {
final todos = Hive.box('todos');
return ValueListenableBuilder(
valueListenable: Hive.box('todos').listenable(),
builder: (context, todoData, _) {
int dataLen = todos.length;
return CustomScrollView(
slivers: <Widget>[
SliverAppBar(
floating: true,
expandedHeight: 100,
flexibleSpace: Container(
padding: EdgeInsets.only(
left: MediaQuery.of(context).size.width / 10,
top: MediaQuery.of(context).size.height / 17),
height: 100,
color: Colors.white,
child: Text(
'My Task',
style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.w600),
),
),
),
SliverList(
delegate:
SliverChildBuilderDelegate((BuildContext context, int index) {
final todoData = todos.getAt(index);
Map todoJson = jsonDecode(todoData);
final data = Todo.fromJson(todoJson);
return MaterialButton(
padding: EdgeInsets.zero,
onPressed: () {},
child: Container(
color: Colors.white,
child: ListTile(
leading: IconButton(
icon: data.done
? Icon(
Icons.done,
color: Colors.red,
)
: Icon(
Icons.done,
),
onPressed: () {
final todoData = Todo(
details: data.details,
title: data.title,
done: data.done ? false : true);
updataTodo(todoData, index);
}),
title: Text(
data.title,
style: TextStyle(
decoration: data.done
? TextDecoration.lineThrough
: TextDecoration.none),
),
subtitle: Text(data.details),
trailing: IconButton(
icon: Icon(Icons.delete_forever),
onPressed: () {
todos.deleteAt(index);
}),
),
),
);
}, childCount: dataLen),
),
],
);
});
}
ShowTaskList is called on
Scaffold(
body: SafeArea(
child: Column(children: <Widget>[
Expanded(
child: showTaskList()
),
]),
),
I tried OffStageSliver to make an widget disappear if no complete todo is present but that did not work and also can not use any other widget on CustomScrollView because that conflict with viewport because it only accept slivers widget.
Here what i have achieved so far
You can try use ScrollController put it on CustomScrollView and listen to it's controller in initState like this :
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
// If it reach end do something here...
}
});
}
I suggest you make bool variable to show your widget, initialize it with false and then after it reach end of controller call setState and make your variable true, which you can't call setState in initState so you have to make another function to make it work like this:
reachEnd() {
setState(() {
end = true;
});
}
Put that function in initState. And make condition based on your bool variabel in your widget
if(end) _yourWidget()
Just like that. I hope you can understand and hopefully this is working the way you want.
I have set UniquiId to the key of Dissmissible Widget. Adding some items after that I'm some dismissing an item. When I am going to the next page I'm getting an error (A dismissed Dismissible widget is still part of the tree.) even after removing the item from the List using Provider in the dismissed callback.
#override
Widget build(BuildContext context) {
return Dismissible(
key: UniqueKey(),
direction: DismissDirection.endToStart,
confirmDismiss: (direction) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Are you sure?'),
content: Text(
'Dou you want to remove the item from the cart?',
),
actions: [
FlatButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text('No'),
),
FlatButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text('Yes'),
),
],
),
);
},
onDismissed: (direction) {
Provider.of<Cart>(context, listen: false)
.removeItem(productId);
},
background: Container(
color: Theme.of(context).errorColor,
child: Icon(
Icons.delete,
color: Colors.white,
size: 40,
),
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 20),
margin: EdgeInsets.symmetric(
horizontal: 15,
vertical: 4,
),
),
child: Container(
child: Padding(
padding: EdgeInsets.all(8),
child: ListTile(
leading: CircleAvatar(
child: Padding(
padding: EdgeInsets.all(5),
child: FittedBox(
child: Text('\₹${(price * quantity)}'),
),
),
),
title: Text(title),
subtitle: Text(laundryName.toUpperCase()),
trailing: Text('$quantity x'),
),
),
),
);
}
}
Please help me out in this tried a lot still getting the same error. Thanks in advance.
Make sure the items are getting removed from the root List. If you are using provider, then get the updated data by keeping listView.builder widget under Consumer. So that when you dismiss an item it will be updated.
So here's my simple use case: I want to have two buttons next to each other horizontally. In native android (which is where I come from) I would have placed them in a LinearLayout and given them weight 1 each and setting their height to wrap_content.
Now, I have placed two RaisedButton in a ButtonBar widget but where I run the app I can see the second one is getting clipped. I want them to be equally spaced and have dynamic height as per their text. How can I achieve the same in flutter? Below is what I have tried so far:
import 'package:flutter/material.dart';
class NewScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: Text("Building layouts"),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context, false),
)),
body: myLayoutWidget(),
),
);
}
}
// replace this method with code in the examples below
Widget myLayoutWidget() {
return Container(
child: Column(
children: <Widget>[
ButtonBar(
children: <Widget>[
RaisedButton(
onPressed: () {},
child: Text("Very long text button",),
),
RaisedButton(
child: Text("Very very very very long text button"),
color: Colors.red,
onPressed: () {},
)
],
),
],
),
);
}
This is how it looks right now:
Try using Row instead of Button Bar and add buttons to Expanded parent
Widget myLayoutWidget() {
return Container(
child: Row(
children: <Widget>[
Expanded(
child: RaisedButton(
onPressed: () {},
child: Text("Very long text button",),
),
),
Expanded(
child: RaisedButton(
child: Text("Very very very very long text button"),
color: Colors.red,
onPressed: () {},
),
)
],
),
);
}
I want to make tutorial screen that show to user at beginning. it's like below :
my specific question, how to make some certain elements will show normally and other are opaque ?
also the arrow and text, how to make them point perfectly based on mobile device screen size (mobile responsiveness) ?
As RoyalGriffin mentioned, you can use highlighter_coachmark library, and I am also aware of the error you are getting, the error is there because you are using RangeSlider class which is imported from 2 different packages. Can you try this example in your app and check if it is working?
Add highlighter_coachmark to your pubspec.yaml file
dependencies:
flutter:
sdk: flutter
highlighter_coachmark: ^0.0.3
Run flutter packages get
Example:
import 'package:highlighter_coachmark/highlighter_coachmark.dart';
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
GlobalKey _fabKey = GlobalObjectKey("fab"); // used by FAB
GlobalKey _buttonKey = GlobalObjectKey("button"); // used by RaisedButton
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
key: _fabKey, // setting key
onPressed: null,
child: Icon(Icons.add),
),
body: Center(
child: RaisedButton(
key: _buttonKey, // setting key
onPressed: showFAB,
child: Text("RaisedButton"),
),
),
);
}
// we trigger this method on RaisedButton click
void showFAB() {
CoachMark coachMarkFAB = CoachMark();
RenderBox target = _fabKey.currentContext.findRenderObject();
// you can change the shape of the mark
Rect markRect = target.localToGlobal(Offset.zero) & target.size;
markRect = Rect.fromCircle(center: markRect.center, radius: markRect.longestSide * 0.6);
coachMarkFAB.show(
targetContext: _fabKey.currentContext,
markRect: markRect,
children: [
Center(
child: Text(
"This is called\nFloatingActionButton",
style: const TextStyle(
fontSize: 24.0,
fontStyle: FontStyle.italic,
color: Colors.white,
),
),
)
],
duration: null, // we don't want to dismiss this mark automatically so we are passing null
// when this mark is closed, after 1s we show mark on RaisedButton
onClose: () => Timer(Duration(seconds: 1), () => showButton()),
);
}
// this is triggered once first mark is dismissed
void showButton() {
CoachMark coachMarkTile = CoachMark();
RenderBox target = _buttonKey.currentContext.findRenderObject();
Rect markRect = target.localToGlobal(Offset.zero) & target.size;
markRect = markRect.inflate(5.0);
coachMarkTile.show(
targetContext: _fabKey.currentContext,
markRect: markRect,
markShape: BoxShape.rectangle,
children: [
Positioned(
top: markRect.bottom + 15.0,
right: 5.0,
child: Text(
"And this is a RaisedButton",
style: const TextStyle(
fontSize: 24.0,
fontStyle: FontStyle.italic,
color: Colors.white,
),
),
)
],
duration: Duration(seconds: 5), // this effect will only last for 5s
);
}
}
Output:
You can use this library to help you achieve what you need. It allows you to mark views which you want to highlight and how you want to highlight them.
Wrap your current top widget with a Stack widget, having the first child of the Stack your current widget.
Below this widget add a Container with black color, wrapped with Opacity like so:
return Stack(
children: <Widget>[
Scaffold( //first child of the stack - the current widget you have
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text("Foo"),
Text("Bar"),
],
),
)),
Opacity( //seconds child - Opaque layer
opacity: 0.7,
child: Container(
decoration: BoxDecoration(color: Colors.black),
),
)
],
);
you then need to create image assets of the descriptions and arrows, in 1x, 2x, 3x resolutions, and place them in your assets folder in the appropriate structure as described here: https://flutter.dev/docs/development/ui/assets-and-images#declaring-resolution-aware-image-assets
you can then use Image.asset(...) widget to load your images (they will be loaded in the correct resolution), and place these widgets on a different container that will also be a child of the stack, and will be placed below the black container in the children list (the Opacity widget on the example above).
It should be mentioned that instead of an opaque approach the Material-oriented feature_discovery package uses animation and integrates into the app object hierarchy itself and therefore requires less custom highlight programming. The turnkey solution also supports multi-step highlights.
Screenshot (Using null-safety):
Since highlighter_coachmark doesn't support null-safety as of this writing, use tutorial_coach_mark which supports null-safety.
Full Code:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late final List<TargetFocus> targets;
final GlobalKey _key1 = GlobalKey();
final GlobalKey _key2 = GlobalKey();
final GlobalKey _key3 = GlobalKey();
#override
void initState() {
super.initState();
targets = [
TargetFocus(
identify: 'Target 1',
keyTarget: _key1,
contents: [
TargetContent(
align: ContentAlign.bottom,
child: _buildColumn(title: 'First Button', subtitle: 'Hey!!! I am the first button.'),
),
],
),
TargetFocus(
identify: 'Target 2',
keyTarget: _key2,
contents: [
TargetContent(
align: ContentAlign.top,
child: _buildColumn(title: 'Second Button', subtitle: 'I am the second.'),
),
],
),
TargetFocus(
identify: 'Target 3',
keyTarget: _key3,
contents: [
TargetContent(
align: ContentAlign.left,
child: _buildColumn(title: 'Third Button', subtitle: '... and I am third.'),
)
],
),
];
}
Column _buildColumn({required String title, required String subtitle}) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
title,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Text(subtitle),
)
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(20),
child: Stack(
children: [
Align(
alignment: Alignment.topLeft,
child: ElevatedButton(
key: _key1,
onPressed: () {},
child: Text('Button 1'),
),
),
Align(
alignment: Alignment.center,
child: ElevatedButton(
key: _key2,
onPressed: () {
TutorialCoachMark(
context,
targets: targets,
colorShadow: Colors.cyanAccent,
).show();
},
child: Text('Button 2'),
),
),
Align(
alignment: Alignment.bottomRight,
child: ElevatedButton(
key: _key3,
onPressed: () {},
child: Text('Button 3'),
),
),
],
),
),
);
}
}
Thanks to #josxha for the suggestion.
If you don't want to rely on external libraries, you can just do it yourself. It's actually not that hard.
Using a stack widget you can put the semi-transparent overlay on top of everything. Now, how do you "cut holes" into that overlay that emphasize underlying UI elements?
Here is an article that covers the exact topic: https://www.flutterclutter.dev/flutter/tutorials/how-to-cut-a-hole-in-an-overlay/2020/510/
I will summarize the possibilities you have:
Use a ClipPath
By using a CustomClipper, given a widget, you can define what's being drawn and what's not. You can then just draw a rectangle or an oval around the relevant underlying UI element:
class InvertedClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
return Path.combine(
PathOperation.difference,
Path()..addRect(
Rect.fromLTWH(0, 0, size.width, size.height)
),
Path()
..addOval(Rect.fromCircle(center: Offset(size.width -44, size.height - 44), radius: 40))
..close(),
);
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}
Insert it like this in your app:
ClipPath(
clipper: InvertedClipper(),
child: Container(
color: Colors.black54,
),
);
Use a CustomPainter
Instead of cutting a hole in an overlay, you can directly draw a shape that is as big as the screen and has the hole already cut out:
class HolePainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.black54;
canvas.drawPath(
Path.combine(
PathOperation.difference,
Path()..addRect(
Rect.fromLTWH(0, 0, size.width, size.height)
),
Path()
..addOval(Rect.fromCircle(center: Offset(size.width -44, size.height - 44), radius: 40))
..close(),
),
paint
);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
Insert it like this:
CustomPaint(
size: MediaQuery.of(context).size,
painter: HolePainter()
);
Use ColorFiltered
This solution works without paint. It cuts holes where children in the widget trees are inserted by using a specific blendMode:
ColorFiltered(
colorFilter: ColorFilter.mode(
Colors.black54,
BlendMode.srcOut
),
child: Stack(
children: [
Container(
decoration: BoxDecoration(
color: Colors.transparent,
),
child: Align(
alignment: Alignment.bottomRight,
child: Container(
margin: const EdgeInsets.only(right: 4, bottom: 4),
height: 80,
width: 80,
decoration: BoxDecoration(
// Color does not matter but must not be transparent
color: Colors.black,
borderRadius: BorderRadius.circular(40),
),
),
),
),
],
),
);
I am trying to create a simple To Do App in in flutter with a Floating Action Button in the bottom which when clicked show an Alert Dialog to add items to the list.
Every time I click on the button, the Keyboard pushes the Action Button upward causing overflowing error.
Is there any way to avoid pushing the action button upward when Keyboard is opened?
Here is the snapshot I took:
Snapshot
Below the source code:
import 'package:flutter/material.dart';
import '../model/todo_item.dart';
class ToDoScreen extends StatefulWidget {
#override
_ToDoScreenState createState() => _ToDoScreenState();
}
class _ToDoScreenState extends State<ToDoScreen> {
TextEditingController _textEditingController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blueAccent,
body: Column(
children: <Widget>[ToDoItem("Going for a Walk", "12 January, 2019")],
),
floatingActionButton: FloatingActionButton(
tooltip: 'Add Item',
child: Icon(Icons.add),
backgroundColor: Colors.red,
onPressed: _showFormDialog,
),
);
}
void _showFormDialog() {
var alert = AlertDialog(
content: Row(
children: <Widget>[
Expanded(
child: TextField(
controller: _textEditingController,
autofocus: true,
decoration: InputDecoration(
labelText: "Item",
hintText: "eg. Buy Vegetables",
icon: Icon(Icons.note_add)),
),
)
],
),
actions: <Widget>[
FlatButton(
onPressed: () {
// _handleSubmit(_textEditingController.text);
_textEditingController.clear();
},
child: Text("Save ToDo"),
),
FlatButton(
onPressed: () {
Navigator.pop(context);
},
child: Text("Cancel"),
)
],
);
showDialog(context: context, builder: (BuildContext context) => alert);
}
}
I had the same issue, where my Floating Action Button would get pushed up.
I solved this using the property:
resizeToAvoidBottomPadding: false, // fluter 1.x
resizeToAvoidBottomInset: false // fluter 2.x
On the parent Scaffold.
I tested it with your code, it solves the issue as well.
You can check if the keyboard is show up or not, and based on that create the floating button or not.
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: keyboardIsOpened ?
null ://or an empty container
FloatingActionButton(
tooltip: 'Add Item',
child: Icon(Icons.add),
backgroundColor: Colors.red,
onPressed: _showFormDialog,
),
);
}
Inside the build method you can know if the keyboard show up by using MediaQuery.of(context).viewInsets.bottom and save its value on a bool variable keyboardIsOpened like the following code.
#override
Widget build(BuildContext context) {
bool keyboardIsOpened = MediaQuery.of(context).viewInsets.bottom != 0.0;
Used MediaQuery and Visibility
Visibility(
visible: MediaQuery.of(context).viewInsets.bottom == 0.0,
child: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.chat),
),
),
When the keyboard is opened, the bottom will not be zero, which will cause fab to get invisible.
Wrap your complete code in this
new Container(
child: Column(
children: <Widget>[
Expanded(
child: SingleChildScrollView(
child: Stack(
children: <Widget>[
// Your body code
] // Widget
), // Stack
), // SingleChildScrollView
), // Expanded
Container(
alignment: Alignment.bottomCenter,
padding: EdgeInsets.all(10.0),
child :
// button code here
// to make button full width use minWidth: double.infinity,
,
), //Container
], // Widget
), // Column
), // Container
Wrapping the Floating Action Button inside a column together with my bottom navigation, then passing this as the child to my bottom navigation bar solved most of the stated concerns: Also ensure you add mainAxisSize: MainAxisSize.min,to your column.
i try this so good
Visibility(
visible: MediaQuery.of(context).viewInsets.bottom == 0.0,
child: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.chat),
),
),