Flutter how to save scrolled image position - flutter

The images are too large to be a lock screen wallpaper, so I want the user to select the desired part by swiping left and right.
I am using InteractiveViewer to scroll left and right.
How can I make the scrolled image as wallpaper when the user presses the set as wallpaper button?
----------->
(wallpaper when I press the button) ----------- (wallpaper which I want to set)
Here is my widget tree:
Stack(
children: <Widget>[
Hero(
tag: widget.heroId,
child: Container(
alignment: Alignment.center,
child: InteractiveViewer(
maxScale: 5.0,
constrained: false,
child: FadeInImage(
height: MediaQuery.of(context).size.height,
placeholder: AssetImage('assets/images/loading-5.gif'),
image: NetworkImage(widget.imageUrl),
fit: BoxFit.cover,
),
),
),
),
Positioned(
right: 16.0,
top: 0.0,
child: ElevatedButton(
onPressed: () {
_setwallpaper(WallpaperManagerFlutter.LOCK_SCREEN);
},
child: Text("Set as Wallpaper")),
),
],
)
Here is set as wallpaper function:
Future<void> _setwallpaper(location) async {
var file = await DefaultCacheManager().getSingleFile(widget.imageUrl);
try {
WallpaperManagerFlutter().setwallpaperfromFile(file, location);
GlobalSnackBar.show(context, 'Wallpaper updated');
} catch (e) {
GlobalSnackBar.show(context, 'Error Setting Wallpaper');
print(e);
}
}

Related

How to save an edited image in flutter (Edits on the image was done using ColorFiltered)?

I'm new to flutter and trying to build an editing app using ColorFiltered Class in flutter the changes are reflected while editing the image but not reflected when I try to save the image to the gallery (Only the unedited image is getting saved).
Below is the code which helps to display the image while editing.
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
height: MediaQuery.of(context).size.width,
width: MediaQuery.of(context).size.width,
child: AspectRatio(
aspectRatio: 1,
child: buildImage(),
),
),
],
)
),
Below is the code inside the buildImage() function which is called being called.
Widget buildImage() {
return ColorFiltered(
colorFilter: ColorFilter.matrix(calculateContrastMatrix(con)),
child: ColorFiltered(
colorFilter: ColorFilter.matrix(calculateSaturationMatrix(sat)),
child: Image.file(
_imageFile,
height: 400,
width: 250,),
),
);
}
And below is the code where I'm trying to savw the image.
TextButton(
child: const Text('Save Image'),
onPressed: () async {
final imageBytes = await _imageFile.readAsBytes();
final result = await ImageGallerySaver.saveImage(imageBytes);
},
Note I'm trying to update the _imageFile using setState.
void initState() {
super.initState();
_imageFile = File(widget.image.path);
}
Thank you.
I want the changes made through ColorFiltered class to be reflected on the saved image also.

Flutter many aligned buttons in a stack don't work

Hi I'm currently stacking buttons in flutter using
Transform.scale(
scale: 0.5,
child: Stack(
children: List.generate(data.length, (int index) {
Alignment alignment = alignments[index];
String message = messages[index];
return Align(
alignment: Alignment(
alignment.x,
alignment.y
),
child: SizedBox(
key: UniqueKey(),
width: 100.0,
height: 100.0,
child: MaterialButton(
key: UniqueKey(),
onPressed: () {
print("Specific message: "+message.toString());
},
child: Container(
width: 100.0,
height: 100.0,
color: Colors.pink,
child: Text("message: "+message.toString()),
),
),
),
);
}),
),
)
However this only works for the first few handful of widgets, the later widgets no longer print the message or register the click.
====
Side note I have just tested where alignment is Alignment(0.0, 0.0) and the top button works. However, when it has alignment away from the centre it doesn't work.
====
I have a suspicion if a button is built initially off screen you won't be able to press it even if you scale the whole screen.
To fix the problem all the buttons need to be on the screen at scale: 1.0

Putting a CircularProgressIndicator below each image

There are a lot of such stack elements on the page:
Stack(
children: <Widget>[
SizedBox(
height: 150,
width: 150,
child: Center(child: CircularProgressIndicator()),
),
Center(child: _getImage(imagePath)),
],
),
So there will be a CircularProgressIndicator below each image. Does it reduce the ui speed? When the Progress Indicator widget is ovelapped by the other widget will be disabled then?
First off you may use const on the constructor for better performance. Secondly, if you want to show a progress indicator when the image is loading maybe try using a FutureBuilder to check if the image is loading, if so return a CircularProgressIndicator else return the image.
But generally speaking flutter should be able to render tons of CircularProgressIndicator and yes it will still render the CircularProgressIndicator even if your image is shown and will therefore take up resources. If you dont want it to show and take resources you could warap it in a Visablity widget but I highly recommend using a futurebuilder so you can maintain the state of the image loading.
I think you are using progress loaders for image loading state, but I'd go with a package.
extended_image: ^4.1.0 seems like a good one and check the code below. It allows you to interfere in Loading State of the image, and you can put whatever widget in place of that state. This package does more than your intended use as well.
ExtendedImage.network(
url,
width: ScreenUtil.instance.setWidth(600),
height: ScreenUtil.instance.setWidth(400),
fit: BoxFit.fill,
cache: true,
loadStateChanged: (ExtendedImageState state) {
switch (state.extendedImageLoadState) {
case LoadState.loading:
_controller.reset();
return Image.asset(
"assets/loading.gif",
fit: BoxFit.fill,
);
break;
///if you don't want override completed widget
///please return null or state.completedWidget
//return null;
//return state.completedWidget;
case LoadState.completed:
_controller.forward();
return FadeTransition(
opacity: _controller,
child: ExtendedRawImage(
image: state.extendedImageInfo?.image,
width: ScreenUtil.instance.setWidth(600),
height: ScreenUtil.instance.setWidth(400),
),
);
break;
case LoadState.failed:
_controller.reset();
return GestureDetector(
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Image.asset(
"assets/failed.jpg",
fit: BoxFit.fill,
),
Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
child: Text(
"load image failed, click to reload",
textAlign: TextAlign.center,
),
)
],
),
onTap: () {
state.reLoadImage();
},
);
break;
}
},
)

Flutter- Image picker package: show images one after another with delete action

In my Flutter pr project, I am using Image Picker plugin to select images from the android mobile gallery or capture images with the camera and show them one after another with a delete icon below each image. On tapping the RaisedButton for selecting images from the gallery, the method imageSelectorGallery() is called. There inside the setState() method , I add a SizedBox and a delete icon to the List namely images_captured. I expect the images_captured to be rendered inside the Column in SingleChildScrollView.
But after selecting an image from the gallery, nothing happens. I also want to tap on the delete icon and remove the image above it. But flutter has no data binding mechanism as I know to correlate the image with the delete button.
Code follows:
class PrescriptionScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new UserOptionsState();
}
}
class UserOptionsState extends State<PrescriptionScreen> {
//save the result of gallery fileUserOptions
File galleryFile;
//save the result of camera file
File cameraFile;
#override
Widget build(BuildContext context) {
var images_captured=List<Widget>();
//display image selected from gallery
imageSelectorGallery() async {
galleryFile = await ImagePicker.pickImage(
source: ImageSource.gallery,
// maxHeight: 50.0,
// maxWidth: 50.0,
);
print("You selected gallery image : " + galleryFile.path);
setState(() {
var sized_box_indiv= new SizedBox(
height: 200.0,
width: 300.0,
//child: new Card(child: new Text(''+galleryFile.toString())),
//child: new Image.file(galleryFile),
child: galleryFile == null
? new Text('Sorry nothing selected from gallery!!')
: new Image.file(galleryFile),
);
images_captured.add(sized_box_indiv);
var delete_button = IconButton(icon: Icon(Icons.delete), onPressed: () {});
images_captured.add(delete_button);
});
}
//display image selected from camera
imageSelectorCamera() async {
cameraFile = await ImagePicker.pickImage(
source: ImageSource.camera,
//maxHeight: 50.0,
//maxWidth: 50.0,
);
print("You selected camera image : " + cameraFile.path);
setState(() {});
}
return new SingleChildScrollView(
child:Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
new RaisedButton(
child: new Text('Select Image from Gallery'),
onPressed: imageSelectorGallery,
),
new RaisedButton(
child: new Text('Select Image from Camera'),
onPressed: imageSelectorCamera,
),
Column(
children: images_captured
),
],
),
);
/* },
),
);*/
}
}
How to show the images selected from gallery one after another with a delete icon button below each of them?
How to remove the corresponding image on tapping the delete icon button?
I think if I can accomplish it for gallery, I can do it for camera capturing as well.
I used the answer by jJuice and the images after being selected showed overflow error. The screenshot is given below:
My code is:
class UserOptionsState extends State<PrescriptionScreen> {
//save the result of gallery fileUserOptions
File galleryFile;
//save the result of camera file
File cameraFile;
var images_captured=List<Widget>();
List<File> images = List<File>();
#override
Widget build(BuildContext context) {
//display image selected from gallery
imageSelectorGallery() async {
galleryFile = await ImagePicker.pickImage(
source: ImageSource.gallery,
// maxHeight: 50.0,
// maxWidth: 50.0,
);
images.add(galleryFile);
print("You selected gallery image : " + galleryFile.path);
setState(() {
});
}
//display image selected from camera
imageSelectorCamera() async {
cameraFile = await ImagePicker.pickImage(
source: ImageSource.camera,
//maxHeight: 50.0,
//maxWidth: 50.0,
);
print("You selected camera image : " + cameraFile.path);
setState(() {});
}
return new SingleChildScrollView(
child:Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
new RaisedButton(
child: new Text('Select Image from Gallery'),
onPressed: imageSelectorGallery,
),
new RaisedButton(
child: new Text('Select Image from Camera'),
onPressed: imageSelectorCamera,
),
new Container(
// new Column(
// children: <Widget>[
height: 1200,
child:GridView.count(
crossAxisSpacing: 6,
mainAxisSpacing: 6,
crossAxisCount: 3,
children: List.generate(images.length, (index) {
return Column(
children: <Widget>[
Container(
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
child: ClipRRect(
child: Image.file(images[index], fit: BoxFit.cover),
borderRadius: BorderRadius.circular(10),
)
),
GestureDetector(
onTap: () {
setState(() {
images.removeAt(index);
});
},
child: Padding(
padding: const EdgeInsets.all(3.0),
child: Align(
alignment: Alignment.bottomCenter,
child: Icon(Icons.clear, color: Colors.black, size: 20),
),
),
),
]
);
}
),
),
// ]
)
/*displaySelectedFile(galleryFile),
displaySelectedFile(cameraFile)*/
],
),
);
}
Widget displaySelectedFile(File file) {
return new SizedBox(
height: 200.0,
width: 300.0,
//child: new Card(child: new Text(''+galleryFile.toString())),
//child: new Image.file(galleryFile),
child: file == null
? new Text('Sorry nothing selected!!')
: new Image.file(file),
);
}
}
Question 1: You first need to store the images picked by using ImagePicker (or MultiImagePicker plugin) in a collection. Here's an example on how to do that:
List<File> images = List<File>();
images.add(await ImagePicker.pickImage(source: ImageSource.gallery, imageQuality: 20););
When you want to show those images on the screen, you can use several different widgets, like ListView, GridView, Row, Column.
Here's an example where I use a GridView:
child: Container(
height: 1200,
child: GridView.count(
crossAxisSpacing: 6,
mainAxisSpacing: 6,
crossAxisCount: 3,
children: List.generate(images.length, (index) {
return Column(
children: <Widget>[
Container(
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
child: ClipRRect(
child: Image.file(images[index], fit: BoxFit.cover),
borderRadius: BorderRadius.circular(10),
)
),
GestureDetector(
onTap: () {
setState(() {
images.removeAt(index);
});
},
child: Padding(
padding: const EdgeInsets.all(3.0),
child: Align(
alignment: Alignment.bottomCenter,
child: Icon(Icons.clear, color: Colors.white, size: 20),
),
),
),
]
),
}
),
),
I think using a Stack widget works best in this case. A Stack can be used to show Widgets, layered on top of each other. So in this case, a widget showing the image, with a Icon widget on top of it that is the button for your delete action.
Question 2:
You could delete the image by calling the removeAt method available on collections like a List. See the code inside the onTap method of the GestureDetector. By calling setState the page gets rebuild once the image has been deleted.
EDIT
Sorry, I misread your question and see that you want to show a button below the image, instead of on top of it. A Column widget could be used for this purpose. I edited the code accordingly.

SingleChildScrollView not keeping scroll position

I have a SingleChildScrollView widget in my app that contains a Column as a child.
The Column has many children and the last one in the very bottom of the scrolled screen is a StreamBuilder that I use to change a child Image
The issue is that when I tap on the image, the logic of the StreamBuilder works and the image is changed, but then the SingleChildScrollView scrolls a bit up so that the image is not visible and forces the user to scroll down again to be able to see the new loaded image.
Widget _buildScroll() => SingleChildScrollView(
child: Container(
width: 2080,
child: Column(
children: <Widget>[
_buildTopBar(),
_buildMainContent(),
SizedBox(height: 30),
Container(
child: Image.asset(
"assets/images/chart_legend.png",
width: 300,
),
),
SizedBox(height: 30),
Image.asset("assets/images/road_map.png", width: 600),
StreamBuilder<int>(
initialData: 1,
stream: _compareStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data == 1) {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare1.png"),
onTap: () => _compareSubject.add(2),
),
);
} else if (snapshot.data == 2) {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare2.png"),
onTap: () => _compareSubject.add(3),
),
);
} else {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare3.png"),
onTap: () => _compareSubject.add(1),
),
);
}
}
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare1.png"),
onTap: () {},
),
);
}),
],
),
),
);
However, even more weird is that, once I have done tap on all images, they will be showed as expected without scrolling up...meaning that, if there is the second time i tap on a image, the second time the image is replaced the scrolling up in not happening.
The problem here is that the size of your children changes and the SingleChildScrollView cannot handle that.
I think there could be two solutions that might work here:
If you know the sizes of your images before they are loaded, you should enforce it using a SizedBox. This way, the scroll position will stay the same:
SizedBox(
width: 300,
height: 120,
child: StreamBuilder<int>(...),
)
Use ensureVisible that you trigger once the stream builder is updated, which lets you control exactly where the image should be displayed.
You would need to assign a ScrollController to your SingleChildScrollView (controller parameter). Then, you also need a GlobalKey for your StreamBuilder that you want to show (key parameter).
If you have saved instances of the two to variables, you will be able to call the following once your image is loaded:
scrollController.position.ensureVisible(
globalKey.currentContext.findRenderObject(),
alignment: 0.5, // Aligns the image in the middle.
duration: const Duration(milliseconds: 120), // So it does not jump.
);