I'm trying to animate some components on scroll, using
const scrollY = useRef(new Animated.Value(0)).current; for tracking the value.
This code below works:
<Animated.ScrollView
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: {
y: scrollY,
},
},
},
],
{
useNativeDriver: false,
}
)}
></Animated.ScrollView>;
But I want to be able to extract Animated.event function within the onScroll prop to its own independent function. This code below doesnt seem to update scrollY:
// My onScroll handler
const handleScroll = ({ nativeEvent }) => {
// Some other code here...
return Animated.event(
[
{
nativeEvent: {
contentOffset: {
y: scrollY,
},
},
},
],
{
useNativeDriver: false,
}
);
};
// Scrollview component
<Animated.ScrollView onScroll={handleScroll}></Animated.ScrollView>;
Not sure if you ever figured it out, but this will work:
const handleScroll =
scrollY && (() => {
console.log('hey');
return Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }], {
useNativeDriver: true,
});
}
)();
Related
Code for Animated Flatlist
const scrollY = useRef(new Animated.Value(0)).current;
const keyExtractorItem = useCallback((item) => item.id.toString(), []);
const anEvent = Animated.event(
[
{
nativeEvent: {
contentOffset: {
y: scrollY,
},
},
},
{ useNativeDriver: true }
],
);
<AnimatedFlashList
onScroll={anEvent}
contentContainerStyle={{ paddingTop: StatusBar.currentHeight || 42 }}
data={DATA}
keyExtractor={keyExtractorItem}
renderItem={renderItem}
estimatedItemSize={20}
/>
Error Occured While Scrolling
Event has less arguments than mapping
Warning Shown
ANIMATED.EVENT NOW REQUIRES A SECOND ARGUMENT FOR OPTIONS
Error Solved
const anEvent = Animated.event(
[
{
nativeEvent: {
contentOffset: {
y: scrollY,
},
},
},
],
{ useNativeDriver: true }
);
UseNativeDriver should be below the array.
When a user try to hover on a dot, it only displays a blue line but it does not show a tooltip as a result below
lib version:
"chart.js": "^2.9.3",
"chartjs-plugin-annotation": "^0.5.7"
Actual : enter image description here
Expected: enter image description here In chart configuration
export chart = () => {
data:{ ... },
options: {
tooltips: {
displayColors: false,
mode: 'index', intersect: true,
callbacks: {
label: function (tooltipItem, data) {
return `${data.datasets[tooltipItem.datasetIndex].label}(${Math.round(tooltipItem.xLabel * 100) / 100
},${Math.round(tooltipItem.yLabel * 100) / 100})`;
},
},
},
legend: {
display: false,
},
annotation: {
drawTime: "afterDatasetsDraw",
events: ["mouseover"],
annotations: [],
},
}
}
convertToHoverLine();
export const convertToHoverLine = (value, scaleID) => {
return {
key: "hoverLine",
type: "line",
mode: "vertical",
scaleID,
value,
borderColor: "blue",
onMouseout: null,
onMouseover: null,
}
}
handleHoverChart(); => this function will trigger when a user hover a dot on the chart
export const handleHoverChart = (myChart, x, scaleID) => {
const indexLine = myChart.options.annotation.annotations.findIndex(i => i.key === "hoverLine")
if (indexLine === -1) {
myChart.options.annotation.annotations.push(convertToHoverLine(x,scaleID))
} else {
myChart.options.annotation.annotations[indexLine] = convertToHoverLine(x,scaleID);
}
myChart.update()
}
I solved this problem by move chart update into the condition
export const handleHoverChart = (myChart, x, scaleID) => {
const indexLine = myChart.options.annotation.annotations.findIndex(i => i.key === "hoverLine")
if(indexLine === -1){
myChart.options.annotation.annotations.push(convertToHoverLine(x,scaleID))
myChart.update()
}else{
myChart.options.annotation.annotations[indexLine] = convertToHoverLine(x,scaleID);
}
}
I am using React Native Navigation v2 from WIX in my RN project. For Dashboard(goToDahboard) stack I need to show hamburger icon on the left on on click show side drawer. How can this be implemented?
Since upgrading from v1, side menu options has changed and the docs aren't clear enough.
export const goToDashboard = () =>
Promise.all([
Icon.getImageSource('home', 22, '#272727'),
Icon.getImageSource('th-list', 22, '#272727'),
]).then(sources => {
Navigation.setRoot({
root: {
bottomTabs: {
children: [
{
stack: {
children: [
{
component: {
name: 'Dashboard',
},
},
],
options: {
bottomTab: {
icon: sources[0],
text: 'Dashboard',
},
},
},
},
{
stack: {
children: [
{
component: {
name: 'Settings',
},
},
],
options: {
bottomTab: {
icon: sources[1],
text: 'Settings',
},
},
},
},
],
id: 'bottomTabs',
},
},
});
});
export const goToAuth = () =>
Navigation.setRoot({
root: {
stack: {
id: 'Login',
children: [
{
component: {
name: 'Login',
},
},
],
},
},
});
I'am using like this, thats my code;
Navigation.setRoot({
root:{
sideMenu:{
left:{
component:{
name:'app.Main.SideDrawer'
}
},
center:{
bottomTabs:{
id: 'MainAppBottomTab',
children:[
{
stack:{
children:[
{
component:{
name: 'app.Main.Bottom_1',
options:{
bottomTab:{
text: "Bottom 1",
icon: require('./../../assets/images/Bottom_1.png'),
}
},
}
}
],
options: {
bottomTab: {
text: 'Bottom 1',
},
bottomTabs:{
backgroundColor: ColorTable.orange,
animate:false,
},
topBar:{
title:{
text: 'Bottom 1',
},
leftButtons:[
{
id: 'btn_toggle_drawer',
name: 'BTN_TOGGLE_DRAWER',
icon: require('./../../assets/images/hamburger_icon.png'),
}
],
}
}
}
}
]
}
}
}
}
});
Now we need to use wix's life cycle.
If you want to close it in SideDrawer, you should use the following code;
Navigation.mergeOptions(this.props.componentId, {
sideMenu: {
left: {
visible: false
}
}
});
this.props.componentId equal to app.Main.SideDrawer. Because of we are in app.Main.SideDrawer component.
If you want to open with hamburger icon, Go to whatever page you want to use for bottomTab, in our example I said Bottom_1.
Don't forget to type Navigation.events().bindComponent(this) into the constructor method. This allows you to linking with native.
Only the following command will work;
navigationButtonPressed({buttonId}) {
if (buttonId === "btn_toggle_drawer") {
Navigation.mergeOptions(this.props.componentId, {
sideMenu: {
left: {
visible: true
}
}
});
}
}
The code above works but is problematic. You're going to tell me that I'm going to have to press twice to turn it off =)
The solution is to use redux. Or mobx whichever you prefer.
To solve this problem, I used redux and redux-thunk.
Wix's is life cycle, please explore it: https://wix.github.io/react-native-navigation/#/docs/Usage?id=screen-lifecycle
With redux solution
Real function is;
navigationButtonPressed({buttonId}) {
if (buttonId === "btn_toggle_drawer") {
this.props.toggleDrawer(this.props.isSideDrawerVisible);
Navigation.mergeOptions(this.props.componentId, {
sideMenu: {
left: {
visible: this.props.isSideDrawerVisible
}
}
});
}
}
toggle_drawer action
export const toggleDrawer = (visible) => {
return (dispatch) => {
(visible) ? visible = true : visible = false;
dispatch({
type: TOGGLE_DRAWER,
payload: visible
});
}
};
toggle_drawer reducer
const INITIAL_STATE = {
isSideDrawerVisible: true
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case TOGGLE_DRAWER:
return {...state, isSideDrawerVisible: action.payload};
default:
return state;
}
}
Sample connect function;
import {connect} from "react-redux";
// actions
import {toggleDrawer} from "../../../actions/toggle_drawer";
const mapStateToProps = state => {
return {
isSideDrawerVisible: state.toggleDrawer.isSideDrawerVisible,
}
};
export default connect(mapStateToProps, {toggleDrawer})(Bottom_1_Screen);
Don't forget to connect wix with Redux. https://wix.github.io/react-native-navigation/#/docs/third-party?id=redux
I hope I can help you, I saw it a little late.
Have a nice day..
You can use
Navigation.mergeOptions()
like this to fire and open the drawer:
navigationButtonPressed = ({ buttonId }) => {
const { componentId } = this.props;
if (buttonId === 'sideMenu') {
Navigation.mergeOptions(componentId, {
sideMenu: {
left: {
visible: true,
},
},
});
}
}
I have an animated component where you can select one of seventeen circles. It looks like this so far:
I would like to add an animation that scales the circle as it gets closer to the center. How do I do that?
Until now I tried to calculate the x value of the circle as Math.sin(index*deltaTheta*Math.PI/180 + Math.PI)*Radius and use this value in a functions which maps to a scaling factor (e.g. a gaussian). This fails because the x value does not change, because I am using CSS transform rotate.
Then I tried to use a different interpolating range for every single circle, but did not achieved a satisfying result.
My code:
import React, { Component } from 'react'
import { Text, View, PanResponder, Animated, Dimensions } from 'react-native'
import styled from 'styled-components'
import Circle from './Circle'
const SCREEN_WIDTH = Dimensions.get('window').width
const Container = styled(Animated.View)`
margin: auto;
width: 200px;
height: 200px;
position: relative;
top: 100px;
`
const gaussFunc = (x, sigma, mu) => {
return 1/sigma/Math.sqrt(2.0*Math.PI)*Math.exp(-1.0/2.0*Math.pow((x-mu)/sigma,2))
}
const myGaussFunc = (x) => gaussFunc(x, 1/2/Math.sqrt(2*Math.PI), 0)
const circles = [{
color: 'red'
}, {
color: 'blue'
}, {
color: 'green'
}, {
color: 'yellow'
}, {
color: 'purple'
}, {
color: 'black'
}, {
color: 'gray'
}, {
color: 'pink'
}, {
color: 'lime'
}, {
color: 'darkgreen'
}, {
color: 'crimson'
}, {
color: 'orange'
}, {
color: 'cyan'
}, {
color: 'navy'
}, {
color: 'indigo'
}, {
color: 'brown'
}, {
color: 'peru'
}
]
function withFunction(callback) {
let inputRange = [], outputRange = [], steps = 50;
/// input range 0-1
for (let i=0; i<=steps; ++i) {
let key = i/steps;
inputRange.push(key);
outputRange.push(callback(key));
}
return { inputRange, outputRange };
}
export default class SDGCircle extends Component {
state = {
deltaTheta: 360/circles.length,
Radius: 0, // radius of center circle (contaienr)
radius: 25, // radius of orbiting circles
container: { height: 0, width: 0 },
deltaAnim: new Animated.Value(0),
}
offset = () => parseInt(this.state.container.width/2)-this.state.radius
_panResponder = PanResponder.create({
nMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (event, gestureState) => true,
onPanResponderGrant: () => {
const { deltaAnim } = this.state
deltaAnim.setOffset(deltaAnim._value)
deltaAnim.setValue(0)
},
onPanResponderMove: (event, gestureState) => {
const { deltaAnim, scaleAnim, deltaTheta, Radius } = this.state
deltaAnim.setValue(gestureState.dx)
console.log(deltaAnim)
},
onPanResponderRelease: (event, gestureState) => {
const {dx, vx} = gestureState
const {deltaAnim} = this.state
deltaAnim.flattenOffset()
Animated.spring(deltaAnim, {
toValue: this.getIthCircleValue(dx, deltaAnim),
friction: 5,
tension: 10,
}).start(() => this.simplifyOffset(deltaAnim._value));
}
})
getIthCircleValue = (dx, deltaAnim) => {
const selectedCircle = Math.round(deltaAnim._value/(600/circles.length))
return (selectedCircle)*600/circles.length
}
getAmountForNextSlice = (dx, offset) => {
// This just rounds to the nearest 200 to snap the circle to the correct thirds
const snappedOffset = this.snapOffset(offset);
// Depending on the direction, we either add 200 or subtract 200 to calculate new offset position. (200 are equal to 120deg!)
// const newOffset = dx > 0 ? snappedOffset + 200 : snappedOffset - 200; // fixed for 3 circles
const newOffset = dx > 0 ? snappedOffset + 600/circles.length : snappedOffset - 600/circles.length;
return newOffset;
}
snapOffset = (offset) => { return Math.round(offset / (600/circles.length)) * 600/circles.length; }
simplifyOffset = (val) => {
const { deltaAnim } = this.state
if(deltaAnim._offset > 600) deltaAnim.setOffset(deltaAnim._offset - 600)
if(deltaAnim._offset < -600) deltaAnim.setOffset(deltaAnim._offset + 600)
}
handleLayout = ({ nativeEvent }) => {
this.setState({
Radius: nativeEvent.layout.width,
container: {
height: nativeEvent.layout.height,
width: nativeEvent.layout.width
}
})
}
render() {
const {deltaAnim, radius} = this.state
return (
<Container
onLayout={this.handleLayout}
{...this._panResponder.panHandlers}
style={{
transform: [{
rotate: deltaAnim.interpolate({
inputRange: [-200, 0, 200],
outputRange: ['-120deg', '0deg', '120deg']
})
}]
}}
>
{circles.map((circle, index) => {
const {deltaTheta, Radius} = this.state
return (
<Circle
key={index}
color={circle.color}
radius={radius}
style={{
left: Math.sin(index*deltaTheta*Math.PI/180 + Math.PI)*Radius+this.offset(),
top: Math.cos(index*deltaTheta*Math.PI/180 + Math.PI)*Radius+this.offset(),
}}
>
<Text style={{color: 'white'}}>{index}</Text>
</Circle>
)
})}
</Container>
)
}
}
FYI: I got a solution. The result looks like this:
and the source code is given by:
import React, { Component } from 'react'
import { Text, View, PanResponder, Animated, Dimensions } from 'react-native'
import styled from 'styled-components'
import Circle from './Circle'
const SCREEN_WIDTH = Dimensions.get('window').width
const Container = styled(Animated.View)`
margin: auto;
width: 200px;
height: 200px;
position: relative;
top: 100px;
`
const gaussFunc = (x, sigma, mu) => {
return 1/sigma/Math.sqrt(2.0*Math.PI)*Math.exp(-1.0/2.0*Math.pow((x-mu)/sigma,2))
}
const myGaussFunc = (x) => gaussFunc(x, 1/2/Math.sqrt(2*Math.PI), 0)
const circles = [{
color: 'red'
}, {
color: 'blue'
}, {
color: 'green'
}, {
color: 'yellow'
}, {
color: 'purple'
}, {
color: 'black'
}, {
color: 'gray'
}, {
color: 'pink'
}, {
color: 'lime'
}, {
color: 'darkgreen'
}, {
color: 'crimson'
}, {
color: 'orange'
}, {
color: 'cyan'
}, {
color: 'navy'
}, {
color: 'indigo'
}, {
color: 'brown'
}, {
color: 'peru'
}]
function withFunction(callback) {
let inputRange = [], outputRange = [], steps = 50;
/// input range 0-1
for (let i=0; i<=steps; ++i) {
let key = i/steps;
inputRange.push(key);
outputRange.push(callback(key));
}
return { inputRange, outputRange };
}
export default class SDGCircle extends Component {
constructor(props) {
super(props)
const deltaTheta = 360/circles.length
const pxPerDeg = 200/120
const thetas = []
for (const i in circles) {
let val = i*deltaTheta*pxPerDeg
if(i >= 9)
val = -(circles.length-i)*deltaTheta*pxPerDeg
thetas.push(val)
}
this.state = {
deltaTheta,
Radius: 0, // radius of center circle (contaienr)
radius: 25, // radius of orbiting circles
container: { height: 0, width: 0 },
deltaAnim: new Animated.Value(0),
thetas,
thetasAnim: thetas.map(theta => new Animated.Value(theta)),
}
}
offset = () => parseInt(this.state.container.width/2)-this.state.radius
_panResponder = PanResponder.create({
nMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (event, gestureState) => true,
onPanResponderGrant: () => {
const { deltaAnim, thetasAnim, thetas } = this.state
deltaAnim.setOffset(deltaAnim._value)
deltaAnim.setValue(0)
const iSel = Math.round((deltaAnim._value+deltaAnim._offset)/(600/circles.length))
for(let i=0; i<circles.length; i++) {
let xi = i+iSel
if(xi > 16)
xi -= circles.length
if(xi < 0)
xi += circles.length
try {
thetasAnim[xi].setOffset(thetas[i])
} catch(err) {console.log(xi)}
}
},
onPanResponderMove: (event, gestureState) => {
const { deltaAnim, scaleAnim, deltaTheta, Radius, thetasAnim } = this.state
deltaAnim.setValue(gestureState.dx)
for (theta of thetasAnim) {
theta.setValue(-gestureState.dx)
}
},
onPanResponderRelease: (event, gestureState) => {
const {dx, vx} = gestureState
const {deltaAnim, thetasAnim, deltaTheta, thetas} = this.state
deltaAnim.flattenOffset()
const ithCircleValue = this.getIthCircleValue(dx, deltaAnim)
Animated.spring(deltaAnim, {
toValue: ithCircleValue,
friction: 5,
tension: 10,
}).start(() => {
this.simplifyOffset(deltaAnim)
});
}
})
getIthCircleValue = (dx, deltaAnim) => {
const selectedCircle = Math.round((deltaAnim._value+deltaAnim._offset)/(600/circles.length))
return (selectedCircle)*600/circles.length
}
snapOffset = (offset) => { return Math.round(offset / (600/circles.length)) * 600/circles.length; }
simplifyOffset = (anim) => {
if(anim._value + anim._offset >= 600) anim.setOffset(anim._offset - 600)
if(anim._value + anim._offset <= -600) anim.setOffset(anim._offset + 600)
}
handleLayout = ({ nativeEvent }) => {
this.setState({
Radius: nativeEvent.layout.width,
container: {
height: nativeEvent.layout.height,
width: nativeEvent.layout.width
}
})
}
render() {
const {deltaAnim, radius} = this.state
return (
<Container
onLayout={this.handleLayout}
{...this._panResponder.panHandlers}
style={{
transform: [{
rotate: deltaAnim.interpolate({
inputRange: [-200, 0, 200],
outputRange: ['-120deg', '0deg', '120deg']
})
}]
}}
>
{circles.map((circle, index) => {
const {deltaTheta, thetasAnim, Radius} = this.state
/* const difInPx = index*deltaTheta*200/120 */
let i = index
/* if(index >= Math.round(circles.length/2)) */
/* i = circles.length - index */
scale = thetasAnim[i].interpolate({
inputRange: [-300, 0, 300],
outputRange: [0, 2, 0],
})
return (
<Circle
key={index}
color={circle.color}
radius={radius}
style={{
left: Math.sin(index*deltaTheta*Math.PI/180 + Math.PI)*Radius+this.offset(),
top: Math.cos(index*deltaTheta*Math.PI/180 + Math.PI)*Radius+this.offset(),
transform: [{ scale }],
}}
>
<Text style={{color: 'white'}}>{index}</Text>
</Circle>
)
})}
</Container>
)
}
}
How I can call both function from onScroll?
I trying this
onScroll={
(event) => console.log(event),
Animated.event(
[
{ nativeEvent:
{
contentOffset: { y: this.state.scrollY },
},
},
])
}
but it's not work together, only separate. For example, If I comment ether console.log or Animated.event there are working.
ops, got it
onScroll={
Animated.event(
[
{ nativeEvent:
{
contentOffset: { y: this.state.scrollY },
},
},
],
{ listener: (event) => console.log(event.nativeEvent.contentOffset.y) }
)
}