I have an app with a feature that allows users to upload images, and later these images are used at various points through the app. I use imagePicker to get the images, with the following method:
onTap: () async {
ImagePicker _picker = ImagePicker();
final XFile? _image = await _picker.pickImage(
source: ImageSource.gallery,
imageQuality: 50,
);
if (_image == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('No image was selected.')));
}
if (_image != null) {
print('Uploading ...');
Container(
child: Center(child: CircularProgressIndicator()),
);
StorageRepository().updateImage(user, _image, index);
}
},
One example of a widget with weird image resizing is the following:
SizedBox(
height: MediaQuery.of(context).size.height / 1.4,
width: MediaQuery.of(context).size.width,
child: Stack(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: Colors.grey,
offset: Offset(3, 3),
blurRadius: 3,
spreadRadius: 3)
],
image: DecorationImage(
fit: BoxFit.fill,
image: NetworkImage(voteUser!
.imageUrls[state.imageUrlIndex]),
),
),
),
...
),
),
In this image, you can see the guy is stretched out, making the photo look weird.
My question is, how can I make an image that consistently looks good, but will not overflow the screen? I recognize cropping the image during the upload can help, however i would then have to set widgets with variable widths and heights rather than using mediaQuery, which runs the risk of overflowing the screen. Any ideas? thanks!
use Boxfit.cover it will work.
fit: BoxFit.cover
Related
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.
I need a button to delete the currently viewed image. The image paths are stored in a List<String>.
The images appear via CarouselSlider from ImagePicker.
I've tried to see if I can delete the image via a hardcoded index with .removeAt() and that works but again, can't figure how to get current image
What is right way to grab the current index of the on screen image? I've tried with single images and it works find but getting the current index of the currently viewed photo is where I'm running into trouble.
This is how I am getting my Images:
List<String> pickedImageList = [];
final imagePicker = ImagePicker();
Future pickImage() async {
final userPickedImages = await imagePicker.pickMultiImage();
for (var image in userPickedImages!) {
pickedImageList.add(image.path);
}
setState(() {});
}
This is how I am showing the images. Button to delete currently selected photo is at the bottom with generic function:
Column(
children: [
ElevatedButton(
onPressed: pickImage,
child: const Text('Add another image')),
CarouselSlider(
options: CarouselOptions(
// height: 200,
viewportFraction: .9,
enlargeCenterPage: true,
),
items: pickedImageList
.map(
(item) => SizedBox(
width: 200,
height: 200,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.asset(
item,
height: 200,
width: 200,
)),
),
// color: Colors.green,
)
.toList(),
),
ElevatedButton(
onPressed: () {
setState(() {
removeImage();
});
},
child: const Text('Remove'))
],
),
All of the delete buttons that people have solved on SO all involve single images which is not the problem.
I thought maybe a GestureDetector counting up and down with left and right swipes, resetting the counter when it reaches the length of the list. Negatives would be multiplied by it self to get current index usable.
I have a container in my flutter project that loads the image selected by the flutter image_picker package. If the value is null, it load a placeholder image and text that says to upload an image.
GestureDetector(
onTap: () async {
setState(() {
_pickImage();
});
},
child: photo != null
? Image.file(
File(
photo!.path.toString(),
),
)
: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: const Color(0xff303434),
),
height: 200,
width: double.infinity,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/images/camera.png'),
Text(
'Upload Image',
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 15,
),
),
],
),
),
),
),
The function to pick the image is:
void _pickImage() async {
setState(() async {
final ImagePicker _picker = ImagePicker();
photo = await _picker.pickImage(
source: ImageSource.camera,
imageQuality: 90,
);
if (photo == null) return;
});
}
The issue is that when I select an image, it will only display in the container when I hot reload my project, and if I choose another image after, I need to hot restart to update the image in the container. How do I fix this issue and make the image in the container upload automatically when I select and image, rather than having to hot restart?
move everything out the setState and call set state after it, with nothing in it, not with async :D
You need to move all the code out of setState, and remove async.
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.
How can I assign an image dynamically in flutter ? For example:
final topContent = Stack(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 10.0),
height: MediaQuery.of(context).size.height * 0.5,
decoration: new BoxDecoration(
image: new DecorationImage(
image: new AssetImage(lesson.imagePath),
fit: BoxFit.cover,
),
)),
Container(
height: MediaQuery.of(context).size.height * 0.5,
padding: EdgeInsets.all(40.0),
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(color: Color.fromRGBO(58, 66, 86, .9)),
child: SingleChildScrollView(
controller: _scrollController,
child: Center(
child: topContentText,
),
),
),
Positioned(
left: 8.0,
top: 60.0,
child: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back, color: Colors.white),
),
)
],
);
Now the image at the start lesson.imagePath is what I want to change dynamically. I tried to use setState() but it gives me an error:
The expression here is a type of void and cannot be used
image: setState ((){
if (someCondition) {
return new AssetImage(lesson.imagePath);
}
}),
Your setState call is wrong! The most simple way is make your image as state of your widget and update this image inside a setState call. setState method does not returns nothing it just rebuilds your widget.
In your _WidgetState class you declare as member:
AssetImage _imageToShow;
You can provider a initial image inside initState method.
#override
initState(){
_imageToShow = AssetImage('youAssetImage');
}
Your Container widget should be declared as:
Container(
padding: EdgeInsets.only(left: 10.0),
height: MediaQuery.of(context).size.height * 0.5,
decoration: new BoxDecoration(
image: new DecorationImage(
image: _imageToShow,
fit: BoxFit.cover,
),
)),
),
And to update your image with setState call you just need:
void updateImage() {
setState ((){
if (someCondition) {
_imageToShow = new AssetImage(lesson.imagePath);
}
});
}
But remember that something has to call updateImage method.
The above solution can work also you can set an array of names and you can set the same image name in the assets folder and you can dynamically select the image where you want to use.
suppose in your case you have a list of lessons.
var lesson = ['a','b','c'];
In assets, folder give the same name to the images. (Don't forget to update the pubspec.yaml file)
Then in the AssetImage you can give the path which can be chosen dynamically.
image:AssetImage('assets/${lesson[index]}.jpg')
Remember to give the same name to the image like here a,b and c to the image.
Also the same extension to be given like here .jpg
image:AssetImage('assets/${lesson[index]}.jpg')