Rebuilding navigated page with new state - user-interface

I have a widget called Classroom, and in this widget has a button that navigattes you to another page called ButtonPage. It has only one button that triggers a function which affects Classroom's state. It works perfectly. But the problem is that I also want to change LightButton's color with the updated state in Classroom widget. Here is codes.
Classroom
class Classroom extends StatefulWidget {
#override
_ClassroomState createState() => _ClassroomState();
}
class _ClassroomState extends State<Classroom> {
bool isLightOn = false;
final List<String> lightColor = ["red", "green", "blue"];
String currentItem = "red";
void onButtonPress() {
setState(() {
isLightOn = isLightOn;
});
}
void selectItem(String selectedItem) {
setState(() {
currentItem = selectedItem;
});
}
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
padding: EdgeInsets.all(5.0),
child: Column(
children: <Widget>[
LightBulb(
isLightOn: isLightOn,
currentItem: currentItem,
),
Container(
child: MaterialButton(
textColor: Colors.white,
color: Colors.blue,
child: Text("Go to button's page"),
onPressed: () {
Navigator.push(context,
MaterialPageRoute<void>(builder: (BuildContext context) {
return ButtonPage(
isLightOn: isLightOn,
onButtonPress: onButtonPress,
);
}));
},
),
),
LightColorSelector(
selectItem: selectItem,
currentItem: currentItem,
lightColor: lightColor,
),
],
),
);
}
}
ButtonPage
class ButtonPage extends StatelessWidget {
final bool isLightOn;
final VoidCallback onButtonPress;
const ButtonPage({this.isLightOn, this.onButtonPress});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Button Page"),
),
body: Center(
child: Container(
child: LightButton(
isLightOn: isLightOn,
onButtonPress: onButtonPress,
),
)),
);
}
}
LightButton
class LightButton extends StatelessWidget {
final bool isLightOn;
final VoidCallback onButtonPress;
const LightButton({this.isLightOn, this.onButtonPress});
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
padding: EdgeInsets.all(5.0),
child: MaterialButton(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
color: Colors.blue,
textColor: Colors.white,
child:
isLightOn ?? false ? Text("Turn light off") : Text("Turn light on"),
onPressed: () {
onButtonPress();
},
),
);
}
}
When I click MaterialButton which is labeled as Go to button's page, it navigates me to ButtonPage with isLightOn and onButtonPress variables. In LightButton, when onButtonPress is triggered, it rebuild only previous page. That's why Light Button's label isn't changed. How can I make it affected?

Related

Changing the AppBar colour when a new tab is selected (Flutter)

I have an app with a tab bar and to make the UI better, I wanted to make it so that the colour changes when you got to another tab. How do I make it so that when I click or swipe to another tab, let's say the yellow tab, the whole appbar changes to that colour?
Code:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<ColorChange>(
create: (context) => ColorChange(),
child: MaterialApp(
theme: ThemeData.light(),
home: SimpleTab(),
),
);
}
}
class _TestPageState extends State<TestPage> {
TabController controller;
class ColorChange extends ChangeNotifier {
Color color = colors[0];
Color getColor() {
return color;
}
void changeColor() {
color = colors[controller.index];
print(color);
notifyListeners();
}
}
List<Color> colors = const [
Colors.green,
Colors.yellow,
Colors.red,
Colors.blue,
Colors.deepOrange,
Colors.deepPurple,
];
class SimpleTab extends StatefulWidget {
#override
_SimpleTabState createState() => _SimpleTabState();
}
class _SimpleTabState extends State<SimpleTab>
with SingleTickerProviderStateMixin {
Tester tester = Tester();
#override
void initState() {
super.initState();
controller = TabController(length: colors.length, vsync: this);
controller.addListener(ColorChange().changeColor);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Simple Tab Demo"),
backgroundColor: Provider.of<ColorChange>(context).getColor(),
bottom: TabBar(
controller: controller,
tabs: [
Tab(
text: 'Green',
),
Tab(
text: 'Yellow',
),
Tab(
text: 'Red',
),
Tab(
text: 'Blue',
),
Tab(
text: 'Orange',
),
Tab(
text: 'Purple',
),
],
isScrollable: true,
),
),
body: TabBarView(
controller: controller,
children: <Widget>[
Container(
// child: WidgetThing(tester: tester),
),
Container(
// child: WidgetThing(tester: tester),
),
Container(
// child: WidgetThing(tester: tester),
),
Container(
// child: WidgetThing(tester: tester),
),
Container(
// child: WidgetThing(tester: tester),
),
Container(
// child: WidgetThing(tester: tester),
),
],
),
);
}
}
This is just a very simplified demo of my real app. My real app deals with a lot of data fetched from APIs, hence it is probably better if setstate() was not used, because re-building the whole widget may call http requests unnecessarily.
You can try this
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Demo App',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: SimpleTab(),
);
}
}
class CustomTab {
const CustomTab({this.title, this.color});
final String title;
final Color color;
}
class SimpleTab extends StatefulWidget {
#override
_SimpleTabState createState() => _SimpleTabState();
}
class _SimpleTabState extends State<SimpleTab>
with SingleTickerProviderStateMixin {
TabController controller;
List<CustomTab> tabs = const <CustomTab>[
const CustomTab(title: 'Home', color: Colors.deepOrangeAccent),
const CustomTab(title: 'Setting', color: Colors.blueGrey),
const CustomTab(title: 'Map', color: Colors.teal),
];
CustomTab selectedTab;
#override
void initState() {
super.initState();
controller = new TabController(length: tabs.length, vsync: this);
controller.addListener(_select);
selectedTab = tabs[0];
}
void _select() {
setState(() {
selectedTab = tabs[controller.index];
});
}
#override
Widget build(BuildContext context) {
textStyle() {
return new TextStyle(color: Colors.white, fontSize: 30.0);
}
return new Scaffold(
appBar: new AppBar(
title: new Text("Smiple Tab Demo"),
backgroundColor: selectedTab.color,
bottom: new TabBar(
controller: controller,
tabs: tabs
.map((e) => new Tab(
text: e.title,
))
.toList()),
),
body: new TabBarView(
controller: controller,
children: tabs
.map(
(e) => new Container(
color: e.color,
child: new Center(
child: new Text(
e.title,
style: textStyle(),
),
),
),
)
.toList()),
);
}
}

Flutter AnimatedList still janky?

I'm currently using an AnimatedList in my Flutter app and having problems with the way removed list items are animated out. The animation itself works as expected but once the removed item finishes animating, it just disappears causing the other widgets to jump into its place. I had expected the other items to transition into the place of the removed item ...
I tried wrapping my list items with a ScaleTransition but that didn't help - the other list items still do not react to the removed item until it has finished the animation.
This kind of defies the purpose of AnimatedList, right? Or did I do something wrong? The "Widget of the week" video about AnimatedList clearly shows that list items react to newly inserted items by changing their position ...
Here is my code:
#override
Widget build(BuildContext context) {
return AnimatedList(
padding: EdgeInsets.only(top: REGULAR_DIM,
bottom: REGULAR_DIM + kBottomNavigationBarHeight),
initialItemCount: data.length,
itemBuilder: (context, index, animation) {
return MyCustomWidget(
data: data[index],
animation: animation,
disabled: false
);
},
);
}
class MyCustomWidget extends AnimatedWidget {
final MyModel data;
final bool disabled;
MyCustomWidget({
#required this.data,
#required Animation<double> animation,
this.disabled = false
}) : super(listenable: animation);
Animation<double> get animation => listenable;
#override
Widget build(BuildContext context) {
final content = ... ;
return ScaleTransition(
scale: CurvedAnimation(
parent: animation,
curve: Interval(0, 0.25)
).drive(Tween(begin: 0, end: 1)),
child: FadeTransition(
opacity: animation,
child: SlideTransition(
position: animation.drive(
Tween(begin: Offset(-1, 0), end: Offset(0, 0))
.chain(CurveTween(curve: Curves.easeOutCubic))),
child: content,
),
),
);
}
}
And then somewhere in the MyCustomWidget I invoke this function:
void _remove(BuildContext context) async {
final animatedList = AnimatedList.of(context);
// obtain myModel asynchronously
myModel.removeData(data);
animatedList.removeItem(index, (context, animation) => MyCustomWidget(
data: data,
animation: animation,
disabled: true,
), duration: Duration(milliseconds: 350));
}
The key is to trigger two Transitions one SlideTranstion() and another SizeTransition to eliminate to jump when the item is removed
here is some sample code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: Text('Update AnimatedList data')),
body: BodyWidget(),
),
);
}
}
class BodyWidget extends StatefulWidget {
#override
BodyWidgetState createState() {
return new BodyWidgetState();
}
}
class BodyWidgetState extends State<BodyWidget>
with SingleTickerProviderStateMixin {
// the GlobalKey is needed to animate the list
final GlobalKey<AnimatedListState> _listKey = GlobalKey(); // backing data
List<String> _data = ['Horse', 'Cow', 'Camel', 'Sheep', 'Goat'];
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
SizedBox(
height: 400,
child: AnimatedList(
key: _listKey,
initialItemCount: _data.length,
itemBuilder: (context, index, animation) {
return _buildItem(
_data[index],
animation,
);
},
),
),
RaisedButton(
child: Text(
'Insert single item',
style: TextStyle(fontSize: 20),
),
onPressed: () {
_onButtonPress();
},
),
RaisedButton(
child: Text(
'Remove single item',
style: TextStyle(fontSize: 20),
),
onPressed: () {
_removeSingleItems();
},
),
],
);
}
Widget _buildItem(String item, Animation<double> animation, {direction: 0}) {
return (direction == 0)
? SizeTransition(
sizeFactor: animation,
child: Card(
color: Colors.amber,
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
),
),
)
: Stack(
children: [
SizeTransition(
sizeFactor: animation,
child: Card(
color: Colors.transparent,
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
),
),
),
Align(
alignment: Alignment.topCenter,
heightFactor: 0,
child: SlideTransition(
position: animation
.drive(Tween(begin: Offset(-1, 0), end: Offset(0, 0))),
child: Card(
color: Colors.red,
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
),
),
),
),
],
);
}
void _onButtonPress() {
_insertSingleItem();
}
void _insertSingleItem() {
String item = "Pig";
int insertIndex = 2;
_data.insert(insertIndex, item);
_listKey.currentState.insertItem(insertIndex);
}
void _removeSingleItems() {
int removeIndex = 2;
String removedItem = _data.removeAt(removeIndex);
// This builder is just so that the animation has something
// to work with before it disappears from view since the
// original has already been deleted.
AnimatedListRemovedItemBuilder builder = (context, animation) {
// A method to build the Card widget.
return _buildItem(removedItem, animation, direction: 1);
};
_listKey.currentState.removeItem(removeIndex, builder);
}
void _updateSingleItem() {
final newValue = 'I like sheep';
final index = 3;
setState(() {
_data[index] = newValue;
});
}
}
enter code here
You need to test the performance with the release version of your app.

How can I select and upload Multiple images in Flutter

I tried to find how to select and upload multiple images in flutter but most of the plugins are not working or I did not understand them well. I found little application but it selects and upload only one picture. How to change this code that user can select and upload multiple pictures or is there any other alternatives. Please write in details, i am freshman in coding. Thanks in advance.
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
import 'package:mime/mime.dart';
import 'dart:convert';
import 'package:http_parser/http_parser.dart';
import 'package:toast/toast.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: 'Image Upload Demo',
theme: ThemeData(primarySwatch: Colors.pink),
home: ImageInput());
}
}
class ImageInput extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _ImageInput();
}
}
class _ImageInput extends State<ImageInput> {
// To store the file provided by the image_picker
File _imageFile;
// To track the file uploading state
bool _isUploading = false;
String baseUrl = 'http://YOUR_IPV4_ADDRESS/flutterdemoapi/api.php';
void _getImage(BuildContext context, ImageSource source) async {
File image = await ImagePicker.pickImage(source: source);
setState(() {
_imageFile = image;
});
// Closes the bottom sheet
Navigator.pop(context);
}
Future<Map<String, dynamic>> _uploadImage(File image) async {
setState(() {
_isUploading = true;
});
// Find the mime type of the selected file by looking at the header bytes of the file
final mimeTypeData =
lookupMimeType(image.path, headerBytes: [0xFF, 0xD8]).split('/');
// Intilize the multipart request
final imageUploadRequest =
http.MultipartRequest('POST', Uri.parse(baseUrl));
// Attach the file in the request
final file = await http.MultipartFile.fromPath('image', image.path,
contentType: MediaType(mimeTypeData[0], mimeTypeData[1]));
// Explicitly pass the extension of the image with request body
// Since image_picker has some bugs due which it mixes up
// image extension with file name like this filenamejpge
// Which creates some problem at the server side to manage
// or verify the file extension
imageUploadRequest.fields['ext'] = mimeTypeData[1];
imageUploadRequest.files.add(file);
try {
final streamedResponse = await imageUploadRequest.send();
final response = await http.Response.fromStream(streamedResponse);
if (response.statusCode != 200) {
return null;
}
final Map<String, dynamic> responseData = json.decode(response.body);
_resetState();
return responseData;
} catch (e) {
print(e);
return null;
}
}
void _startUploading() async {
final Map<String, dynamic> response = await _uploadImage(_imageFile);
print(response);
// Check if any error occured
if (response == null || response.containsKey("error")) {
Toast.show("Image Upload Failed!!!", context,
duration: Toast.LENGTH_LONG, gravity: Toast.BOTTOM);
} else {
Toast.show("Image Uploaded Successfully!!!", context,
duration: Toast.LENGTH_LONG, gravity: Toast.BOTTOM);
}
}
void _resetState() {
setState(() {
_isUploading = false;
_imageFile = null;
});
}
void _openImagePickerModal(BuildContext context) {
final flatButtonColor = Theme.of(context).primaryColor;
print('Image Picker Modal Called');
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
height: 150.0,
padding: EdgeInsets.all(10.0),
child: Column(
children: <Widget>[
Text(
'Pick an image',
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(
height: 10.0,
),
FlatButton(
textColor: flatButtonColor,
child: Text('Use Camera'),
onPressed: () {
_getImage(context, ImageSource.camera);
},
),
FlatButton(
textColor: flatButtonColor,
child: Text('Use Gallery'),
onPressed: () {
_getImage(context, ImageSource.gallery);
},
),
],
),
);
});
}
Widget _buildUploadBtn() {
Widget btnWidget = Container();
if (_isUploading) {
// File is being uploaded then show a progress indicator
btnWidget = Container(
margin: EdgeInsets.only(top: 10.0),
child: CircularProgressIndicator());
} else if (!_isUploading && _imageFile != null) {
// If image is picked by the user then show a upload btn
btnWidget = Container(
margin: EdgeInsets.only(top: 10.0),
child: RaisedButton(
child: Text('Upload'),
onPressed: () {
_startUploading();
},
color: Colors.pinkAccent,
textColor: Colors.white,
),
);
}
return btnWidget;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Image Upload Demo'),
),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 40.0, left: 10.0, right: 10.0),
child: OutlineButton(
onPressed: () => _openImagePickerModal(context),
borderSide:
BorderSide(color: Theme.of(context).accentColor, width: 1.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.camera_alt),
SizedBox(
width: 5.0,
),
Text('Add Image'),
],
),
),
),
_imageFile == null
? Text('Please pick an image')
: Image.file(
_imageFile,
fit: BoxFit.cover,
height: 300.0,
alignment: Alignment.topCenter,
width: MediaQuery.of(context).size.width,
),
_buildUploadBtn(),
],
),
);
}
}
I used that package:
dependencies:
multi_image_picker: ^4.6.7
Ex:
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:multi_image_picker/multi_image_picker.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Asset> images = List<Asset>();
String _error;
#override
void initState() {
super.initState();
}
Widget buildGridView() {
if (images != null)
return GridView.count(
crossAxisCount: 3,
children: List.generate(images.length, (index) {
Asset asset = images[index];
return AssetThumb(
asset: asset,
width: 300,
height: 300,
);
}),
);
else
return Container(color: Colors.white);
}
Future<void> loadAssets() async {
setState(() {
images = List<Asset>();
});
List<Asset> resultList;
String error;
try {
resultList = await MultiImagePicker.pickImages(
maxImages: 300,
);
} on Exception catch (e) {
error = e.toString();
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
images = resultList;
if (error == null) _error = 'No Error Dectected';
});
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('Plugin example app'),
),
body: Column(
children: <Widget>[
Center(child: Text('Error: $_error')),
RaisedButton(
child: Text("Pick images"),
onPressed: loadAssets,
),
Expanded(
child: buildGridView(),
)
],
),
),
);
}
}
Add dependecy of image_picker:
image_picker: ^0.8.4+3
Then make a method for selectImages():
final ImagePicker imagePicker = ImagePicker();
List<XFile>? imageFileList = [];
void selectImages() async {
final List<XFile>? selectedImages = await
imagePicker.pickMultiImage();
if (selectedImages!.isNotEmpty) {
imageFileList!.addAll(selectedImages);
}
print("Image List Length:" + imageFileList!.length.toString());
setState((){});
}
Create a builder for showing selected Images:
return Scaffold(
appBar: AppBar(
title: Text('Multiple Images'),
),
body: SafeArea(
child: Column(
children: [
ElevatedButton(
onPressed: () {
selectImages();
},
child: Text('Select Images'),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GridView.builder(
itemCount: imageFileList!.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return Image.file(File(imageFileList![index].path),
fit: BoxFit.cover,);
}),
),
),
],
),
));
Complete Source code available in github link...
https://github.com/NishaJain24/multi_image_picker

flutter how to make Hero like animation between widgets on the same page

I have a button with a text, and when I pressed the button, a text widget with the same text is added to the same page.
I'd like to add Hero like animation between them.
I guess what I need is SlideTransition, but I don't know how to slide from one widget position to another widget position.
Is it possible to do? What widget (or class) should I look into?
Here's the code I want to do (but doesn't work since Hero doesn't work on the same page widgets):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> text = [];
String buttonTag = "0";
#override
Widget build(BuildContext context) {
List<Widget> textWidgets = [];
for (int i = 0; i < text.length; ++i) {
textWidgets.add(
Padding(
padding: const EdgeInsets.all(8.0),
child: Hero(tag: "${i}", child: Text(text[i])),
)
);
}
return SafeArea(
child: Scaffold(
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
child: Hero(
tag: buttonTag,
child: Text("abcde${text.length}")),
onPressed: () {
setState(() {
text.add("abcde${text.length}");
buttonTag = "${text.length}";
});
},
)
] + textWidgets,
),
),
),
);
}
}
Not exactly the answer to the question but instead of Hero (if it's possible) you can use AnimatedList to get the same result.
Code snippet
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
final List<Text> _textWidgets = [];
var rng = new Random();
_addItem() {
setState(() {
_listKey.currentState.insertItem(_textWidgets.length,
duration: const Duration(milliseconds: 500));
int id = rng.nextInt(100);
_textWidgets.add(Text('item $id'));
});
}
Widget _buildItem(
BuildContext context, Text item, Animation<double> animation) {
final offsetAnimation = Tween<Offset>(
begin: Offset(1.0, 0.0),
end: Offset(0.0, 0.0),
).animate(animation);
return SlideTransition(
position: offsetAnimation,
child: SizedBox(
height: 50.0,
child: Center(
child: item,
),
),
);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: <Widget>[
RaisedButton(
child: Text("Add Item"),
onPressed: () {
setState(() {
_addItem();
});
},
),
Expanded(
child: AnimatedList(
key: _listKey,
initialItemCount: _textWidgets.length,
itemBuilder: (context, index, animation) {
return _buildItem(context, _textWidgets[index], animation);
},
),
),
],
),
),
);
}
}
I think it's possible by this llibrary which named: LocalHero
Implement your self
The AnimationController, Tween, AnimatedBuilder are key components.
This is a sample and code about this.
Don't use AnimatedController.animate in AnimatedBuilder builder.
evaluate is enough. Because the builder function is called every ticker frame.
Use AnimationController.animate as class member field.
class _AuthorizedState extends State<_Authorized> with SingleTickerProviderStateMixin {
late final _menuAC = AnimationController(vsync: this, duration: 200.ms);
late final isFilterOpen = ValueNotifier(false)..addListener(_handleFilterOpenChanged);
late final filterColor =
ColorTween(begin: context.color.primaryContainer, end: context.color.secondaryContainer)
.animate(_menuAC);
late final filterBorderRadius = Tween<double>(begin: 12, end: 0).animate(_menuAC);
void _handleFilterOpenChanged() {
print(isFilterOpen.value);
if (isFilterOpen.value) {
_menuAC.forward();
} else {
_menuAC.reverse();
}
}
#override
void dispose() {
_menuAC.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AppScaffold(
child: LayoutBuilder(
builder: (context, cons) => Stack(
children: [
AnimatedBuilder(
animation: _menuAC,
builder: (context, child) {
return Positioned(
bottom: Tween(begin: 16.0, end: 0.0).evaluate(_menuAC),
right: Tween(begin: 16.0, end: 0.0).evaluate(_menuAC),
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: const Radius.circular(12.0),
topRight: const Radius.circular(12.0),
bottomLeft:
Radius.circular(Tween<double>(begin: 12, end: 0).evaluate(_menuAC)),
bottomRight:
Radius.circular(Tween<double>(begin: 12, end: 0).evaluate(_menuAC)),
),
child: Container(
width: Tween(begin: 56.0, end: cons.maxWidth).evaluate(_menuAC),
height: Tween(begin: 56.0, end: min(cons.maxHeight * 0.7, 500.0))
.evaluate(_menuAC),
decoration: BoxDecoration(
color: filterColor.value,
),
child: child,
),
),
);
},
child: Material(
elevation: 24,
color: Colors.transparent,
child: InkWell(
onTap: () {
isFilterOpen.value = !isFilterOpen.value;
},
child: const Center(child: Icon(MdiIcons.filter)),
),
),
),
],
),
),
);
}
}

How to make copyable Text Widget in Flutter?

When long tab on Text widget, a tooltip show up with 'copy'. When click on the 'copy' the text content should copy to system clipboard.
The following will copy the text on long tap, but does not show up 'copy', so user will not know, the content is copied to the clipboard.
class CopyableText extends StatelessWidget {
final String data;
final TextStyle style;
final TextAlign textAlign;
final TextDirection textDirection;
final bool softWrap;
final TextOverflow overflow;
final double textScaleFactor;
final int maxLines;
CopyableText(
this.data, {
this.style,
this.textAlign,
this.textDirection,
this.softWrap,
this.overflow,
this.textScaleFactor,
this.maxLines,
});
#override
Widget build(BuildContext context) {
return new GestureDetector(
child: new Text(data,
style: style,
textAlign: textAlign,
textDirection: textDirection,
softWrap: softWrap,
overflow: overflow,
textScaleFactor: textScaleFactor,
maxLines: maxLines),
onLongPress: () {
Clipboard.setData(new ClipboardData(text: data));
},
);
}
}
Since Flutter 1.9 you can use
SelectableText("Lorem ipsum...")
When text is selected the "Copy" context button will appear.
You can use a SnackBar to notify the user about the copy.
Here is a relevant code:
String _copy = "Copy Me";
#override
Widget build(BuildContext context) {
final key = new GlobalKey<ScaffoldState>();
return new Scaffold(
key: key,
appBar: new AppBar(
title: new Text("Copy"),
centerTitle: true,
),
body:
new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new GestureDetector(
child: new Text(_copy),
onLongPress: () {
Clipboard.setData(new ClipboardData(text: _copy));
key.currentState.showSnackBar(
new SnackBar(content: new Text("Copied to Clipboard"),));
},
),
new TextField(
decoration: new InputDecoration(hintText: "Paste Here")),
]),
);
}
EDIT
I was working on something and I did the followin, so I thought of revisiting this answer:
import "package:flutter/material.dart";
import 'package:flutter/services.dart';
void main() {
runApp(new MaterialApp(home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
String _copy = "Copy Me";
#override
Widget build(BuildContext context) {
final key = new GlobalKey<ScaffoldState>();
return new Scaffold(
key: key,
appBar: new AppBar(
title: new Text("Copy"),
centerTitle: true,
),
body:
new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new GestureDetector(
child: new CustomToolTip(text: "My Copyable Text"),
onTap: () {
},
),
new TextField(
decoration: new InputDecoration(hintText: "Paste Here")),
]),
);
}
}
class CustomToolTip extends StatelessWidget {
String text;
CustomToolTip({this.text});
#override
Widget build(BuildContext context) {
return new GestureDetector(
child: new Tooltip(preferBelow: false,
message: "Copy", child: new Text(text)),
onTap: () {
Clipboard.setData(new ClipboardData(text: text));
},
);
}
}
There is also list of properties it in SelectableText to enable option copy, paste, selectAll, cut
child: Center(
child: SelectableText('Hello Flutter Developer',
cursorColor: Colors.red,
showCursor: true,
toolbarOptions: ToolbarOptions(
copy: true,
selectAll: true,
cut: false,
paste: false
),
style: Theme.of(context).textTheme.body2)
),
SelectableText widget
const SelectableText(
this.data, {
Key key,
this.focusNode,
this.style,
this.strutStyle,
this.textAlign,
this.textDirection,
this.showCursor = false,
this.autofocus = false,
ToolbarOptions toolbarOptions,
this.maxLines,
this.cursorWidth = 2.0,
this.cursorRadius,
this.cursorColor,
this.dragStartBehavior = DragStartBehavior.start,
this.enableInteractiveSelection = true,
this.onTap,
this.scrollPhysics,
this.textWidthBasis,
})
SelectableText(
"Copy me",
onTap: () {
// you can show toast to the user, like "Copied"
},
)
If you want to have different styling for text, use
SelectableText.rich(
TextSpan(
children: [
TextSpan(text: "Copy me", style: TextStyle(color: Colors.red)),
TextSpan(text: " and leave me"),
],
),
)
I use Clipboard.setData inside function.
...
child: RaisedButton(
onPressed: (){
Clipboard.setData(ClipboardData(text: "$textcopy"));
},
disabledColor: Colors.blue[400],
child: Text("Copy", style: TextStyle(color: Colors.white),),
),
I created a helper class CopiableText to accomplish my job. Just copy the class from below and put it in your code.
Helper class
copiable_text_widget.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class CopiableText extends StatelessWidget {
final String text;
final String copyMessage;
final Widget child;
CopiableText(this.text, {this.copyMessage, this.child});
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
child: InkWell(
onTap: () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(this.copyMessage ?? 'Copied to clipboard'),
));
Clipboard.setData(new ClipboardData(text: this.text));
},
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 2),
child: this.child ??
Text(
this.text,
style: TextStyle(color: Color(0xFF1E272E), fontSize: 14),
),
),
),
),
);
}
}
Use it in different ways
import 'package:chaincargo_courier/ui/widgets/copiable_text_widget.dart';
import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
// Just straightforward, click to copy
CopiableText('You are awesome'),
// Give a custom confirmation message
CopiableText(
'Asia, Nepal, Biratnagar',
copyMessage: 'Address copied to clipboard',
),
// Set custom child
CopiableText(
'Stunning view of mount everest',
copyMessage: 'Caption copied to clipboard',
child: Column(
children: [
Image.network(
'https://cdn.pixabay.com/photo/2010/11/29/mount-everest-413_960_720.jpg',
errorBuilder: (BuildContext context, Object exception,
StackTrace stackTrace) {
return Text('Cannot load picture');
},
),
Text('Stunning view of mount everest')
],
),
),
],
),
);
}
}
Just use SelectableText
SelectableText(
iosInfo.identifierForVendor.toString(),
),
Support Links and Copy&Paste
If you want to support both Links and Copy&Paste, use the SelectableLinkify widget.
This widget is part of the flutter_linkify package.
SelectableLinkify(
text: "Made by https://cretezy.com\n\nMail: example#gmail.com",
);

Resources