React Native PanResponder slow - react-native

I have a 300x300 orange View that can receive pan events using PanResponder. I have 100 circles that can be moved around together in this view. Each circle is rendered in my "Node" component when onPanResponderMove is called.
The code is ok for one circle but when there are 100 it lags. If panning quickly, there can be up to a 2s delay after touch is released where the circles continue to draw.
import React from 'react';
import {View, PanResponder} from 'react-native'
class App extends React.Component {
render(){
return <ReactSurface />
}
}
class ReactSurface extends React.Component {
constructor (props){
super(props)
console.log('ReactSurface constructor')
this.onTouchStart = this.onTouchStart.bind(this)
this.onTouchMove = this.onTouchMove.bind(this)
this.onTouchEnd = this.onTouchEnd.bind(this)
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => true,
onPanResponderStart: this.onTouchStart,
onPanResponderMove: this.onTouchMove,
onPanResponderRelease: this.onTouchEnd,
})
this.coordinates = []
for(let i = 0; i < 100; i++){
this.coordinates.push(2*i)
}
this.state = {
xAxisY: 0,
yAxisX: 0,
}
}
onTouchStart(){
console.log('onTouchStart')
this.startYAxisX = this.state.yAxisX
this.startXAxisY = this.state.xAxisY
}
onTouchMove(e, gestureState) {
console.log('onTouchMove')
this.setState({
yAxisX: this.startYAxisX + gestureState.dx,
xAxisY: this.startXAxisY + gestureState.dy
})
}
onTouchEnd() {
console.log('onTouchEnd')
}
render(){
return(<View
{...this.panResponder.panHandlers}>
<View style={{
backgroundColor: 'orange',
position: 'absolute',
width:300,
height:300,
}}>
<AllNodes
xAxisY={this.state.xAxisY}
yAxisX={this.state.yAxisX}
nodeRad={25}
coordinates={this.coordinates}/>
</View>
</View>)
}
}
const AllNodes = props => {
return props.coordinates.map((value, index) => {
return <Node key={index}
rawX={value} rawY={value}
yAxisX={props.yAxisX} xAxisY={props.xAxisY}
nodeRad={props.nodeRad}/>
})
}
const Node = props => {
return <View style={{
position: 'absolute',
top: props.xAxisY + props.rawY - props.nodeRad,
left: props.yAxisX + props.rawX - props.nodeRad,
width: 2*props.nodeRad,
height: 2*props.nodeRad,
borderRadius: props.nodeRad,
borderWidth: 1,
}}/>
}
export default App
How can I make my PanResponder significantly faster?
Note: If I can't I'm considering switching to the Flutter framework as it is efficient at drawing.
Edit: here is a link to a video of the panning on react-native and flutter
https://drive.google.com/open?id=1OgvHkf56Ru_I1NVooIrqby1hy5WtSIVR

Related

automatic scrolling in code for PDF using React Native and react-native-pdf

I'm a bit new to React Native programming, and I'm working on implementing an autoscroll feature for PDFs. For example, in some cases, I want to automatically scroll a PDF down x pages and then scroll at a desired speed. I followed this tutorial here here which works for just normal data, but when I used a object from react-native-pdf, it does not seem to scroll anymore. I'm wrapping the PDF object inside a ScrollView and can confirm that the scrolling code is being called. Can anyone suggest a solution or explain why this does not work with the PDF? Thanks so much!
I've also attached my code below if that helps. Currently, the PDF displays but is not autoscrolling at all.
import React from 'react';
import {StyleSheet, Dimensions, View, ScrollView} from 'react-native';
import Pdf from 'react-native-pdf';
export default class PDFScroll extends React.Component {
constructor(props) {
super(props);
this.state = {
currentPosition: 0,
};
this.scrolling = this.scrolling.bind(this);
}
componentDidMount(){
this.activeInterval = setInterval(this.scrolling, 100);
}
componentWillUnmount(){
clearInterval(this.activeInterval);
}
scrolling() {
position = this.state.currentPosition + 50;
this.pdf.scrollTo({ x: position, animated: true });
// After position passes this value, snaps back to beginning
let maxOffset = 2000;
// Set animation to repeat at end of scroll
if (this.state.currentPosition > maxOffset) {
this.pdf.scrollTo({ x: 0, animated: false })
this.setState({ currentPosition: 0 });
}
else {
this.setState({ currentPosition: position });
}
}
render() {
const source = {
uri: 'http://samples.leanpub.com/thereactnativebook-sample.pdf',
cache: true,
};
return (
<View style={styles.container}>
<ScrollView
style={styles.scrollview}
horizontal={false}
bounces={true}
ref={(ref) => this.pdf = ref}
>
{
<Pdf
source={source}
onLoadComplete={(numberOfPages, filePath) => {
console.log(`number of pages: ${numberOfPages}`);
}}
onPageChanged={(page, numberOfPages) => {
console.log(`current page: ${page}`);
}}
onError={error => {
console.log(error);
}}
onPressLink={uri => {
console.log(`Link presse: ${uri}`);
}}
style={styles.pdf}
/>
}
</ScrollView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'flex-start',
alignItems: 'center',
marginTop: 25,
},
pdf: {
flex: 1,
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
},
});

How to display a loader icon, while the page is rendered in React-native?

I am actually rendering a Complex UI in a react-native app. I am using react-navigation. Whenever I click the option in navigation drawer for my complex UI Page, the whole app hangs for 3-5 seconds and then the page is shown. What I want is a loader screen that loads immediately when I click on the option in navigation drawer and when the complex UI is rendered the loader should disappear and the UI should be shown. The app freezes because of the rendering of the UI. Is there any way to asynchronously render the UI after displaying the loading screen?
Edit
Below is the complex UI that I mentioned earlier. This table is loaded when I navigate to this page.
// source https://snack.expo.io/#shrey/highly-responsive-sheet
import React from "react"
import { Animated, ActivityIndicator, FlatList, ScrollView, StyleSheet, Text, View,TouchableOpacity } from "react-native"
const NUM_COLS = 15
const NUM_ROWS_STEP = 20
const CELL_WIDTH = 100
const CELL_HEIGHT = 60
const black = "#000"
const white = "#fff"
const styles = StyleSheet.create({
container: { backgroundColor: white, marginVertical: 40, marginBottom: 80 },
header: { flexDirection: "row", borderTopWidth: 1, borderColor: black },
identity: { position: "absolute", width: CELL_WIDTH },
body: { marginLeft: CELL_WIDTH },
cell: {
width: CELL_WIDTH,
height: CELL_HEIGHT,
borderRightWidth: 1,
borderBottomWidth: 1,
borderColor: black,
},
column: { flexDirection: "column" },
})
class Sheet extends React.Component {
constructor(props: {}) {
super(props)
this.headerScrollView = null
this.scrollPosition = new Animated.Value(0)
this.scrollEvent = Animated.event(
[{ nativeEvent: { contentOffset: { x: this.scrollPosition } } }],
{ useNativeDriver: false },
)
this.state = { count: NUM_ROWS_STEP, loading: false }
}
handleScroll = e => {
if (this.headerScrollView) {
let scrollX = e.nativeEvent.contentOffset.x
this.headerScrollView.scrollTo({ x: scrollX, animated: false })
}
}
scrollLoad = () => this.setState({ loading: false, count: this.state.count + NUM_ROWS_STEP })
handleScrollEndReached = () => {
if (!this.state.loading) {
this.setState({ loading: true }, () => setTimeout(this.scrollLoad, 500))
}
}
formatCell(value) {
return (
<TouchableOpacity onPress=()>
<View key={value} style={styles.cell}>
<Text>{value}</Text>
</View>
</TouchableOpacity>
)
}
formatColumn = (section) => {
let { item } = section
let cells = []
for (let i = 0; i < this.state.count; i++) {
cells.push(this.formatCell(`col-${i}-${item.key}`))
}
return <View style={styles.column}>{cells}</View>
}
formatHeader() {
let cols = []
for (let i = 0; i < NUM_COLS; i++) {
cols.push(this.formatCell(`frozen-row-${i}`))
}
return (
<View style={styles.header}>
{this.formatCell("frozen-row")}
<ScrollView
ref={ref => (this.headerScrollView = ref)}
horizontal={true}
scrollEnabled={false}
scrollEventThrottle={16}
>
{cols}
</ScrollView>
</View>
)
}
formatIdentityColumn() {
let cells = []
for (let i = 0; i < this.state.count; i++) {
cells.push(this.formatCell(`frozen-col-${i}`))
}
return <View style={styles.identity}>{cells}</View>
}
formatBody() {
let data = []
for (let i = 0; i < NUM_COLS; i++) {
data.push({ key: `content-${i}`})
}
return (
<View>
{this.formatIdentityColumn()}
<FlatList
style={styles.body}
horizontal={true}
data={data}
renderItem={this.formatColumn}
stickyHeaderIndices={[0]}
onScroll={this.scrollEvent}
scrollEventThrottle={16}
extraData={this.state}
/>
</View>
)
}
formatRowForSheet = (section) => {
let { item } = section
return item.render
}
componentDidMount() {
this.listener = this.scrollPosition.addListener(position => {
this.headerScrollView.scrollTo({ x: position.value, animated: false })
})
}
render () {
let body = this.formatBody()
let data = [{ key: "body", render: body }]
return (
<View style={styles.container}>
{this.formatHeader()}
<FlatList
data={data}
renderItem={this.formatRowForSheet}
onEndReached={this.handleScrollEndReached}
onEndReachedThreshold={.005}
/>
{this.state.loading && <ActivityIndicator />}
</View>
)
}
}
export default Sheet
Your UI probably also loads slowly because you are using a FlatList inside a FlatList. In my experience it will only cause confussion and performance issues.
One thing you might also want to do is integrate with something like Redux, to handle a global loading state, and based on that value you show a loading spinner or the data.
Without seeing actual code, I can only suggest high-level solutions:
Consider using requestAnimationFrame or InteractionManager to schedule expensive calculations.
Render the loading state first, then listen to navigation focus event to start rendering your Complex UI.
Remember to test in production mode, because the difference with development can be signification.
Links to the concepts I mentioned:
https://facebook.github.io/react-native/docs/performance#my-touchablex-view-isn-t-very-responsive
https://facebook.github.io/react-native/docs/timers#interactionmanager
https://reactnavigation.org/docs/en/navigation-events.html

Animated element display not updated after position change

I'm fairly new to React-Native, so it's very likely I'm missing some core concepts.
I want to create a draggable element and be able to move it back to its original position.
The first part is ok, but when I try to update the position, it looks like it works (because when I click again, the element goes back to its original position), but the view isn't updated.
I tried calling setState and forceUpdate but it doesn't update the view.
Do you guys have any idea why ?
Here is a demo of what I have so far :
import React from 'react';
import {Button, StyleSheet, PanResponder, View, Animated} from 'react-native';
export default class Scene extends React.Component {
constructor(props) {
super(props)
const rectanglePosition = new Animated.ValueXY({ x: 0, y: 0 })
const rectanglePanResponder = this.createPanResponder();
this.state = {
rectanglePosition,
rectanglePanResponder
}
}
createPanResponder = () => {
return PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (event, gesture) => {
this.state.rectanglePosition.setValue({ x: gesture.dx, y: gesture.dy });
},
onPanResponderRelease: () => {
this.state.rectanglePosition.flattenOffset();
},
onPanResponderGrant: () => {
this.state.rectanglePosition.setOffset({
x: this.state.rectanglePosition.x._value,
y: this.state.rectanglePosition.y._value
});
}
});
}
resetPosition = () => {
const newPosition = new Animated.ValueXY({ x: 0, y: 0 })
this.setState({ rectanglePosition: newPosition }) // I thought updating the state triggered a re-render
this.forceUpdate() // doesn't work either
}
getRectanglePositionStyles = () => {
return {
top: this.state.rectanglePosition.y._value,
left: this.state.rectanglePosition.x._value,
transform: this.state.rectanglePosition.getTranslateTransform()
}
}
render() {
return (
<View style={styles.container}>
<Animated.View
style={[styles.rectangle, this.getRectanglePositionStyles()]}
{...this.state.rectanglePanResponder.panHandlers}>
</Animated.View>
<View style={styles.footer}>
<Button title="Reset" onPress={this.resetPosition}></Button>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
borderColor: 'red',
backgroundColor: '#F5FCFF',
},
footer: {
borderWidth: 1,
width: '100%',
position: 'absolute',
bottom: 0,
left: 0,
backgroundColor: 'blue',
},
rectangle: {
position: 'absolute',
height: 50,
width: 50,
backgroundColor: 'red'
}
});
If your only intention is to put it on upper left corner:
resetPosition = () => {
this.state.rectanglePosition.setValue({ x: 0, y: 0 });
};
Note! Refer to this snack to see how you do it without a state https://snack.expo.io/#ziyoshams/stack-overflow

Glitches with draggable components using react native - implemented using Animated and PanResponder

Drawing inspiration from this question, I have implemented two draggable components as children in a view. The parent view is as follows:
import React, { Component } from "react";
import { Text, View, StyleSheet, Dimensions } from "react-native";
import Draggable from "./Draggable";
export default class FloorPlan extends Component {
constructor() {
super();
const { width, height } = Dimensions.get("window");
this.separatorPosition = (height * 2) / 3;
}
render() {
return (
<View style={styles.mainContainer}>
<View style={[...styles.dropZone, { height: this.separatorPosition }]}>
<Text style={styles.text}>Floor plan</Text>
</View>
<View style={styles.drawerSeparator} />
<View style={styles.row}>
<Draggable />
<Draggable />
</View>
</View>
);
}
}
const styles = StyleSheet.create({
mainContainer: {
flex: 1
},
drawerSeparator: {
backgroundColor: "grey",
height: 20
},
row: {
flexDirection: "row",
marginTop: 25
},
dropZone: {
height: 700,
backgroundColor: "#f4fffe"
},
text: {
marginTop: 25,
marginLeft: 5,
marginRight: 5,
textAlign: "center",
color: "grey",
fontSize: 20
}
});
And the draggable component is implemented as follows:
import React, { Component } from "react";
import {
StyleSheet,
View,
PanResponder,
Animated,
Text,
Dimensions
} from "react-native";
export default class Draggable extends Component {
constructor() {
super();
const { width, height } = Dimensions.get("window");
this.separatorPosition = (height * 2) / 3;
this.state = {
pan: new Animated.ValueXY(),
circleColor: "skyblue"
};
this.currentPanValue = { x: 0, y: 0 };
this.panListener = this.state.pan.addListener(
value => (this.currentPanValue = value)
);
}
componentWillMount() {
this.state.pan.removeListener(this.panListener);
}
componentWillMount() {
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => false,
onMoveShouldSetPanResponder: (evt, gestureState) => false,
onMoveShouldSetPanResponderCapture: (evt, gesture) => {
return true;
},
onPanResponderGrant: (e, gestureState) => {
this.setState({ circleColor: "red" });
},
onPanResponderMove: (event, gesture) => {
Animated.event([
null,
{
dx: this.state.pan.x,
dy: this.state.pan.y
}
])(event, gesture);
},
onPanResponderRelease: (event, gesture) => {
this.setState({ circleColor: "skyblue" });
if (gesture.moveY < this.separatorPosition) {
this.state.pan.setOffset({
x: this.currentPanValue.x,
y: this.currentPanValue.y
});
this.state.pan.setValue({ x: 0, y: 0 });
// this.state.pan.flattenOffset();
} else {
//Return icon to start position
this.state.pan.flattenOffset();
Animated.timing(this.state.pan, {
toValue: {
x: 0,
y: 0
},
useNativeDriver: true,
duration: 200
}).start();
}
}
});
}
render() {
const panStyle = {
transform: this.state.pan.getTranslateTransform()
};
return (
<Animated.View
{...this.panResponder.panHandlers}
style={[
panStyle,
styles.circle,
{ backgroundColor: this.state.circleColor }
]}
/>
);
}
}
let CIRCLE_RADIUS = 30;
let styles = StyleSheet.create({
circle: {
backgroundColor: "skyblue",
width: CIRCLE_RADIUS * 2,
height: CIRCLE_RADIUS * 2,
borderRadius: CIRCLE_RADIUS,
marginLeft: 25
}
});
A draggable component can be dragged onto the FloorPlan and it's location will be remembered for the next pan action. However, sometimes during dragging, a glitch occurs and the icon jumps at the beginning of the pan or completetely disappears.
What could be the problem? I am developing using React Native 0.55.2 and testing using a device running Android 7.

Absolute positioning not working inside FlatList

I am trying to create a tinder like swipe deck animation. I am using FlatList to render the images. To stack the images one above the other, I am using 'absolute' positioning. The issue with this is the images are not getting rendered and all I am seeing is a blank screen. I am not sure whether there is something wrong with using positioning inside FlatList.
The reason I went with FlatList is my stack will contain around 200 to 300 images. I think I can implement this without using FlatList by just rendering the images as batches (say render 10 images at once and then render the next 10 and so on).
I want to know whether it is possible to implement this using FlatList.
NOTE: The issue is in android and I am not sure about iOS
import React from "react";
import {
StyleSheet,
Text,
View,
FlatList,
Image,
Dimensions,
Animated,
PanResponder
} from "react-native";
const DATA = [
{
id: 1,
text: "Card #1",
uri: "http://www.fluxdigital.co/wp-content/uploads/2015/04/Unsplash.jpg"
},
{
id: 2,
text: "Card #2",
uri: "https://images.pexels.com/photos/247932/pexels-photo-247932.jpeg?h=350"
},
{
id: 3,
text: "Card #3",
uri: "http://www.fluxdigital.co/wp-content/uploads/2015/04/Unsplash.jpg"
}
];
const { width, height } = Dimensions.get("window");
export default class App extends React.Component {
constructor(props) {
super(props);
this.position = new Animated.ValueXY();
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (event, gestureState) => true,
onPanResponderMove: (event, gestureState) => {},
onPanResponderRelease: (event, gestureState) => {}
});
this.state = {
currentIndex: 0
};
}
extractKey = (item, index) => item.id.toString();
renderCard = ({ item }) => {
return (
<View style={styles.imageContainer}>
<Image
source={{ uri: item.uri }}
resizeMode="cover"
style={styles.image}
/>
</View>
);
};
render() {
return (
<FlatList
contentContainerStle={styles.container}
data={DATA}
keyExtractor={this.extractKey}
renderItem={this.renderCard}
scrollEnabled={true}
/>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
imageContainer: {
width,
height: height - 20,
backgroundColor: "red",
padding: 10
position: 'absolute'
},
image: {
flex: 1,
width: null,
height: null,
borderRadius: 20
}
});
i create tinder swiper using following
might be help you
import React from "react";
import {
StyleSheet,
Text,
View,
FlatList,
Image,
Dimensions,
Animated,
PanResponder
} from "react-native";
const SCREEN_HEIGHT = Dimensions.get('window').height
const SCREEN_WIDTH = Dimensions.get('window').width
const DATA = [
{
id: 1,
text: "Card #1",
uri: "https://images.pexels.com/photos/247932/pexels-photo-247932.jpeg?h=350"
},
{
id: 2,
text: "Card #2",
uri: "http://www.fluxdigital.co/wp-content/uploads/2015/04/Unsplash.jpg"
},
{
id: 3,
text: "Card #3",
uri: "http://www.fluxdigital.co/wp-content/uploads/2015/04/Unsplash.jpg"
}
];
export default class App extends React.Component {
constructor(props) {
super(props);
this.position = new Animated.ValueXY();
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (event, gestureState) => true,
onPanResponderMove: (event, gestureState) => {},
onPanResponderRelease: (event, gestureState) => {}
});
this.state = {
currentIndex: 0
};
}
extractKey = (item, index) => item.id.toString();
renderUsers = () => {
return DATA.map((item,i)=>{
return(
<View style={ { height: SCREEN_HEIGHT - 120, width: SCREEN_WIDTH, padding: 10, position: 'absolute' }}>
<Image
style={{ flex: 1, height: null, width: null, resizeMode: 'cover', borderRadius: 20 }}
source={{uri:item.uri}} />
</View>
)
})
}
render() {
return (
<View style={{marginTop:24,flex:1,backgroundColor:'#eee'}}>
{
this.renderUsers()
}
</View>
);
}
}