Related
I have an Animated View to which in transform prop I'm setting the translateX values. It doesn't seem to be working though. I debugged the translateX value and it seems to be updating, but the View doesn't seem to.
react-native-reanimated version: 1.13.1, react-native-gesture-handler version: 1.10.3
function Cursor() {
const translateX = new Value(0)
const gestureState = new Value(State.UNDETERMINED)
const onGestureEvent = event([{
nativeEvent: {
translationX: translateX,
state: gestureState
}
}], { useNativeDriver: true })
return (
<PanGestureHandler {...{ onGestureEvent }} onHandlerStateChange={onGestureEvent}>
<Animated.View
style={{
...StyleSheet.absoluteFillObject,
width: height,
height: height,
borderRadius: height / 2,
backgroundColor: "white",
transform: [{ translateX }],
}}
/>
</PanGestureHandler>
)
}
I am trying to rotate an icon on press of a button in my react native application.
But I am getting this error:
Error while updating property 'transform' of a view managed by:
RCTView
This is the Icon itself:
<TouchableOpacity
style={[
styles.expandButton,
{transform: [{rotateX: toString(expandIconAngle) + 'deg'}]},
]}
onPress={() => {
rotateIcon();
}}>
<Icon name="expand-less" color="#ffffff" size={28} />
</TouchableOpacity>
This is the function which is responsible for rotating the icon:
const expandIconAngle = useRef(new Animated.Value(0)).current;
function rotateIcon() {
Animated.timing(expandIconAngle, {
toValue: 180,
duration: 300,
easing: Easing.linear,
}).start();
}
Where am I going wrong?
use interpolate and Animated.Image :
import React, { useRef } from "react";
import { Animated, Text, View, StyleSheet, Button, SafeAreaView,Easing,TouchableOpacity } from "react-native";
const App = () => {
// fadeAnim will be used as the value for opacity. Initial Value: 0
const angle = useRef(new Animated.Value(0)).current;
const fadeOut = () => {
// Will change fadeAnim value to 0 in 3 seconds
Animated.timing(
angle,
{
toValue: 1,
duration: 3000,
easing: Easing.linear, // Easing is an additional import from react-native
useNativeDriver: true // To make use of native driver for performance
}
).start()
};
const spin =angle.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
})
return (
<SafeAreaView style={styles.container}>
<Animated.Image
style={{transform: [{rotateX: spin}] }}
source={require('#expo/snack-static/react-native-logo.png')} />
<TouchableOpacity onPress={fadeOut} style={styles.buttonRow}>
<Text>Button</Text>
</TouchableOpacity>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center"
},
buttonRow: {
flexBasis: 100,
justifyContent: "space-evenly",
marginVertical: 16
}
});
export default App;
LIVE example - https://snack.expo.dev/TP-fQExXT
In the following simple slider (typescript) example the PanGestureHandler from react-native-gesture-handler will only be set after the gesture was started. The user would need to move the finger.
This is what I would like to achieve: Taps should also set the slider value (including tap and drag). This is a common pattern, e.g. when seeking through a video file or setting volume to max and then adjusting.
I guess I could wrap this in a TapGestureHandler but I'm seeking the most elegant way to achieve this without too much boilerplate.
// example extracted from https://www.npmjs.com/package/react-native-reanimated-slider
import React, { Component } from 'react';
import Animated from 'react-native-reanimated';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
const { Value, event, cond, eq, Extrapolate, interpolate } = Animated;
interface IProps {
minimumTrackTintColor: string;
maximumTrackTintColor: string;
cacheTrackTintColor: string;
value: number;
style: any;
cache;
onSlidingStart;
onSlidingComplete;
}
class Slider extends Component<IProps, {}> {
static defaultProps = {
minimumTrackTintColor: '#f3f',
maximumTrackTintColor: 'transparent',
cacheTrackTintColor: '#777',
};
private gestureState;
private x;
private width;
private clamped_x;
private onGestureEvent;
public constructor(props: IProps) {
super(props);
this.gestureState = new Value(State.UNDETERMINED);
this.x = new Value(0);
this.width = new Value(0);
this.clamped_x = cond(
eq(this.width, 0),
0,
interpolate(this.x, {
inputRange: [0, this.width],
outputRange: [0, this.width],
extrapolate: Extrapolate.CLAMP,
})
);
this.onGestureEvent = event([
{
nativeEvent: {
state: this.gestureState,
x: this.x,
},
},
]);
}
onLayout = ({ nativeEvent }) => {
this.width.setValue(nativeEvent.layout.width);
};
render() {
const { style, minimumTrackTintColor, maximumTrackTintColor } = this.props;
return (
<PanGestureHandler
onGestureEvent={this.onGestureEvent}
onHandlerStateChange={this.onGestureEvent}
>
<Animated.View
style={[
{
flex: 1,
height: 30,
overflow: 'visible',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#3330',
},
style,
]}
onLayout={this.onLayout}
>
<Animated.View
style={{
width: '100%',
height: 5,
borderRadius: 2,
overflow: 'hidden',
borderWidth: 1,
backgroundColor: maximumTrackTintColor,
}}
>
<Animated.View
style={{
backgroundColor: minimumTrackTintColor,
height: '100%',
maxWidth: '100%',
width: this.clamped_x,
position: 'absolute',
}}
/>
</Animated.View>
</Animated.View>
</PanGestureHandler>
);
}
}
export default Slider;
Thanks in advance!
Edit: This works as intended, but has a visible render quirk and also a small delay.
import React, { Component } from 'react';
import Animated from 'react-native-reanimated';
import { PanGestureHandler, TapGestureHandler, State } from 'react-native-gesture-handler';
const { Value, event, cond, eq, Extrapolate, interpolate } = Animated;
interface IProps {
minimumTrackTintColor?: string;
maximumTrackTintColor?: string;
cacheTrackTintColor?: string;
value: number;
style?: any;
onSlidingStart;
onSlidingComplete;
}
class Slider extends Component<IProps, {}> {
static defaultProps = {
minimumTrackTintColor: '#f3f',
maximumTrackTintColor: 'transparent',
cacheTrackTintColor: '#777',
};
private gestureState;
private x;
private width;
private clamped_x;
private onGestureEvent;
private onTapGesture;
public constructor(props: IProps) {
super(props);
this.gestureState = new Value(State.UNDETERMINED);
this.x = new Value(0);
this.width = new Value(0);
this.clamped_x = cond(
eq(this.width, 0),
0,
interpolate(this.x, {
inputRange: [0, this.width],
outputRange: [0, this.width],
extrapolate: Extrapolate.CLAMP,
})
);
this.onGestureEvent = event([
{
nativeEvent: {
state: this.gestureState,
x: this.x,
},
},
]);
this.onTapGesture = event([
{
nativeEvent: {
state: this.gestureState,
x: this.x,
},
},
]);
}
onLayout = ({ nativeEvent }) => {
this.width.setValue(nativeEvent.layout.width);
};
render() {
const { style, minimumTrackTintColor, maximumTrackTintColor } = this.props;
return (
<TapGestureHandler
onGestureEvent={this.onTapGesture}
onHandlerStateChange={this.onTapGesture}
>
<Animated.View>
<PanGestureHandler
onGestureEvent={this.onGestureEvent}
onHandlerStateChange={this.onGestureEvent}
>
<Animated.View
style={[
{
flex: 1,
height: 30,
overflow: 'visible',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#3330',
},
style,
]}
onLayout={this.onLayout}
>
<Animated.View
style={{
width: '100%',
height: 5,
borderRadius: 2,
overflow: 'hidden',
borderWidth: 1,
backgroundColor: maximumTrackTintColor,
}}
>
<Animated.View
style={{
backgroundColor: minimumTrackTintColor,
height: '100%',
maxWidth: '100%',
width: this.clamped_x,
position: 'absolute',
}}
/>
</Animated.View>
</Animated.View>
</PanGestureHandler>
</Animated.View>
</TapGestureHandler>
);
}
}
export default Slider;
After reading the documentation several times I figured it out. It's simpler than expected :)
<PanGestureHandler
onGestureEvent={this.onGestureEvent}
onHandlerStateChange={this.onGestureEvent}
minDist={0}
>
The property minDist can be set to 0.
Actually one needs to use the LongPressGestureHandler, as the PanHandler only changes it's state after some initial movement and not on touch begin.
The solution is to use something like:
<LongPressGestureHandler
onGestureEvent={this.onGestureEvent}
onHandlerStateChange={this.onGestureEvent}
minDurationMs={0}
maxDist={Number.MAX_SAFE_INTEGER}
shouldCancelWhenOutside={false}
hitSlop={10}
>
{...}
</LongPressGestureHandler>
With this reference
Problems with parallax header in react native
The only solution found is just an hack that hide you refreshcomponent because contentContainerStyle don't interact with the refreshcomponent.
So, the only solution is to move the scrollview component, but moving it while you are scrolling is pretty laggy and staggering.
Any solution?
This is pretty common case, i mean..Facebook app and Twitter app have both this this type of home screen!
and example animation is:
animated header from play store app home
added snack:
snack esample of header animation
as you see, on Android, scrolling up and down start to stagger because the 2 animation (container and scroll) are concurrent: they don't mix, each one try to animate ..going mad.
UPDATE 3: snack solution good for android and ios
update: complete snack with gif like animation
I found a workaround for the first partial solution (absolute header with transform translate and contentContainerStyle with paddingTop)
The problem is only on the refresh component, so what to do?
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
<AnimatedFlatList
data={data}
renderItem={item => this._renderRow(item.item, item.index)}
scrollEventThrottle={16}
onScroll={Animated.event([
{ nativeEvent: { contentOffset: { y: this.state.scrollAnim } }, },
], { useNativeDriver: true })}
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={() => {
this.setState({ refreshing: true });
setTimeout(() => this.setState({ refreshing: false }), 1000);
}}
// Android offset for RefreshControl
progressViewOffset={NAVBAR_HEIGHT}
/>
}
// iOS offset for RefreshControl
contentInset={{
top: NAVBAR_HEIGHT,
}}
contentOffset={{
y: -NAVBAR_HEIGHT,
}}
/>
This apply offset style on the refreshController, aligning it with the content.
UPDATE2:
there are some problems on ios.
UPDATE3:
fixed on ios too.
snack working both ios and android
You can try the react-spring library as it supports Parallax effects for react native.
Update: A working solution from your example
import React, { Component } from 'react';
import { Animated, Image, Platform, StyleSheet, View, Text, FlatList } from 'react-native';
const data = [
{
key: 'key',
name: 'name',
image: 'imageUrl',
},
];
const NAVBAR_HEIGHT = 90;
const STATUS_BAR_HEIGHT = Platform.select({ ios: 20, android: 24 });
const styles = StyleSheet.create({
fill: {
flex: 1,
},
navbar: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
alignItems: 'center',
backgroundColor: 'white',
borderBottomColor: '#dedede',
borderBottomWidth: 1,
height: NAVBAR_HEIGHT,
justifyContent: 'center',
paddingTop: STATUS_BAR_HEIGHT,
},
contentContainer: {
flex: 1,
},
title: {
color: '#333333',
},
row: {
height: 300,
width: null,
marginBottom: 1,
padding: 16,
backgroundColor: 'transparent',
},
rowText: {
color: 'white',
fontSize: 18,
},
});
export default class App extends Component {
constructor(props) {
super(props);
const scrollAnim = new Animated.Value(0);
this._clampedScrollValue = 0;
this._offsetValue = 0;
this._scrollValue = 0;
this.state = {
scrollAnim,
};
}
_renderRow(rowData, rowId) {
return (
<View style={{ flex: 1 }}>
<Image key={rowId} style={styles.row} source={{ uri: rowData.image }} resizeMode="cover" />
<Text style={styles.rowText}>{rowData.title}</Text>
</View>
);
}
render() {
const { scrollAnim } = this.state;
const navbarTranslate = scrollAnim.interpolate({
inputRange: [0, NAVBAR_HEIGHT - STATUS_BAR_HEIGHT],
outputRange: [0, -(NAVBAR_HEIGHT - STATUS_BAR_HEIGHT)],
extrapolate: 'clamp',
});
const navbarOpacity = scrollAnim.interpolate({
inputRange: [0, NAVBAR_HEIGHT - STATUS_BAR_HEIGHT],
outputRange: [1, 0],
extrapolate: 'clamp',
});
return (
<View style={styles.fill}>
<View style={styles.contentContainer}>
<FlatList
data={data}
renderItem={item => this._renderRow(item.item, item.index)}
scrollEventThrottle={16}
onScroll={Animated.event([
{ nativeEvent: { contentOffset: { y: this.state.scrollAnim } } },
])}
/>
</View>
<Animated.View style={[styles.navbar, { transform: [{ translateY: navbarTranslate }] }]}>
<Animated.Text style={[styles.title, { opacity: navbarOpacity }]}>PLACES</Animated.Text>
</Animated.View>
</View>
);
}
}
In case anybody else falls on this thread, I developed a new package, react-native-animated-screen, that does exactly what you need
Check it out
https://www.npmjs.com/package/react-native-animated-screen
someone, please help me implementing countdown circle in react-native
I want the timer to start at 300 seconds goes down to 0 with an animated circle and text(time) inside that.
I tried using https://github.com/MrToph/react-native-countdown-circle
but here the issue is that text(time) is updated after one complete animation.
You can also see the issue I have opened there.
Below is the code snippet, of my implementation
<CountdownCircle
seconds={300}
radius={25}
borderWidth={3}
color="#006400"
bgColor="#fff"
textStyle={{ fontSize: 15 }}
onTimeElapsed={() =>
console.log('time over!')}
/>
I have changed the library file which you mentioned in your question. I know it's not good but I have tried to solve your problem.
import CountdownCircle from 'react-native-countdown-circle'//you can make your own file and import from that
<CountdownCircle
seconds={30}
radius={30}
borderWidth={8}
color="#ff003f"
bgColor="#fff"
textStyle={{ fontSize: 20 }}
onTimeElapsed={() => console.log("Elapsed!")}
/>
Here is library file which now you can use it as a component also here is that react-native-countdown-circle library file code(modified code)
import React from "react";
import {
Easing,
Animated,
StyleSheet,
Text,
View,
ViewPropTypes
} from "react-native";
import PropTypes from "prop-types";
// compatability for react-native versions < 0.44
const ViewPropTypesStyle = ViewPropTypes
? ViewPropTypes.style
: View.propTypes.style;
const styles = StyleSheet.create({
outerCircle: {
justifyContent: "center",
alignItems: "center",
backgroundColor: "#e3e3e3"
},
innerCircle: {
overflow: "hidden",
justifyContent: "center",
alignItems: "center",
backgroundColor: "#fff"
},
leftWrap: {
position: "absolute",
top: 0,
left: 0
},
halfCircle: {
position: "absolute",
top: 0,
left: 0,
borderTopRightRadius: 0,
borderBottomRightRadius: 0,
backgroundColor: "#f00"
}
});
function calcInterpolationValuesForHalfCircle1(animatedValue, { shadowColor }) {
const rotate = animatedValue.interpolate({
inputRange: [0, 50, 50, 100],
outputRange: ["0deg", "180deg", "180deg", "180deg"]
});
const backgroundColor = shadowColor;
return { rotate, backgroundColor };
}
function calcInterpolationValuesForHalfCircle2(
animatedValue,
{ color, shadowColor }
) {
const rotate = animatedValue.interpolate({
inputRange: [0, 50, 50, 100],
outputRange: ["0deg", "0deg", "180deg", "360deg"]
});
const backgroundColor = animatedValue.interpolate({
inputRange: [0, 50, 50, 100],
outputRange: [color, color, shadowColor, shadowColor]
});
return { rotate, backgroundColor };
}
function getInitialState(props) {
console.log();
return {
circleProgress,
secondsElapsed: 0,
text: props.updateText(0, props.seconds),
interpolationValuesHalfCircle1: calcInterpolationValuesForHalfCircle1(
circleProgress,
props
),
interpolationValuesHalfCircle2: calcInterpolationValuesForHalfCircle2(
circleProgress,
props
)
};
}
const circleProgress = new Animated.Value(0);
export default class PercentageCircle extends React.PureComponent {
static propTypes = {
seconds: PropTypes.number.isRequired,
radius: PropTypes.number.isRequired,
color: PropTypes.string,
shadowColor: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
bgColor: PropTypes.string,
borderWidth: PropTypes.number,
containerStyle: ViewPropTypesStyle,
textStyle: Text.propTypes.style,
updateText: PropTypes.func,
onTimeElapsed: PropTypes.func
};
static defaultProps = {
color: "#f00",
shadowColor: "#999",
bgColor: "#e9e9ef",
borderWidth: 2,
seconds: 10,
children: null,
containerStyle: null,
textStyle: null,
onTimeElapsed: () => null,
updateText: (elapsedSeconds, totalSeconds) =>
(totalSeconds - elapsedSeconds).toString()
};
constructor(props) {
super(props);
this.state = getInitialState(props);
this.restartAnimation();
}
componentWillReceiveProps(nextProps) {
if (this.props.seconds !== nextProps.seconds) {
this.setState(getInitialState(nextProps));
}
}
onCircleAnimated = ({ finished }) => {
// if animation was interrupted by stopAnimation don't restart it.
if (!finished) return;
const secondsElapsed = this.state.secondsElapsed + 1;
const callback =
secondsElapsed < this.props.seconds
? this.restartAnimation
: this.props.onTimeElapsed;
const updatedText = this.props.updateText(
secondsElapsed,
this.props.seconds
);
this.setState(
{
...getInitialState(this.props),
secondsElapsed,
text: updatedText
},
callback
);
};
restartAnimation = () => {
Animated.timing(this.state.circleProgress, {
toValue:
parseFloat(JSON.stringify(this.state.circleProgress)) +
100 / this.props.seconds,
duration: 1000,
easing: Easing.linear
}).start(this.onCircleAnimated);
};
renderHalfCircle({ rotate, backgroundColor }) {
const { radius } = this.props;
return (
<View
style={[
styles.leftWrap,
{
width: radius,
height: radius * 2
}
]}
>
<Animated.View
style={[
styles.halfCircle,
{
width: radius,
height: radius * 2,
borderRadius: radius,
backgroundColor,
transform: [
{ translateX: radius / 2 },
{ rotate },
{ translateX: -radius / 2 }
]
}
]}
/>
</View>
);
}
renderInnerCircle() {
const radiusMinusBorder = this.props.radius - this.props.borderWidth;
return (
<View
style={[
styles.innerCircle,
{
width: radiusMinusBorder * 2,
height: radiusMinusBorder * 2,
borderRadius: radiusMinusBorder,
backgroundColor: this.props.bgColor,
...this.props.containerStyle
}
]}
>
<Text style={this.props.textStyle}>{this.state.text}</Text>
</View>
);
}
render() {
const {
interpolationValuesHalfCircle1,
interpolationValuesHalfCircle2
} = this.state;
return (
<View
style={[
styles.outerCircle,
{
width: this.props.radius * 2,
height: this.props.radius * 2,
borderRadius: this.props.radius,
backgroundColor: this.props.color
}
]}
>
{this.renderHalfCircle(interpolationValuesHalfCircle1)}
{this.renderHalfCircle(interpolationValuesHalfCircle2)}
{this.renderInnerCircle()}
</View>
);
}
}