I'm doing a bar chart and the issue is that it loads to the downside. I just need to make it correct side. How can I solve that issue?
Here I attached a current image of the graph:
Here is the index.js and I used child component AnimatedBar to draw each bar column. I generated random heights and passed them to the child component.
componentDidMount() {
this.generateData();
// this.interval = setInterval(() => {
// this.generateData();
// }, 1000); }
componentWillUnmount() {
clearInterval(this.interval); }
generateData = () => {
const data = [];
for (let i = 0; i < 10; i++) {
data.push(Math.floor(Math.random() * window.width));
}
this.setState({
data,
}); }
render() {
return (
<View style={{ flex: 1,flexDirection:"column", backgroundColor: '#F5FCFF', justifyContent: 'center'}}>
<View style={{flexDirection:"row",justifyContent: 'flex-end'}}>
{this.state.data.map((value, index) => <AnimatedBar value={value} delay={DELAY * index} key={index} />)}
</View>
</View>
); }
Here I added the child component:
class AnimatedBar extends Component {
constructor(props) {
super(props);
this._width = new Animated.Value(0);
this.state = {
color: randomcolor(),
};
}
componentDidMount() {
this.animateTo( this.props.value);
}
componentWillReceiveProps(nextProps) {
this.animateTo( nextProps.value);
}
animateTo = (value) => {
// Animated.sequence([
// Animated.delay(delay),
// Animated.timing(this._width, {
// toValue: value,
// }),
// ]).start();
Animated.timing(this._width, {
toValue: value,
}).start();
}
render() {
const barStyles = {
backgroundColor: this.state.color,
height: this._width,
width:40,
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
};
return (
<Animated.View style={barStyles} />
);
}
}
Animated.timing(this._width, {
toValue: value,
}).start();
If you will pass the positive value to the toValue variable then bar will go downward and if you will pass the negative value toValue variable then bar will go upward.
Try this by passing negative value to the toValue like :
Animated.timing(this._width, {
toValue: -30,
}).start();
Related
I have a expo app written with react-navigation ^3.12.0
I have a theme selection on my app, meaning you can click on color circle, and every screen on the app will have the background color whatever you chose, however the react-navigation doesn't change the header color, react-navigation only changes color accordingly if you navigate to different screen and go back to the screen where you can choose theme colors.
This is my code.
class AccountScreen extends Component {
static navigationOptions = ({navigation}) => {
const {params} = navigation.state;
return {
title: navigation.getParam("otherParam", "Account"),
headerTintColor: "white",
headerStyle: {
elevation: 0,
shadowOpacity: 0,
borderBottomWidth: 0,
backgroundColor: navigation.getParam("themeBackgroundColor"),
},
headerLeft: (
< TouchableOpacity
style = {
{
paddingLeft: 15
}
}
onPress = {()
=>
navigation.dispatch(DrawerActions.toggleDrawer())
}
>
<
Feather
name = "arrow-left"
size = {24}
color = "#ffffff" / >
< /TouchableOpacity>
),
headerRight: <
View
style = {
{
flexDirection: "row"
}
}><
/View>,
}
;
};
constructor(props) {
super(props);
this.state = {
loading: false,
};
}
componentDidMount() {
// https://github.com/facebook/react-native/issues/12981
YellowBox.ignoreWarnings(["Setting a timer"]);
const {theme, navigation} = this.props;
navigation.setParams({
themeBackgroundColor: theme.backgroundColor,
});
}
render() {
renderItem = ({item}) => (
< TouchableOpacity
onPress = {()
=>
this.props.setTheme(item.key)
}>
<
View
style = {
[
style.itemContainer,
{
backgroundColor: item.backgroundColor,
}
,
]
}
/>
< /TouchableOpacity>
)
;
return (
< FlatList
style = {
[
style.container,
{
backgroundColor: this.props.theme.backgroundColor
}
,
]
}
data = {this.props.themes}
numColumns = {3}
contentContainerStyle = {
{
flexGrow: 1,
justifyContent
:
"center",
left
:
"14%",
}
}
renderItem = {renderItem}
/>
)
;
}
}
Do I need to use redux? Please advise.
Edit:
This is where i handle color selection
<TouchableOpacity onPress={() => this.props.setTheme(item.key)}>
<View
style={[
style.itemContainer,
{
backgroundColor: item.backgroundColor,
},
]}
/>
</TouchableOpacity>
Ciao, try to modify your code like:
static navigationOptions = ({ navigation }) => {
const {params} = navigation.state;
return {
...
headerStyle: {
...
backgroundColor: navigation.getParam('BackgroundColor', '#ED2525'), // replace themeBackgroundColor with BackgroundColor
// #ED2525 is a default value, you can remove it if you don't need
},
...
};
};
Then on componentDidMount:
componentDidMount() {
// https://github.com/facebook/react-native/issues/12981
YellowBox.ignoreWarnings(["Setting a timer"]);
const {theme, navigation} = this.props;
navigation.setParams({
BackgroundColor: theme.backgroundColor,
});
}
Should solve your problem.
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
I need to change the selectedDay to the current day every time the user navigates to the calendar screen.
I set the selected date as today and can already detect when the user acesses the screen, but even when I change the state or force update, the calendar doesn't move to the date I set as selected.
componentDidUpdate = async (prevProps) => {
if (prevProps.isFocused !== this.props.isFocused && this.props.isFocused) {
this.forceUpdate()
}
}
and
<Agenda
items={events}
pastScrollRange={50}
futureScrollRange={50}
onDayPress={this.setCurrentDate}
loadItemsForMonth={this.loadItems}
renderItem={this.renderItem}
renderEmptyDate={this.renderEmptyDate}
rowHasChanged={this.rowHasChanged}
selected={this.state.today}
/>
With forceUpdate or changing some arbitrary state, the calendar stays in the currently selected date. I wanted it to go back to today.
You can set the ref for Agenda component
ref={ref => {
this.agenda = ref;
}}
Then on componentDidUpdate, change date to current date by calling function onDayChange of Agenda component
if (prevProps.isFocused !== this.props.isFocused) {
setTimeout(() => {
this.agenda.onDayChange(this.state.today);
}, 500);
Complete Code
import React, { Component } from "react";
import { Agenda } from "react-native-calendars";
import { Text, View, StyleSheet } from "react-native";
import { withNavigationFocus } from "react-navigation";
class Home extends Component {
constructor(props) {
super(props);
this.state = {
items: {},
today: new Date().toISOString().split("T")[0]
};
}
componentDidUpdate(prevProps) {
if (prevProps.isFocused !== this.props.isFocused) {
setTimeout(() => {
this.agenda.onDayChange(this.state.today);
}, 500);
}
}
render() {
return (
<View style={{ flex: 1 }}>
<Text
style={{ padding: 30, fontWeight: "bold", textAlign: "center" }}
onPress={() => this.props.navigation.navigate("NewScreen")}
>
Go To Next Screen
</Text>
<Agenda
items={this.state.items}
loadItemsForMonth={this.loadItems.bind(this)}
selected={this.state.today}
renderItem={this.renderItem.bind(this)}
renderEmptyDate={this.renderEmptyDate.bind(this)}
rowHasChanged={this.rowHasChanged.bind(this)}
onDayPress={day => {
console.log("selected day", day);
}}
ref={ref => {
this.agenda = ref;
}}
/>
</View>
);
}
loadItems(day) {
setTimeout(() => {
for (let i = -15; i < 85; i++) {
const time = day.timestamp + i * 24 * 60 * 60 * 1000;
const strTime = this.timeToString(time);
if (!this.state.items[strTime]) {
this.state.items[strTime] = [];
const numItems = Math.floor(Math.random() * 5);
for (let j = 0; j < numItems; j++) {
this.state.items[strTime].push({
name: "Item for " + strTime,
height: Math.max(50, Math.floor(Math.random() * 150))
});
}
}
}
//console.log(this.state.items);
const newItems = {};
Object.keys(this.state.items).forEach(key => {
newItems[key] = this.state.items[key];
});
this.setState({
items: newItems
});
}, 1000);
// console.log(`Load Items for ${day.year}-${day.month}`);
}
renderItem(item) {
return (
<View style={[styles.item, { height: item.height }]}>
<Text>{item.name}</Text>
</View>
);
}
renderEmptyDate() {
return (
<View style={styles.emptyDate}>
<Text>This is empty date!</Text>
</View>
);
}
rowHasChanged(r1, r2) {
return r1.name !== r2.name;
}
timeToString(time) {
const date = new Date(time);
return date.toISOString().split("T")[0];
}
}
const styles = StyleSheet.create({
item: {
backgroundColor: "white",
flex: 1,
borderRadius: 5,
padding: 10,
marginRight: 10,
marginTop: 17
},
emptyDate: {
height: 15,
flex: 1,
paddingTop: 30
}
});
export default withNavigationFocus(Home);
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
i make a side menu,it's ok on genymotion,but not work on my phone. it's response is delay more than 10s and most times not response on phone。please help me !
At the beginning ,I think it's flow reason:
1、position: 'absolute'
2、PanResponder wrapper a touchable
i had try clean this,but it's also not work。maybe its a bug ,are you ?
enter image description here
import React, {Component} from 'react'
import {
View,
Text,
StyleSheet,
ScrollView,
Alert,
PanResponder,
TouchableOpacity
} from 'react-native'
import {get_pointBaikeCate} from 'api'
import {Touchable} from 'basic'
export default class PointBaike extends Component {
static navigationOptions = {
title: '穴位百科',
header: null
}
constructor(props) {
super(props);
this.state = {
info: []
}
this.scrollY = []
}
async getInfo() {
let res = await get_pointBaikeCate();
console.log(res)
// let info = [];
// res.data.map((r)=> {
// r.content.map(c=> {
// let flag = info.find((item)=> {
// return item.jl == c.jl
// })
// if (flag) {
// flag.xw += `,${c.xw}`
// } else {
// info.push({
// jl: c.jl,
// xw: c.xw
// })
// }
// })
// });
//
// // 对info的xw进行过滤
// info = info.map((item)=> {
// let xw = item.xw.split(/[,|]/)
// item.xw = [...new Set(xw)].filter((x)=> {
// return x
// })
// return item
// });
this.setState({
info: res.data
})
}
goDetail(item) {
let {navigate} = this.props.navigation
console.log(item)
navigate('PointBaikeDetail', {
item
})
}
componentWillMount() {
this.getInfo()
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder.bind(this),
onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture.bind(this),
onMoveShouldSetPanResponder: this._handlerMoveShouldSetPanResponder.bind(this),
onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture.bind(this),
onPanResponderTerminationRequest: this._handleMoveShouldSetPanResponderCapture,
onPanResponderMove: this._handlePanResponderMove.bind(this),
});
}
_handlerMoveShouldSetPanResponder(evt, gestureState){
if (gestureState.dx != 0 && gestureState.dy == 0) {
return true;
}
return false;
}
_handleMoveShouldSetPanResponderCapture(evt, gestureState) {
return gestureState.dx != 0 && gestureState.dy != 0
}
_handleStartShouldSetPanResponderCapture(evt, gestureState) {
return gestureState.dx != 0 && gestureState.dy != 0;
}
_handleMoveShouldSetPanResponderCapture(evt, gestureState) {
return gestureState.dx != 0 && gestureState.dy != 0;
}
_scrollTo(index) {
// Alert.alert('索引', `${index}`)
this._scrollView.scrollTo({y: this.scrollY[index]})
}
_handleStartShouldSetPanResponder() {
return true
}
_handlePanResponderMove(e, gestureState) {
// console.log('滑动', e.nativeEvent.pageY)
// 计算手指在那个元素上,得出index,然后根据index设置scrollTop
let y = e.nativeEvent.pageY - 100
let index = Math.ceil(y / 20) - 1
console.log(index, this)
this._scrollView.scrollTo({y: this.scrollY[index]})
}
_onLayout({nativeEvent}) {
this.scrollY.push(nativeEvent.layout.y)
}
render() {
let {info} = this.state
return (
<View style={styles.wrapper}>
<ScrollView style={{flex: 1, height: 300}}
showsVerticalScrollIndicator={false}
ref={(e)=> {
this._scrollView = e
}}
>
{
info.map((item, i)=> {
return (
<View key={i} style={styles.lists}
onLayout={this._onLayout.bind(this)}
>
<View>
<Text style={styles.title}>{item.title}</Text>
</View>
<View style={styles.listBox}>
{
item.list.map((x, xi)=> {
return (
<Touchable key={xi}
onPress={this.goDetail.bind(this, x)}
>
<View style={styles.list}>
<Text
style={styles.text}>{x.title}</Text>
</View>
</Touchable>
)
})
}
</View>
</View>
)
})
}
<View style={{height: 100}}></View>
</ScrollView>
<View style={styles.sideMenu}
{...this._panResponder.panHandlers}
>
{
info.map((item, i)=> {
return (
<TouchableOpacity key={i} onPress={this._scrollTo.bind(this, i)}>
<View>
<Text
style={styles.sideText}>{item.title.charAt(3) || item.title.charAt(0)}</Text>
</View>
</TouchableOpacity>
)
})
}
</View>
</View>
)
}
}
const styles = StyleSheet.create({
wrapper: {
paddingLeft: 6,
paddingRight: 6,
paddingTop: 10,
paddingBottom: 10,
flex: 1,
backgroundColor: '#fff'
},
sideText: {
width: 25,
height: 20,
lineHeight: 20,
textAlign: 'center'
},
sideMenu: {
position: 'absolute',
right: 0,
top: 100,
zIndex: 100,
backgroundColor: '#eee'
},
lists: {},
listBox: {
flexDirection: 'row',
flexWrap: 'wrap'
},
list: {
width: (WinWidth - 20) / 5,
},
title: {
fontSize: 18,
color: DEFAULT_COLOR,
paddingTop: 14,
paddingBottom: 10
},
text: {
flex: 1,
textAlign: 'center',
paddingTop: 10,
paddingBottom: 10,
}
})
you can try this
componentWillMount() {
this.getInfo()
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture.bind(this),
onMoveShouldSetPanResponder: this._handlerMoveShouldSetPanResponder.bind(this),
onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture.bind(this),
onPanResponderTerminationRequest: this._handleMoveShouldSetPanResponderCapture,
onPanResponderMove: this._handlePanResponderMove.bind(this),
});
}