I have a Stack with 3 Positioned widgets with a GestureDetector as a child. I'm able to drag them, but I also want to bring the clicked one to the front as soon as you click it.
I've tried lifting up a call to change state in the parent widget, but that does also exchange the afected widgets position.
Here is the full sample code (it's the body in a Scaffold.
Any help will be appreciated
class DragBox extends StatefulWidget {
final Offset startPosition;
final Color color;
final String label;
final Function bringToTop;
DragBox({
this.startPosition,
this.color: const Color.fromRGBO(255, 255, 255, 1.0),
this.label,
this.bringToTop
});
#override
DragBoxState createState() => DragBoxState();
}
class DragBoxState extends State<DragBox> {
Offset position;
Offset _startPos;
#override
void initState() {
position = widget.startPosition;
super.initState();
}
#override
Widget build(BuildContext context) {
return Positioned(
left: position.dx,
top: position.dy,
child: GestureDetector(
onScaleStart: (details) {
//Store start conditions
_startPos = details.focalPoint;
widget.bringToTop(widget);
},
onScaleUpdate: (scaleDetails) {
setState(() {
position += scaleDetails.focalPoint - _startPos;
_startPos = scaleDetails.focalPoint;
});
},
child: _buildPart()
));
}
Widget _buildPart() {
return Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
border: Border.all(color: Color.fromARGB(255, 0, 0, 0), width: 1.0),
color: widget.color),
child: Text(
widget.label,
style: TextStyle(
color: Color.fromRGBO(255, 255, 255, 1.0),
//decoration: TextDecoration.none,
fontSize: 15.0,
),
),
);
}
}
class WorkTable extends StatefulWidget {
#override
WorkTableState createState() => WorkTableState();
}
class WorkTableState extends State<WorkTable> {
List<DragBox> dragParts = [];
#override
void initState() {
dragParts = [];
//dragParts = [];
dragParts.add(DragBox(
startPosition: Offset(0.0, 0.0),
color: Colors.red,
label: "Box1",
bringToTop: this.bringToTop,
));
dragParts.add(DragBox(
startPosition: Offset(50.0, 50.0),
color: Colors.red,
label: "Box2",
bringToTop: this.bringToTop,
));
dragParts.add(DragBox(
startPosition: Offset(100.0, 100.0),
color: Colors.blue,
label: "Box3",
bringToTop: this.bringToTop,
));
super.initState();
}
#override
Widget build(BuildContext context) {
for(DragBox d in dragParts){
print(d.label);
}
return Stack(
children: dragParts,
);
}
void bringToTop(Widget widget) {
setState(() {
dragParts.remove(widget);
dragParts.add(widget);
});
}
}
The strange part happens here
void bringToTop(Widget widget) {
setState(() {
dragParts.remove(widget);
dragParts.add(widget);
});
}
Instead of changing only depth, positions are also exchanged
Adding a GlobalKey (or some other key) to your widgets should fix the problem:
dragParts.add(DragBox(
key: GlobalKey(),
startPosition: Offset(0.0, 0.0),
color: Colors.red,
label: "Box1",
bringToTop: this.bringToTop,
));
dragParts.add(DragBox(
key: GlobalKey(),
startPosition: Offset(50.0, 50.0),
color: Colors.red,
label: "Box2",
bringToTop: this.bringToTop,
));
dragParts.add(DragBox(
key: GlobalKey(),
startPosition: Offset(100.0, 100.0),
color: Colors.blue,
label: "Box3",
bringToTop: this.bringToTop,
));
https://i.stack.imgur.com/TeeQZ.gif
My knowledge column: https://zhuanlan.zhihu.com/p/46982762
Example:
class TestBringToFrontPage extends StatefulWidget {
TestBringToFrontPage({Key key}) : super(key: key);
#override
State<TestBringToFrontPage> createState() {
return TestBringToFrontState();
}
}
class BoxData {
final double width;
final double height;
final Offset position;
final String key;
final Color color;
final TextAlign align;
BoxData(this.key, this.color,this.align, this.width, this.height, this.position);
}
class TestBringToFrontState extends State<TestBringToFrontPage> {
List<Widget> widgetArrays = [];
var boxDataArrays = [
BoxData("Flutter版的bringToFront", Colors.blueGrey,TextAlign.center, 200.0, 30.0, Offset(100.0, 200.0)),
BoxData("AAA", Colors.blue,TextAlign.left, 100.0, 100.0, Offset(100.0, 300.0)),
BoxData("BBB", Colors.orange,TextAlign.right, 100.0, 100.0, Offset(150.0, 250.0)),
];
#override
void initState() {
super.initState();
widgetArrays = [];
for (int i = 0; i < boxDataArrays.length; i++) {
var boxData = boxDataArrays[i];
widgetArrays.add(Positioned(
key: Key(boxData.key),
left: boxData.position.dx,
top: boxData.position.dy,
child: GestureDetector(
child: _buildBox(
boxData.color,
boxData.key,
boxData.align,
boxData.width,
boxData.height
),
onTap: () {
bringToFront(Key(boxData.key));
},
)));
}
}
#override
Widget build(BuildContext context) {
return Stack(
children: widgetArrays,
);
}
void bringToFront(Key key) {
setState(() {
for (var i = 0; i < widgetArrays.length; i++) {
Widget widget = widgetArrays[i];
if (key == widget.key) {
widgetArrays.remove(widget);
widgetArrays.add(widget);
break;
}
}
});
}
Widget _buildBox(
Color color, String label, TextAlign align, double width, double height) {
return Container(
width: width,
height: height,
decoration: BoxDecoration(
border: Border.all(color: Color.fromARGB(255, 0, 0, 0), width: 1.0),
color: color),
child: Text(
label,
textAlign: align,
style: TextStyle(
color: Color.fromRGBO(255, 255, 255, 1.0),
fontWeight: FontWeight.w700,
//decoration: TextDecoration.none,
fontSize: 15.0,
),
),
);
}
}
Related
I have implemented a TextField that allows user input in my flutter app. Now, I would like to save the user input data to a variable on pressing a button in order to use it later on. As I see it, in order to achieve this, I have to create a specific instance of the stateful widget and of its state so that when I call the widget to extract the variable, it does not create a new version of the widget with an empty TextField. The textfields in question are ProjectNameField and ProjectDescriptionField.
Here is my implementation:
ProjectDescriptionField savedDescription = ProjectDescriptionField();
ProjectNameField savedName = ProjectNameField();
AddPage savedPage = AddPage();
_AddPageState savedPageState = _AddPageState();
List<String> image_list_encoded = [];
class AddPage extends StatefulWidget {
final _AddPageState _addPageState = _AddPageState();
AddPage({Key? key}) : super(key: key);
#override
_AddPageState createState() => _AddPageState();
}
class _AddPageState extends State<AddPage> {
List<Asset> images = <Asset>[];
String _error = NOERROR;
#override
Widget build(BuildContext context) {
if (images.isEmpty)
return AppLayout(logicalHeight * 0.15);
else
return AppLayout(logicalHeight * 0.01);
}
List<Asset> getImages() {
return images;
}
Widget AppLayout(double distanceFromImages) {
return Scaffold(
appBar: AppBar(
leading: BackButton(color: Colors.white),
title: Text(CREATEPROJECT),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.check,
color: Colors.white,
),
onPressed: () {
SavedData _savedData = SavedData(
savedName._nameFieldState.myController.value.text,
savedDescription
._descriptionFieldState.myController.value.text,
savedPage._addPageState.getImages());
print(_savedData.saved_Project_Description);
print(_savedData.saved_Project_Name);
print(_savedData.saved_images.toString());
saveNewProject(_savedData);
},
),
],
),
body: Column(children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.02, 0, 0),
child: Center(
child: Container(
height: logicalHeight * 0.05,
width: logicalWidth * 0.9,
child: savedName)),
),
Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
child: ElevatedButton(
child: Text(PICKIMAGES),
onPressed: loadAssets,
)),
Center(
child: Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
child: getWidget())),
Padding(
padding: EdgeInsets.fromLTRB(
logicalWidth * 0.05, distanceFromImages, logicalWidth * 0.05, 0),
child: Center(child: Container(child: savedDescription)),
),
]),
);
}
Widget getWidget() {
if (images.length > 0) {
return Container(
height: logicalHeight * 0.4,
width: logicalWidth * 0.8,
child: ImagePages());
} else {
return Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.15, 0, 0),
child: Container(child: Text(NOPICTURESSELECTED)));
}
}
PageView ImagePages() {
final PageController controller = PageController(initialPage: 0);
List<Widget> children = [];
images.forEach((element) {
children.add(Padding(
padding: EdgeInsets.fromLTRB(
logicalWidth * 0.01, 0, logicalWidth * 0.01, 0),
child: AssetThumb(
asset: element,
width: 1000,
height: 1000,
)));
});
return PageView(
scrollDirection: Axis.horizontal,
controller: controller,
children: children,
);
}
}
class ProjectNameField extends StatefulWidget {
ProjectNameFieldState _nameFieldState = ProjectNameFieldState();
#override
ProjectNameFieldState createState() {
return _nameFieldState;
}
}
class ProjectNameFieldState extends State<ProjectNameField> {
final myController = TextEditingController();
#override
void dispose() {
// Clean up the controller when the widget is disposed.
myController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return TextField(
controller: myController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
hintText: PROJECTNAME,
),
maxLength: 30,
);
}
}
class ProjectDescriptionField extends StatefulWidget {
ProjectDescriptionFieldState _descriptionFieldState =
ProjectDescriptionFieldState();
#override
ProjectDescriptionFieldState createState() {
return _descriptionFieldState;
}
}
class ProjectDescriptionFieldState extends State<ProjectDescriptionField> {
final myController = TextEditingController();
#override
void dispose() {
// Clean up the controller when the widget is disposed.
myController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return TextField(
controller: myController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
hintText: PROJECTDESCRIPTION,
),
minLines: 1,
maxLines: 5,
maxLength: 5000,
);
}
}
class SavedData {
String saved_Project_Name = "";
String saved_Project_Description = "";
List<Asset> saved_images = [];
SavedData(String saved_Project_Name, String saved_Project_Description,
List<Asset> saved_images) {
this.saved_Project_Name = saved_Project_Name;
this.saved_Project_Description = saved_Project_Description;
this.saved_images = saved_images;
}
}
I believe the problem lies here (ProjectNameField and ProjectDescriptionField are essentially the same, with only minor differences):
class ProjectNameField extends StatefulWidget {
ProjectNameFieldState _nameFieldState = ProjectNameFieldState();
#override
ProjectNameFieldState createState() {
return _nameFieldState;
}
}
class ProjectNameFieldState extends State<ProjectNameField> {
final myController = TextEditingController();
#override
void dispose() {
// Clean up the controller when the widget is disposed.
myController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return TextField(
controller: myController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
hintText: PROJECTNAME,
),
maxLength: 30,
);
}
}
When I let createState() return _nameFieldState instead of ProjectNameFieldState(), the following error occurs:
The createState function for ProjectDescriptionField returned an old or invalid state instance: ProjectDescriptionField, which is not null, violating the contract for createState.
'package:flutter/src/widgets/framework.dart':
Failed assertion: line 4681 pos 7: 'state._widget == null'
However, when I return ProjectNameFieldState(), then this code
onPressed: () {
SavedData _savedData = SavedData(
savedName._nameFieldState.myController.value.text,
savedDescription
._descriptionFieldState.myController.value.text,
savedPage._addPageState.getImages());
print(_savedData.saved_Project_Description);
print(_savedData.saved_Project_Name);
print(_savedData.saved_images.toString());
saveNewProject(_savedData);
}
does not save the project name.
How can I get rid of this error and save the project name and project description?
Thank you!
You can use the Form and TextFormFiled Widgets to easily save the value in your variable and also validate the form fields.
Here is the code snippet:
Declare a GlobalKey for the Form Widget
final _formKey = GlobalKey<FormState>();
In your Scaffold body where you will putting your TextFormFiled add the Form Widget as the parent for all the fields
Form(
key: _formKey,
child: Column(children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.02, 0, 0),
child: Center(
child: Container(
height: logicalHeight * 0.05,
width: logicalWidth * 0.9,
child: TextFormField(
controller: projectNameController,
decoration: InputDecoration(
hintText: PROJECTNAME,
border: UnderlineInputBorder()),
maxLength: 30
validator: (value) {
if (value.isEmpty) {
return 'This is a required field';
}
return null;
},
onSaved: (value) => savedProjectName = value),
)),
),
Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
child: ElevatedButton(
child: Text(PICKIMAGES),
onPressed: loadAssets,
)),
Center(
child: Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
child: getWidget())),
Padding(
padding: EdgeInsets.fromLTRB(
logicalWidth * 0.05, distanceFromImages, logicalWidth * 0.05, 0),
child: Center(child: Container(child: TextFormField(
controller:projectDescriptionController ,
decoration: InputDecoration(
hintText: PROJECTDESCRIPTION,
border: UnderlineInputBorder()),
minLines: 1,
maxLines: 5,
maxLength: 5000,
validator: (value) {
if (value.isEmpty) {
return 'This is a required field';
}
return null;
},
onSaved: (value) => savedProjectDescription = value))),
),
]),
),
Now in your IconButton's onPressed validate the form and use the save function to save the TextFormField values in your variable.
final form = _formKey.currentState;
if (form.validate()) {
form.save();
}
Now here in the above code form.validate() will invoke the validator and form.save() will invoke onSaved property of the TextFormField
Here is the complete code:
import 'package:flutter/material.dart';
class AddPage extends StatefulWidget {
AddPage({Key? key}) : super(key: key);
#override
_AddPageState createState() =>
_AddPageState();
}
class _AddPageState extends State<AddPage> {
final _formKey = GlobalKey<FormState>();
String savedProjectName;
String savedProjectDescription;
final projectNameController = TextEditingController();
final projectDescriptionController = TextEditingController();
double logicalHeight; //your logical height
double logicalWidth; //your logical widgth
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: BackButton(color: Colors.white),
title: Text(CREATEPROJECT),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.check,
color: Colors.white,
),
onPressed: () {
final form = _formKey.currentState;
if (form.validate()) {
form.save();
}
},
),
],
),
body:Form(
key: _formKey,
child: Column(children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.02, 0, 0),
child: Center(
child: Container(
height: logicalHeight * 0.05,
width: logicalWidth * 0.9,
child: TextFormField(
controller: projectNameController,
decoration: InputDecoration(
hintText: PROJECTNAME,
border: UnderlineInputBorder()),
maxLength: 30,
validator: (value) {
if (value.isEmpty) {
return 'This is a required field';
}
return null;
},
onSaved: (value) => savedProjectName = value),
)),
),
Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
child: ElevatedButton(
child: Text(PICKIMAGES),
onPressed: loadAssets,
)),
Center(
child: Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
child: getWidget())),
Padding(
padding: EdgeInsets.fromLTRB(
logicalWidth * 0.05, distanceFromImages, logicalWidth * 0.05, 0),
child: Center(child: Container(child: TextFormField(
controller:projectDescriptionController ,
decoration: InputDecoration(
hintText: PROJECTDESCRIPTION,
border: UnderlineInputBorder()),
minLines: 1,
maxLines: 5,
maxLength: 5000,
validator: (value) {
if (value.isEmpty) {
return 'This is a required field';
}
return null;
},
onSaved: (value) => savedProjectDescription = value),)),
),
]),
),
);
}
}
I have a main widget called DashboardWidget. Inside it, I have a Scaffold with BottomNavigationBar and a FloatingActionButton:
Now, I want to make a widget that would be dragged from the bottom by:
Swiping up with the finger.
Pressing on FloatingActionButton.
In other words, I want to expand the BottomNavigationBar.
Here's a design concept in case I was unclear.
The problem is, I'm not sure where to start to implement that. I've thought about removing the BottomNavigationBar and create a custom widget that can be expanded, but I'm not sure if it's possible either.
Output:
I used a different approach and did it without AnimationController, GlobalKey etc, the logic code is very short (_handleClick).
I only used 4 variables, simple and short!
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static double _minHeight = 80, _maxHeight = 600;
Offset _offset = Offset(0, _minHeight);
bool _isOpen = false;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFF6F6F6),
appBar: AppBar(backgroundColor: Color(0xFFF6F6F6), elevation: 0),
body: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child: FlatButton(
onPressed: _handleClick,
splashColor: Colors.transparent,
textColor: Colors.grey,
child: Text(_isOpen ? "Back" : ""),
),
),
Align(child: FlutterLogo(size: 300)),
GestureDetector(
onPanUpdate: (details) {
_offset = Offset(0, _offset.dy - details.delta.dy);
if (_offset.dy < _HomePageState._minHeight) {
_offset = Offset(0, _HomePageState._minHeight);
_isOpen = false;
} else if (_offset.dy > _HomePageState._maxHeight) {
_offset = Offset(0, _HomePageState._maxHeight);
_isOpen = true;
}
setState(() {});
},
child: AnimatedContainer(
duration: Duration.zero,
curve: Curves.easeOut,
height: _offset.dy,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 10)]),
child: Text("This is my Bottom sheet"),
),
),
Positioned(
bottom: 2 * _HomePageState._minHeight - _offset.dy - 28, // 56 is the height of FAB so we use here half of it.
child: FloatingActionButton(
child: Icon(_isOpen ? Icons.keyboard_arrow_down : Icons.add),
onPressed: _handleClick,
),
),
],
),
);
}
// first it opens the sheet and when called again it closes.
void _handleClick() {
_isOpen = !_isOpen;
Timer.periodic(Duration(milliseconds: 5), (timer) {
if (_isOpen) {
double value = _offset.dy + 10; // we increment the height of the Container by 10 every 5ms
_offset = Offset(0, value);
if (_offset.dy > _maxHeight) {
_offset = Offset(0, _maxHeight); // makes sure it does't go above maxHeight
timer.cancel();
}
} else {
double value = _offset.dy - 10; // we decrement the height by 10 here
_offset = Offset(0, value);
if (_offset.dy < _minHeight) {
_offset = Offset(0, _minHeight); // makes sure it doesn't go beyond minHeight
timer.cancel();
}
}
setState(() {});
});
}
}
You can use the BottomSheet class.
Here is a Medium-tutorial for using that, here is a youtube-tutorial using it and here is the documentation for the class.
The only difference from the tutorials is that you have to add an extra call method for showBottomSheet from your FloatingActionButton when it is touched.
Bonus: here is the Material Design page on how to use it.
You can check this code, it is a complete example of how to start implementing this kind of UI, take it with a grain of salt.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:rxdart/rxdart.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Orination Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
bool _isOpen;
double _dragStart;
double _hieght;
double _maxHight;
double _currentPosition;
GlobalKey _cardKey;
AnimationController _controller;
Animation<double> _cardAnimation;
#override
void initState() {
_isOpen = false;
_hieght = 50.0;
_cardKey = GlobalKey();
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 700));
_cardAnimation = Tween(begin: _hieght, end: _maxHight).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.addListener(() {
setState(() {
_hieght = _cardAnimation.value;
});
});
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: Colors.transparent,
titleSpacing: 0.0,
title: _isOpen
? MaterialButton(
child: Text(
"Back",
style: TextStyle(color: Colors.red),
),
onPressed: () {
_isOpen = false;
_cardAnimation = Tween(begin: _hieght, end: 50.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.forward(from: 0.0);
},
)
: Text(""),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.keyboard_arrow_up),
onPressed: () {
final RenderBox renderBoxCard = _cardKey.currentContext
.findRenderObject();
_maxHight = renderBoxCard.size.height;
_cardAnimation = Tween(begin: _hieght, end: _maxHight).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.forward(from: 0.0);
_isOpen = true;
}),
body: Stack(
key: _cardKey,
alignment: Alignment.bottomCenter,
children: <Widget>[
Container(
width: double.infinity,
height: double.infinity,
color: Colors.black12,
),
GestureDetector(
onPanStart: _onPanStart,
onPanUpdate: _onPanUpdate,
onPanEnd: _onPanEnd,
child:Material(
borderRadius: BorderRadius.only(
topRight: Radius.circular(16.0),
topLeft: Radius.circular(16.0),
),
elevation: 60.0,
color: Colors.white,
// shadowColor: Colors.,
child: Container(
height: _hieght,
child: Center(
child: Text("Hello, You can drag up"),
),
),
),
),
],
),
);
}
void _onPanStart(DragStartDetails details) {
_dragStart = details.globalPosition.dy;
_currentPosition = _hieght;
}
void _onPanUpdate(DragUpdateDetails details) {
final RenderBox renderBoxCard = _cardKey.currentContext.findRenderObject();
_maxHight = renderBoxCard.size.height;
final hieght = _currentPosition - details.globalPosition.dy + _dragStart;
print(
"_currentPosition = $_currentPosition _hieght = $_hieght hieght = $hieght");
if (hieght <= _maxHight && hieght >= 50.0) {
setState(() {
_hieght = _currentPosition - details.globalPosition.dy + _dragStart;
});
}
}
void _onPanEnd(DragEndDetails details) {
_currentPosition = _hieght;
if (_hieght <= 60.0) {
setState(() {
_isOpen = false;
});
} else {
setState(() {
_isOpen = true;
});
}
}
}
Edit: I modified the code by using Material Widget instead of A container with shadow for better performance,If you have any issue, please let me know .
I want to show a custom toast (my own widget layout). I know how to show a custom alert dialogue, but that's not what I want.
Because, Alert dialogue:
Has a black background
Prevents touches when it's shown
Has to dismiss manually
I don't want to use flutter toast library because I can't make a custom layout with that.
I want to show my own layout on top of all other widgets and make it disappear after some time. Also, it should not prevent any input when it's shown.
You can add this library to add and customize your own toasts.
Widget widget = Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(30.0),
child: Container(
width: 40.0,
height: 40.0,
color: Colors.grey.withOpacity(0.3),
child: Icon(
Icons.add,
size: 30.0,
color: Colors.green,
),
),
),
);
ToastFuture toastFuture = showToastWidget(
widget,
duration: Duration(seconds: 3),
onDismiss: () {
print("the toast dismiss"); // the method will be called on toast dismiss.
},
);
custom toast
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class Toast {
static void show(
String msg,
BuildContext context) {
Color textColor = Colors.white;
Color backgroundColor = Colors.blueAccent;
dismiss();
Toast._createView(msg, context, backgroundColor, textColor);
}
static OverlayEntry _overlayEntry;
static bool isVisible = false;
static void _createView(
String msg,
BuildContext context,
Color background,
Color textColor,
) async {
var overlayState = Overlay.of(context);
final themeData = Theme.of(context);
_overlayEntry = new OverlayEntry(
builder: (BuildContext context) => _ToastAnimatedWidget(
child: Container(
width: MediaQuery.of(context).size.width,
child: Container(
alignment: Alignment.center,
width: MediaQuery.of(context).size.width,
child: Container(
decoration: BoxDecoration(
color: background,
borderRadius: BorderRadius.circular(20),
),
margin: EdgeInsets.symmetric(horizontal: 20),
padding: EdgeInsets.fromLTRB(16, 10, 16, 10),
child: Text(
msg,
softWrap: true,
style: themeData.textTheme.body1.copyWith(
fontFamily: 'intel',
color: Colors.white,
),
),
),
),
),
),
);
isVisible = true;
overlayState.insert(_overlayEntry);
}
static dismiss() async {
if (!isVisible) {
return;
}
isVisible = false;
_overlayEntry?.remove();
}
}
class _ToastAnimatedWidget extends StatefulWidget {
_ToastAnimatedWidget({
Key key,
#required this.child,
}) : super(key: key);
final Widget child;
#override
_ToastWidgetState createState() => _ToastWidgetState();
}
class _ToastWidgetState extends State<_ToastAnimatedWidget>
with SingleTickerProviderStateMixin {
bool get _isVisible => true; //update this value later
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Positioned(
bottom: 50,
child: AnimatedOpacity(
duration: Duration(seconds: 2),
opacity: _isVisible ? 1.0 : 0.0,
child: widget.child,
)
);
}
}
for call
Toast.show(ApiContent.something_wrong, context);
I have created my custom stateful widget and I am taking a boolean that I use to check the state of the AnimatedContainer. Also, there's a function in createState to check the state of the AnimatedContainer and change the width of the container. My problem is that I am trying to use the function _handleTap() in AnimatedContainer as I child but it gives me an error saying that the expression has a type of void therefore, can't be used.
class SectionTaps extends StatefulWidget {
SectionTaps(this.isActive);
bool isActive = false;
_SectionTapsState createState() => _SectionTapsState();
}
class _SectionTapsState extends State<SectionTaps> {
bool _isActive = false;
double _width = 255.0;
void _handleTap(){
setState(() {
_isActive = widget.isActive;
_isActive == true ? _width = 55.0 : _width = 255.0;
//change container width
});
}
final leftButton = new AnimatedContainer(
duration: Duration(seconds: 1),
height: 88.0,
width: 255.0,
decoration: new BoxDecoration(
color: new Color(0xFF376480),
shape: BoxShape.rectangle,
borderRadius: new BorderRadius.only(
topRight: Radius.circular(80.0),
bottomRight: Radius.circular(80.0),
),
boxShadow: <BoxShadow>[
new BoxShadow(
color: Colors.black12,
blurRadius: 10.0,
offset: new Offset(0.0, .0),
),
],
),
child: _handleTap(),
);
You need to Wrap your - AnimatedContainer with GestureDetector & use onTap: to call setSate().
Code:
class SectionTaps extends StatefulWidget {
SectionTaps(this.isActive);
bool isActive = false;
_SectionTapsState createState() => _SectionTapsState();
}
class _SectionTapsState extends State<SectionTaps> {
bool _isActive = false;
double _width = 255.0;
void _handleTap() {
setState(() {
print ('Set State Called.');
_isActive = widget.isActive;
_isActive == true ? _width = 55.0 : _width = 255.0;
//change container width
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: GestureDetector(
onTap: () {
_handleTap();
},
child: new AnimatedContainer(
duration: Duration(seconds: 1),
height: 88.0,
width: 255.0,
decoration: new BoxDecoration(
color: new Color(0xFF376480),
shape: BoxShape.rectangle,
borderRadius: new BorderRadius.only(
topRight: Radius.circular(80.0),
bottomRight: Radius.circular(80.0),
),
boxShadow: <BoxShadow>[
new BoxShadow(
color: Colors.black12,
blurRadius: 10.0,
offset: new Offset(0.0, .0),
),
],
),
),
),
),
);
}
}
I am trying to create a list of dismissible cards inside a customscrollview. The cards are getting rendered, but when i swipe the cards to dismiss them , they don't get removed from the list. Below is the code. Please help.
CustomScrollView customScroll = new CustomScrollView(
slivers: <Widget>[
new SliverAppBar(
backgroundColor: Colors.black,
automaticallyImplyLeading: false,
expandedHeight: 90.0,
title: new Text("Test"),
),
new SliverFixedExtentList(
itemExtent: 128.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
return new Dismissible(key: new ObjectKey(objects[index]),
child: widget.widgetAdapter(objects[index]),
onDismissed: (DismissDirection direction) {
setState(() {
this.objects.removeAt(index);
this.reIndex();
});
direction == DismissDirection.endToStart ? print(
"favourite") : print("remove");
},
background: new Container(
color: const Color.fromRGBO(183, 28, 28, 0.8),
child: const ListTile(
leading: const Icon(
Icons.delete, color: Colors.white, size: 36.0)
)
),
secondaryBackground: new Container(
color: const Color.fromRGBO(0, 96, 100, 0.8),
child: const ListTile(
trailing: const Icon(
Icons.favorite, color: Colors.white, size: 36.0)
)
),
);
},
childCount: objects.length,
),
),
]
);
your attempt is basically correct - I have simplified list creation and replaced it in your sample code below - what you are looking for is in the dmiss function # line 35;
import 'package:flutter/material.dart';
class TestDismissCSV extends StatefulWidget {
#override
_TestDismissCSVState createState() => new _TestDismissCSVState();
}
class _TestDismissCSVState extends State<TestDismissCSV> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: "Dismiss in Cust Scroll V",
theme: new ThemeData(brightness: Brightness.dark),
home: new Scaffold(
body: dmiss(context),
),
);
}
List<TheListClass> _theList;
Widget dmiss(context) {
return new CustomScrollView(slivers: <Widget>[
new SliverAppBar(
backgroundColor: Colors.black,
automaticallyImplyLeading: false,
expandedHeight: 90.0,
title: new Text("Test"),
),
new SliverFixedExtentList(
itemExtent: 128.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
return new Dismissible(
key: new ObjectKey(_theList[index]),
child: new Material(child: new Text(_theList[index].title)),
onDismissed: (DismissDirection direction) {
setState(() {
this._theList.removeAt(index);
//this.reIndex();
});
direction == DismissDirection.endToStart
? print("favourite")
: print("remove");
},
background: new Container(
color: const Color.fromRGBO(183, 28, 28, 0.8),
child: const ListTile(
leading: const Icon(Icons.delete,
color: Colors.white, size: 36.0))),
secondaryBackground: new Container(
color: const Color.fromRGBO(0, 96, 100, 0.8),
child: const ListTile(
trailing: const Icon(Icons.favorite,
color: Colors.white, size: 36.0))),
);
},
childCount: _theList.length,
),
),
]);
}
#override
void initState() {
super.initState();
_theList = new List<TheListClass>();
for (var i = 0; i < 100; i++) {
_theList.add(new TheListClass('List Item ' + i.toString()));
}
}
#override
void dispose() {
super.dispose();
}
}
class TheListClass {
String title;
TheListClass(this.title);
}
List Item dismissed
Happy coding!