I have a quite typical problem of laggy animations during component rendering. However, as far as I understand, the common solutions I found in the documentation of react-native cannot be applied to my use case.
Let me explain: I have a tab pager implemented with (i) a ViewPagerAndroid containing a set of pages and (ii) a tab bar composed of a set of touchables and a line (Animated.View) that moves (translateX transform) when the pager X-scroll value changes.
Each page component follows the same logic:
when componentDidMount is called, it triggers a network call for loading data
once the data are loaded, the state is updated with the new data and the component is rerendered
the render method simply returns a FlatList that displays the loaded data (20 items only)
My problem is: if the user clicks on a tab while the pages are rendered, the animations are horribly laggy. Given that the animation and the rerendering are performed on the UI thread, I'm guessing that they are competing in some way.
I eliminated various causes, but wasn't able to find a proper solution to keep the animation fluid:
I replaced the network call with hardcoded data and a simple timeout; the UI is still laggy when the pages are rerendered
I tried to improve the rerendering time, but the FlatList is quite slow even using pure components.
I tried in release mode and there is no differences
The problem occurs also on a physical device (Nexus 5X, which is quite powerful)
It's not specific to my implementation, I tried github.com/skv-headless/react-native-scrollable-tab-view and github.com/zbtang/React-Native-ViewPager
I know about InteractionManager and TimerMixin.requestAnimationFrame, but it's useless in my case: when the rendering is started, the user can still click on the tab bar buttons and it's not an option to wait the end of the render (laggy).
I uploaded my code in a github repository (https://github.com/benjaminbillet/tabbartest), in case you want to have more details. It's quite straightforward, but don't hesitate to ask me more details.
Some additional information:
react-native 0.46.4
node 8.2.0
npm 5.3.0
tried only on Android.
Do you think I should fill a bug report?
Thanks a lot.
EDIT-1 Here is a simplified version of the animation code:
render() {
const underlineStyle = {
position: 'absolute',
left: 0,
bottom: 0,
width: this.tabWidth,
height: underlineHeight,
backgroundColor: underlineColor,
transform: [{
translateX: this.pagerScrollX.interpolate({
inputRange: [0, 1],
outputRange: [0, this.tabWidth],
}),
}],
};
return (
<View style={[styles.tabs, style]}>
...
<Animated.View style={underlineStyle} />
</View>
);
}
onPageScroll(offset) {
const { offset, position } = e.nativeEvent;
this.pagerScrollX.setValue(position + offset);
}
Related
Before you link me to another question similar to this one, such as this or that. I will say that I have done exactly what the answers said, but my gif won't animate as it should (It is displayed though).
Here is what I've done in a function, which is displayed through the main App function Stack.Screen within a NavigationContainer and Stack.Navigator. (I'm using React Navigation to move across screens, the context here is that a button is pressed and it displays the contents of the DetailsScreen function)
function DetailsScreen({ navigation }) {
return (
<View style={{ flex: 2, alignItems: 'center', justifyContent: 'center' }}>
<Image source={require('./src/gif/moving.gif')} />
<Text>Here is a gif</Text>
</View>
);
}
This displays the first still image of my gif, but doesn't animate it.
I also already went ahead and placed the implementations in the build.gradle dependencies, but it didn't do anything for me. I have a feeling the problem lies there.
implementation 'com.facebook.fresco:fresco:1.+'
// For animated GIF support
implementation 'com.facebook.fresco:animated-gif:1.+'
// For WebP support, including animated WebP
implementation 'com.facebook.fresco:animated-webp:1.+'
implementation 'com.facebook.fresco:webpsupport:1.+'
(I already checked fresco's new implementation version 2, but it still didn't help. I also tried changing from a specific version, still doesn't work)
I am using React Native version 0.67. (I tried starting it again while downgrading react-native to 0.66 and it still doesn't work.)
Also, not sure if this has to do with anything in this screenshot here, this is what I had by default and gave me this error message as soon as I opened the file, but the program launches just fine even with that on
Doing it normally in the main App() function starting first displays the gif, but still remains as a still image.
What should I do? I mean... what else can I do?
Edit:
I found the solution to the problem... it was a simple case of just cold booting the emulator I was using from android studio.
However, Tadej's answer is valid, as the view style aligning messes up the gif a bit. If you are having a similar problem and the answer doesn't help, try cold booting your emulator, or even reinstall a newer one... or alternatively, use a real android phone to test these sorts of things.
Anyway, thanks a lot for the help Tadej ! I hope this question has helped others in my situation.
Tadej Slemenšek
This worked for me. Setting height and width on Image prop did not show the gif. So I flexed it and added maxWidth and maxHeight.
const imageUrl = 'https://media.giphy.com/media/xT0xeCCINrlk96yc0w/giphy.gif';
const App = () => {
const { width } = useWindowDimensions();
return (
<View style={{flex: 1}}>
<Image style={{flex: 1, maxWidth: width, maxHeight: width}} source={{uri: imageUrl}}/>
</View>
);
};
I'need to show user feed with items where every item contain a chart.
Now I use react-native-svg-charts:
<LineChart
style={{ height: 150, position: 'relative', left: -20 }}
data={data}
curve={shape.curveNatural}
svg={{ stroke: chartColor1, strokeWidth: 5 }}
contentInset={{ top: 20, bottom: 20 }}
showGrid={false}
numberOfTicks={0}
key={props.id}
>
But when I load more then 50 items performance of the app fall down to 10-15 fps.
I think it because of many SVG's on page. Which solution do you think should I use to avoid this?
I answered a similar question recently here: Real-time data update chart in React Native and will suggest the same here.
I've struggled with performance when plotting in React Native with all of the SVG-based libraries I've tried. I recently decided to try using a couple canvas-based plotting libraries within a WebView and had very good results. I ended up making a simple package: https://www.npmjs.com/package/#dpwiese/react-native-canvas-charts.
Should you not want to use this package and instead do it yourself, it's quite straightforward. While the package source code is probably the best resource I'll summarize the steps below:
Create an HTML file and import it into your component:
const htmlTemplate = require("./index.html");
where the HTML contains the JavaScript for the charting library of choice. The linked package above currently supports Chart.js v3 and uPlot. In the steps below I'll show a Chart.js configuration.
Create a ref, for example let webref.
Create a WebView and onLoadEnd you can inject some JavaScript into the WebView that will configure and create your chart
<WebView
originWhitelist={["*"]}
ref={r => (webref = r)}
source={htmlTemplate}
onLoadEnd={() => { addChart(config) }}
/>
where addChart looks something like:
const addChart = config => {
webref.injectJavaScript(`const el = document.createElement("canvas");
document.body.appendChild(el);
window.canvasLine = new Chart(el.getContext('2d'), ${JSON.stringify(config)});`);
};
and config is a valid Chart.js configuration.
To update the chart data simply inject some JavaScript to update the data. In the case of Chart.js here, that'd look like:
const setData = dataSets => {
if (dataSets) {
dataSets.forEach((_, i) => {
webref.injectJavaScript(`window.canvasLine.config.data.datasets[${i}].data = ${JSON.stringify(dataSets[i])};
window.canvasLine.update();`);
});
}
};
where dataSets are valid Chart.js data sets.
That's it! I've only played around with these two plotting libraries via the https://www.npmjs.com/package/#dpwiese/react-native-canvas-charts package, but so far performance has been really good, even with the JSON stringification of all the passed chart data. I haven't quantified it thoroughly, but both libraries are orders of magnitude more performant than any of the SVG-based ones I've tried.
So I know this has been gone over in multiple other threads but this one is kind of unique, I've built this app using React Native that uses swipe gestures to animate views sliding in an out of the view. iOS is nearly flawless but when running on android anything past the first view in the stack button presses inside of the views stop working, I believe that this is because the parent view is essentially sliding out of the view and so non of the touches are registering. I'm kind of stumped on how to fix this, this is kind of an adhoc implementation so maybe theres react native package that I overlooked.
<Animated.View style={{
transform: [{
translateX: xOffset.interpolate({
inputRange: [0, 2],
outputRange: [0, -Dimensions.get('window').width * 2]
})
}]
}}>
<RenderViews />
</Animated.View>
const RenderViews = props => {
return (
<View style={{
flexDirection: 'row',
}}>
<Preset1 />
<Preset2 />
<Manual />
</View>
)
};
I ended up implementing react-native-swiper which handles all of the swipe logic and button presses are working again on android.
https://github.com/leecade/react-native-swiper
you have use tovalue to start the animation
example
Animated.timing(
// Animate value over time
this.state.fadeAnim, // The value to drive
{
toValue: 1, // Animate to final value of 1
},
).start();
You should probably listen to the screen events
And run an animation event when there is a change
I've used Flatlist's scrollToIndex many times before but I'm developing a custom calendar and have ran into the following use case which breaks the method:
Calendar has 2 modes:
DAY SELECTION: Basically just a matrix of touchable days to select.
YEAR SELECTION: A scrollable FlatList with years to select from.
Only one of them must be shown at a given time.
Everything works fine except returning the scroll position to the selected year once I switch modes in Android (in iOS works as expected, you can check on snack example below).
Here's a little code snippet showing how I'm managing the scroll update.
componentDidUpdate(_, prevState) {
if (prevState.mode === TEXT_MODE && this.state.mode === LIST_MODE) {
console.log(`updated scroll to index ${this.state.selectedItem}`);
this.itemsScroll.current.scrollToIndex({
animated: false,
index: this.state.selectedItem,
viewPosition: 0.5,
viewOffset: 0,
});
}
}
HERE is a minimal working example illustrating the bug.
NOTE: RN version is 0.57.1. Bug doesn't happen unless list visibility is modified.
I'm wondering what is the best way to swap out two panels on the screen with a fade effect?
I have two panels which I have positioned over top of each other using CSS. panelOne is visible, panelTwo is hidden.
On click of another button (not on either panel), I want panelOne to fade out and panelTwo to fade in.
I currently have this working using the code below, but I find it's quite laggy on some Android devices we have here for testing. Is there a better way to do it than what I'm currently using? How can I improve this animation?
This code is executed on button tap:
Ext.Anim.run(panelOne, 'fade', {
duration: 100,
after: function() {
panelOne.hide();
}
});
Ext.Anim.run(panelTwo, 'fade', {
out: false,
duration: 100,
before: function() {
panelTwo.show();
}
});
Any help is much appreciated.
Thanks.
It's not necessary to use Ext.Anim such long.
For example, your app has an Ext.Container which contains panelOne and panelTwo as the first and second item, respectively.
Then if you want to navigate from panelOne to panelTwo with a fade animation, just simply use:
Ext.getCmp('your_container_id').animateActiveItem(1,'fade')
or you can do this:
Add showAnimation: 'fadeIn' to your panelTwo's config
Add hideAnimation: 'fadeOut' to your panelOne's config
Hope it helps.
to control other config, such as duration, use:
hideAnimation: {type:'fadeOut', duration: 1000}