The purpose of this question is to find the best way to display a lot of images (20 or more) from the gallery.
I need to display all the photos on the on the device. As you can see there could be a lot of images to be displayed, and when I try to display them all using different Image.file() widget, My phone (LG G2) gets slow and then the app completely crashes.
I think the Problem Is that I am Loading a lot of 4K Images (More Than 100) on a 5 years old Device. The Images actually displayed on the Screen at the same time are actually around 15, but I need to have something like a gallery, so all the images in a GridView.
So, I don't think it's the best option, maybe there is a function to downscale the image to like 100x100 px. But looking online I couldn't find anything. So, Is there a better way?
I get all the Image Paths and their folder using a Platform Channel to Android.
I get them all in a list like this:
[["Camera", "storage/emulated/0/downloads/1.png"], etc]
This is The UI I got: (Sorry For the Awful design)
There Are three Main components on the Screen, The IconButton With The Two Arrows:
The DropDownMenu and The GridView.
The IconButton requeries all the paths from android. in the List as stated above.
The DropDown Menu has a function on onChanged which creates all the Cards in the List used by gridView.
This is The Funciton:
void _onChanged(String value) {
setState(() {
_currFolder = value;
_photoCards = calculatePhotoCards();
});
}
And the calculatePhotoCards is this:
List<Widget> calculatePhotoCards() {
List<Widget> list = new List();
for (int v = 0; v < _foldersAndPaths.length; v++) {
if (_foldersAndPaths[v][0] == _currFolder) {
list.add(Card(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0))),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: new ClipRRect(
borderRadius: new BorderRadius.circular(8.0),
child: Image.file(
File(_foldersAndPaths[v][1]),
fit: BoxFit.cover,
),
),
),
));
}
}
print(list);
return list;
}
Where _foldersAndPaths is the List containing all the paths which their respective folders (as stated above), while _currFolder is the Selected Folder in DropDownMenu.
You need to add cacheWidth and cacheHeight
example:
Image.network(
shop.shopPic,
width: 46,
height: 46,
cacheWidth: 46,
cacheHeight: 46,
)
I decided to implement this to see if it was something to do with image sizing etc, as there have been a few questions about that in the past for remote images.
When I implemented it, I ran into the exact same problem as you. I thought that flutter did caching of images scaled to a reasonable size but that doesn't seem to be the case. It's actually loading up each image into memory, which is why you're getting a crash. It doesn't take that many images to run out of memory as by the time they're decoded into pixels they're pretty huge.
Here and here are bugs about this (I raised one myself) against flutter.
So unfortunately, I don't think there's a pure flutter way of doing this at the moment. There is a image package on pub that can scale down images but it does it directly in dart code so isn't all that performant.
Until those bugs have been fixed, you may have to look for an android-specific (and iOS-specific if you're interested in that) solution to this. That would entail using the android thumbnail creator to create a bitmap, then either saving that bitmap and passing it's URL back to flutter, or passing the bitmap's bytes directly.
Sorry I don't have a better answer than that!
I'm using CachedNetworkImage and I had an issue with loading multiple Images and the app got to over 3GB ram because of it the solution was
CachedNetworkImage(
height: height,
width: width,
memCacheHeight: height, //add this line
memCacheWidth: width, //add this line
imageUrl: imageUrl,
),
It reduced the memory usage from3GB to 60MB
Related
I have a Zebra CC600 running Android Oreo. https://www.zebra.com/us/en/products/spec-sheets/interactive-kiosks/cc600.html
Screen resolution: 1280x720, Screen size: 5inch
The problem is that everything is way too small. Is there a way with Flutter, to scale the whole app?
I did try MediaQuery.textScaleFactorOf(context)
How to set MediaQuery textScaleFactor of fontSize in MaterialApp theme
But that only sizes the text, but not everything else.
The third photo compares a TC51(top) to the CC600(bottom). The letters of the CC600 are barely readable. I think that the device was not configured correctly by the manufacturer. This is the reason I would like to scale the whole app.
The Flutter app actually looks completely normal. It's scaled to the device's DPI.
BUT if you want to scale the app, here's my advice. (What we do for our production apps, be aware that some steps in the main are skipped on the release..)
The reason we use a different technique is that it's far easier and less messy than the MediaQuery / LayoutBuilder solution (which is awful in my opinion)
The easy solution
In the main.dart put this at the top, before the class so it's globally available
/// Used to scale smaller devices.
/// Font and sizes. Not used everywhere. Used for tweaking smaller device.
double deviceScale = 1.0;
Make this function somewhere
setDeviceScaler() {
// You can tweak this so scale for different devices. For example tiny iPhone SE can by super smaller or big :-)
double deviceHeightInPoints = (window.physicalSize.height / window.devicePixelRatio);
if (deviceHeightInPoints < 700.0) {
deviceScale = 0.90;
}
if (deviceHeightInPoints < 600.0) {
deviceScale = 0.80;
}
print('Device scale: ' + deviceScale.toString());
}
Inside your main app class add this:
void initState() {
super.initState();
setDeviceScaler();
}
Also, add this to your first screen. Because sometimes the window.devicePixelRatio is not initialized properly until the first scaffold widget. You may need to add setDeviceScaler() inside those first screen inits.
Now, anywhere you set TextStyles or Widget dimensions simply call * deviceScale
TextStyle titleStyle({bool darkText = false}) {
return TextStyle(
color: darkText ? AppColors.darkFont : AppColors.lightFont,
fontSize: 20 * deviceScale,
fontWeight: FontWeight.w700,
);
}
I'm trying to increase the speed of my change in text by pressing a button. So my text is a number.toString() and is increasing by 1 (++) every time I click my GestureDetector that I've created. But I'm trying to use the property onLongPress to increase the speed by which my text increases when I hold the button down. But nothing I've tried has worked successfully!
I've scoured the internet for info and all I found was how to slow it down one by one, which is not only the opposite of what I need but also it doesn't continue decreasing in count when I hold down the button, it only decreases the speed at which the button is pressed per number. The code I used was:
import 'package:flutter/scheduler.dart' show timeDilation;
...
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
GestureDetector(onTap: () => age >= 100 ? null : setState(() {
age++; }),
child: Icon(Icons.add_circle_outline, size: 40.0, color:
Colors.white,),
onLongPress: () { setState(() {
timeDilation = 10.0;
weight++;
});
},
), //GestureDetector
I expected the animation time to decrease, but I didn't expect to have to repeat tapping the button each time to allow for this decrease. I really want to increase the animation time and keep it increasing without having to constantly repeat pressing the button.
Although onLongPress in the GestureDetector is used for longer taps, it's still designed for a single press, not for contnual presses. What you're looking for though can be accomplished with the Listener widget, using onPointerDown, which would allow you to continuously process a tap down. There's a simple example here.
The photos captured with the device are big. I want to upload them to my backend server after resizing them (scaling them) to more reasonable sizes (less than 800x800). I hoped to use ImageEditor module's coprImage() function, but running it with a large image results in a OutOfMemory exception. I assume that since the module tries to decode a large image and store it in memory, the app crashes.
What I need is the following:
Input
{
width: 3100,
height: 2500,
uri: content://android/1 (some location in Android device)
}
Output
{
width: 800,
height: 650,
uri: content://android/1/resized (some location in Android device)
}
Then I can grab this uri to send the picture to my backend server, and delete the resized photo from the device.
I assume that I will have to write a NativeModule so I can resize an image without loading a large decoded image into memory. React Native's Image component uses Fresco to handle resizing before rendering them, but I don't think it provides a way to resize an image and temporarily save it in fs.
Any help would be appreciated.
References:
https://developer.android.com/training/displaying-bitmaps/load-bitmap.html
http://frescolib.org/docs/resizing-rotating.html
https://facebook.github.io/react-native/docs/images.html
Memory efficient image resize in Android
Expo library has an image manipulator that can resize and more:
import { ImageManipulator } from 'expo';
...
const manipResult = await ImageManipulator.manipulate(
imageUri,
[{ resize: { width: 640, height: 480 } }],
{ format: 'jpg' }
);
manipResult will return an object with the new uri, width and height.
Find it here:
https://docs.expo.io/versions/latest/sdk/imagemanipulator.html
In my app I use react-native-image-picker which works really well.
If you don't want to use it, have a look into this function source to see how resizing is done.
Cheers.
Did you try with react-native-image-resizer? For me it works pretty well, I'm using it with react-native-camera, it looks like this:
fromCamera() {
let newWidth = 800;
let newHeight = 650;
this.refs.camera.capture()
.then((data) => {
ImageResizer.createResizedImage(data.path, newWidth, newHeight, 'JPEG', 100, rotation)
.then(uri => {
// send to backend
}
})
}
Original image is saving on device with uri: data.path, so there are no problems with memory.
Lets check out this. It will provide you detailed description about your issue for resizing and uploading to backend server.
Incase If you want to send Base64 String you can check the code below
how to install and link with android please check this link
https://github.com/TBouder/react-native-asset-resize-to-base64
// this code is for resizing and pushing to array only
let images = [];
NativeModules.RNAssetResizeToBase64.assetToResizedBase64(
response.uri,
newWidth,
newHeight,
(err, base64) => {
if (base64 != null) {
const imgSources = { uri: base64 };
this.setState({
image: this.state.image.concat(imgSources)
});
this.state.image.map((item, index) => {
images.push(item);
});
if (err) {
console.log(err, "errors");
}
}
}
);
I'm using Nativescript 1.6 and this component https://github.com/bradmartin/nativescript-floatingactionbutton
I would like to have some nice "material design effect" when you click on this button and then navigate to another page. It's a bit hacky but it does the job and looks quite nice (i probably should use fragment transitions with shared elements when i was in native Android world, but im not)
My current attempt:
XML:
<FAB:fab tap="tap"
loaded="buttonLoaded"
row="1"
icon="res://ic_add_white_24dp"
rippleColor="#f1f1f1"
class="fab-button" />
JS:
fabButton.icon = ""; // Error! ResourceNotFoundException
fabButton.animate({
scale: { x: 20, y: 20 },
translate: { x: -200, y: -200 },
duration: 300
}).then(function(){
navigation.goToDetailPage();
fabButton.animate({ // reset button to original state
scale: { x: 1, y: 1 },
translate: { x: 0, y: 0 },
delay: 100,
duration: 0
});
});
2 questions:
How can i remove the icon, just for a nicer effect? "", {}, null are not allowed, should i really create a transparent png for this?
Is there a better way to restore/reset element after an animation?
If the exception is ResourceNotFoundException that looks like a native exception thrown so it doesn't look like you'll be able to set a null value. Your best option is probably an empty .png. I suppose the plugin could check for a null value and bypass the native exception but for now that's not the case. You could make a PR with this feature if you wanted.
As for resetting an element to its original state, what you are doing is what I would do. I don't think there would be a "better" way because once you've changed the state with the initial animation, the native setTranslateX/Y and other methods to run the animation have changed the items location/position/color, etc. So what you are doing is probably the best approach.
I'd like to see this animation if you don't mind, looks like something other devs might want :) Maybe record a .gif and attach here for others to see as a reference. If you don't have a tool to record a .gif here is what I use: http://www.cockos.com/licecap/ when I record from the emulators.
Hope this helps some.
I'm back with tales of frustrating adventures. This time concerning images on the android side of titanium.
Long story short, I can't get any images to show up for android whatsoever, whether it be a background image or a plain image in an imageView object.
I will provide code I'm trying and keep it extremely small and simple so that it can be easily replicated for all of our testing purposes.
The Code:
Attempt #1 programatically creating view and image:
index.js
var header = Ti.UI.createImageView({
width: 300,
image: '/images/header.png',
width: 300
});
var win = Ti.UI.createWindow({
backgroundColor: 'white',
height: Ti.UI.FILL,
title: 'test',
width: Ti.UI.FILL
});
win.add(header);
win.open();
Attempt #2 plain jane .xml and .tss styling:
index.js:
$.index.open();
index.xml:
<Alloy>
<Window class="container">
<Label id="label">Hello World!!</Label>
<ImageView id='img1'></ImageView>
</Window>
</Alloy>
index.tss:
".container": {
backgroundColor: 'white'
}
"#img1": {
width: 300,
image: '/header.png',
width: 300,
top: 0,
left: 0
}
file locations (i copied the same image to 3 different locations to try and get something):
app/assets/header.png
app/assets/android/header.png
app/assets/android/images/header.png
IMPORTANT, What I have tried:
using "/images/header.png
using "images/header.png"
using "header.png"
using "/header.png"
First thing, in the ImageView property you have mentioned the width twice, so you can remove one declaration and put the height of the image, like 300 (you can put Ti.UI.SIZE to maintain the aspect ratio)
Second, put the images inside app/asset/android/images/res-<density> respectively. Replace <density> with for example mdpi / hdpi / xhdpi. (you can put this in res-hdpi for testing)
Do a clean build and then check if it is getting reflected or not.
Very Simple!
Alloy example:
Put your image at app/assets/images for example app/assets/images/header.png .
Now your code is
<ImageView id='img1' image='/images/header.png' ></ImageView>
Or in .tss file
"#img1": {
width: 300,
image: '/images/header.png',
width: 300,
top: 0,
left: 0
}
End !
put your image :
app/assets/images/header.png
then access it with
<ImageView id='img1' image="/images/header.png"></ImageView>
important : try to clean your project first for every changes you made at assets folder before you run the app!