I created an list view with images which automatically requests new items when scrolled down. I am using Image component to show the image from url source. the problem is that the images are not cached in memory once they are loaded. I can see them when I scroll down but when I go back up I have to wait for them to be loaded again. Is there a way to fix this? Image component has a property cache but it doesn't do any improvement. I know that in Android this is done in exactly the same way and the images are persisted in memory once downloaded.
here is a sample code:
ApplicationWindow {
visible: true
width: 800
height: 600
id: rootId
property url fullImageUrl
property string tag : "windsurfing"
XmlListModel{
id : modelId
namespaceDeclarations: "declare namespace media = 'http://search.yahoo.com/mrss/';"
source: "http://api.flickr.com/services/feeds/photos_public.gne?format=rss2&tags="+tag
query: "//item[title and media:thumbnail and media:content]"
XmlRole{name:"fullImage"; query:"media:content/#url/string()" }
}
TextField{
id : userValueId
font.pointSize: 14
width: parent.width
height : implicitHeight
placeholderText: "Enter a Flickr Tag"
onEditingFinished: tag = text
}
ListView{
id : listViewId
anchors.fill: parent
anchors.topMargin: userValueId.height + 10
Layout.minimumWidth: 400
Layout.maximumWidth: parent.width - 50
model: modelId
delegate: delegateId
}
Component{
id: delegateId
Rectangle{
id :itemId
height : 300
width : 500
Image{
id : imageId
source : fullImage
anchors.fill: parent
fillMode: Image.Stretch
cache: true
}
}
}
}
How can I cache the graphic items supplied by the model and drawn in
QML ListView?
I would try to use ListView cacheBuffer property which is specified in pixels to fit delegates. If your delegate is of 300 pixels in the scroll direction (say, height and you scroll vertically) then having one delegate per row and 10000 pixels for the "cache buffer" it fits up to 33 delegates then.
ListView {
id : listViewId
anchors.fill: parent
anchors.topMargin: userValueId.height + 10
cacheBuffer: 10000 // pixels in direction of scrolling the view,
// saves a bit of processing just by caching
// delegates content in memory, causes async
// read-ahead for images outside of view area
Layout.minimumWidth: 400
Layout.maximumWidth: parent.width - 50
model: modelId
delegate: delegateId
}
If the amount of memory strictly limited it makes sense to specify reasonably small cacheBuffer e.g. 2 pages of list view to prevent too much read-ahead. The cache is not a guarantee that the data won't be read again. I also had some doubts if using Component is the right way to go with image caching but concluded it should not affect things as long as there is no Loader in the code for that Component that does the load at arbitrary times.
And you can also try to implement own image provider in C++ to explicitly control the image download / caching so the logic will be fully controlled by your code.
Related
I'm trying to display only the right part of an image in QML by using the sourceClipRect property
Here is a snippet of the code
Image
{
id : _image
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectCrop
width: parent.width
height: parent.height
sourceSize.width : undefined
sourceSize.height : undefined
source : imagePath
sourceClipRect: Qt.rect(sourceSize.width/2, 0, sourceSize.width/2, sourceSize.height)
smooth : true
mipmap : true
}
This image is inside an item with a fixed Width and Height
This gives me a warning and a binding loop on the property sourceClipRect, I'm guessing from using sourceSize in the sourceClipRect
I cannot use hard numbers in the sourceClipRect, as I don't know the original size of the picture
Do you know how can I avoid this binding loop ?
Maybe by getting the original width and height another way, but I don't know any other way than sourceSize in pure QML
Ps : The results works as expected and is working fine, i just have an ugly warning stating a binding loop
Thanks a lot in advance
I finnaly found a fix for this that is acceptable.
It is maybe not the cleanest but it's acceptable for me.
But at the instanciation of the image i just save the var without the binding like so
Image
{
id : _image
property var sourceSizeWidth :{sourceSizeWidth = sourceSize.width} //initializing this way avoids the binding between sourceSize.Width and sourceSizeWidth
property var sourceSizeHeight :{sourceSizeHeight = sourceSize.height} //initializing this way avoids the binding between sourceSize.height and sourceSizeHeight
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectCrop
width: parent.width
height: parent.height
sourceSize.width : undefined
sourceSize.height : undefined
source : imagePath
sourceClipRect: Qt.rect(sourceSizeWidth/2, 0, sourceSizeWidth/2, sourceSizeHeight) //Use the unbinded var to set the sourceClipRect
smooth : true
mipmap : true
}
Doing an initialization like this avoid the binding between newly created sourceSizeWidth and QML property sourceSize.width
property var sourceSizeWidth :{sourceSizeWidth = sourceSize.width}
As I said, not the cleanest and there is probably a smarter way of doing it, but it's enough for now, works fine and without warning binding loop.
Cheers !
I'm developing a TimeLine component. There are Views list inside horizontal ScrollView that represents half an hour blocks. And I have a Component called TimeRangeSelector, which I use to select a range of time in TimeLine ScrollView. So while I scroll the ScrollView I need to move the TimeRangeSelector in parallel without any lag. Below is the TimeLine component. You can see the 30mins blocks are filled inside ScrollView. The yellow color one is the TimeRangeSelector which is a Animated.View. And the left position of the TimeRangeSelector is set using the scroll position. Each time ScrollView moves im setting the state as below.
<ScrollView
horizontal={true}
onScroll={this.onScrollFunc}
>
{timelineElements}
</ScrollView>
onScrollFunc = (event, gesture) => {
this.setState({
contentOffsetX: event.nativeEvent.contentOffset.x,
scrollStopped: false
});
};
And Im passing the scrollBarPosition as props to the TimeRangeSelector and setting its left position style as shown in below code
<TimeRangeSelector
scrollBarPosition={this.state.contentOffsetX}
/>
let styles= [
{
position: "absolute",
left: this.props.scrollBarPosition,
backgroundColor: backgroundColor,
marginTop: 20,
marginLeft: 1,
width: this.state.selectionWidth,
height: 100
}
];
But the issue is when I scroll the ScrollView the TimeRangeSelector moves with it, but it has a delay.. when I scroll faster the distance between where it should be and where it is, is becoming high. Can anyone give me some ideas according to your knowledge.
My assumption: According to my understanding, I think the lag is because of it takes several instances to reach the setState in and set the ScrollBarPosition as below steps.
ScrollView moved 1 frame.
ScrollView fires the onScroll function and emmits the event with new x point.
Then in the onScroll function it sets the State.
As I understand it takes some time to happen all these steps from JavaScript thread all the way to Native Thread and come back to JS thread again.
You should use something like const onScroll = Animated.event([{ nativeEvent: { contentOffset: { x: animatedValue } } }], { useNativeDriver: true }); with const animatedValue = new Animated.Value(0). This way the animation is only done on the native level without going back and forth through the JS thread.
This animated value can only be used effectively with opacity and transform style properties, but it should help you with your problem.
You can read more about this, in this awesome article.
I want to have a window xtype that contains just an of its own size but when I show my window, it shows up as a tiny square in the middle of the screen (not the actual size of the image). I've tried using this block of code
listeners:{
afterrender: function(me){
me.el.on({
load: function (evt, ele, eOpts){
me.updateLayout();
},
error: function(evt,ele,eOpts){
}
});
}
}
to update the layout of the parent window from within the image xtype, but this makes the window not centered and centering the window during an afterrender event isn't working. Am I missing something simple? I also have the maxWidth and maxHeight configs of the image set to Ext.getBody().getViewSize().width and height respectively.
Sencha had a good reason not to use xtype: 'image' for their own icons etc.
xtype: 'image' only works with fixed width and height, so you can't even preserve aspect ratio, as far as I know.
What you want to do is to have a container and set the background-image of the container.
xtype: 'container',
style: 'background-image: url(\'image.jpg\'); background-repeat:no-repeat; background-position:50% 50%; background-size:contain'
or even just use the window: https://fiddle.sencha.com/#view/editor&fiddle/244n
I am trying to render a list of ~250 images in 3 columns using FlatList in RN0.43, and I change the width of the images in the onLayout function of the FlatList to fit the width of screen.
The initial performance is ok, but after some scrolling up/down, sometimes it takes a second or 2 until images are shown.
it is even worse if I change to screen orientation, it takes 2~3 seconds to get screen updated.
a few findings:
after screen rotation, it takes a second or 2 until FlatList.onLayout is called
after FlatList.onLayout and update of image width, each image (about half of the list, ~150 images; while only ~15 are shown) is rendered 2~4 times, while render() is only called once.
question:
how can I modify the code to improve the performance?
in the getItemLayout() of a multicolumn list, should the offset be something like (itemHeight + separatorHeight) * (index%numColumns)?
Thanks.
tested on: GalaxySII (4.1.2) and Android SDK emulator (7.1.1)
var data = [
require('./res/img/Search.png'),
require('./res/img/test - Copy.png'),
// ~250 items
...];
class app extends React.Component {
renderItem (info, width) {
console.log('renderItem', info.index);
if(width !== this.width) {
this.imageStyle = {width: width-MarginHorizontal , height: width-MarginHorizontal, resizeMode: 'contain'};
}
return (
<Image
source = {info.item}
key = {info.index}
style={this.imageStyle}/>
);
}
render() {
console.log('Test.render');
return (
<View style={{
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
backgroundColor: '#F5FCFF'
}}>
<GridList
numColumns={3}
columnWrapperStyle={{ alignItems: 'center', marginVertical: 5, justifyContent: 'space-around'}}
data={data}
renderItem={this.renderItem}
/>
</View>
);
}
}
class GridList extends Component {
onLayout(event) {
console.log('GridList.onLayout()');
let newColumnWidth = event.nativeEvent.layout.width/ this.numColumns;
this.layout = Object.assign({},event.nativeEvent.layout);
if( undefined === this.columnWidth || Math.abs(newColumnWidth - this.columnWidth) > WidthTolerance ) {
this.columnWidth = newColumnWidth;
if(this.isComponentMounted) {
this.setState({renderCount: this.state.renderCount+1});
} else {
this.state.renderCount +=1;
}
}
}
render() {
console.log('GridList.render()');
return (
<FlatList
{...(this.modifiedProps)}
renderItem={(info) => { return this.props.renderItem(info, this.columnWidth); }}>
{this.props.children}
</FlatList>
);
}
}
Disclaimer: I know that the question is old, but here is my response anyways.
My app has a hand full of lists with 500+ items. So, we got to a point where the app was crashing on popular not-bad phones. Then I've made this extensive research about performance on FlatLists.
The FlatList component was presented as a alternative for the old ScrollView. The problem is that ScrollViews render all your list at once so they perform visually better, but there is a trade off in memory consumption, that leads to app crashes.
So Flat Lists are a necessary evil. They essentially only render items that are visible, which is a huge gain on memory consumption, but a pain for visual performance, specially for heavy/complex items, that happens to be your case with those responsive images.
How to workaround?
There are a lot of strategies that you can implement to mitigate your problem.
Use cached and performatic images, such as react-native-fast-image. Every operation that you can remove or abbreviate for freeing the Javascript thread: do it (every image is a new Image(), so, if they are cached, you have your loaded hook called sooner)
Your list item component is a read-only component, which is supposed to be 'dumb'. Implement a shouldComponentUpdate() { return false } or a more solid update control method as needed. This is HUGE perf boost.
Remove console.logs anywhere near your list. They slow the Javascript thread really bad.
Build your app for production and test it. It becomes almost always twice or three times faster. Dev env is slow because of debugging.
Give this article a good read for more strategies.
Conclusion
FlatList IS a slow component. This is a known, open and well documented issue. Do what you can to improve it, and let's hope future releases may fix this.
Yes I was having the same problem - multiple images & videos in list in react native So I removed Flatlist instead of this I preferred to use ListView to render fast & to fix touchability issue on list item but Dont forget to set PureComponent to the list item
You're re-creating lots of styling objects for each row of the list individually. This puts a lot of traffic on the JS->Native bridge. Try using stylesheet instead of passing the styles inline.
I strongly recommend everybody to read the article in the link attached below to optimize your flatlist.
https://reactnative.dev/docs/optimizing-flatlist-configuration
Try to set unique key for each item using keyExtractor
For Example:
render() {
return (
<List>
<FlatList
...
keyExtractor={item => item.email}
/>
</List>
);
}
jQuery(document).ready(function(){
if(screen.width < 767){
jQuery.fn.fullpage.setAutoScrolling(false);
}
else{
jQuery('#fullpage').fullpage({
scrollBar:true,
afterRender: function(){
jQuery('.hslider_misc_holder').each(function(){
var hslider_slider_id = jQuery(this).attr('id').slice(8);
activate_width_checker(1080, 1080, 'responsive', hslider_slider_id);
});
}
});
}
});
I'm trying to turn off fullpage.js below 767px the current function is to turn off auto scroll, no matter what solution I try I can't stop the effect!!!
Ideally I want it turned off entirely.
Help appreciated
Ant
Use the fullpage.js responsive options: responsiveWidth or responsiveHeight.
From the docs:
responsiveWidth: (default 0) A normal scroll (autoScrolling:false) will be used under the defined width in pixels. A class fp-responsive is added to the body tag in case the user wants to use it for his own responsive CSS. For example, if set to 900, whenever the browser's width is less than 900 the plugin will scroll like a normal site.
responsiveHeight: (default 0) A normal scroll (autoScrolling:false) will be used under the defined height in pixels. A class fp-responsive is added to the body tag in case the user wants to use it for his own responsive CSS. For example, if set to 900, whenever the browser's height is less than 900 the plugin will scroll like a normal site.
Example online (code available in github examples folder)