Flash of content before/after rotate transform animation (flip card animation) - react-native

I see this behavior on both iOS and Android. Do you know why the content within the card flashes (hides then reappears instantly) after the transform animation starts (and also right before the animation ends).
This only happens if I do rotate, rotateY, or rotateX (untested rotateZ). I have no idea why.
Here is screencast:
High quality webm - https://gfycat.com/SplendidCompetentEnglishpointer
Low quality gif:
My render code is this:
<View style={style}>
<Animated.View style={[styleCommon, styleFace]}><Text>front</Text></Animated.View>
<Animated.View style={[styleCommon, styleBack]}><Text>front</Text></Animated.View>
</View>
And my styles are:
const styleCommon = {
justifyContent: 'center',
alignItems: 'center',
borderRadius: 10,
elevation: 2,
shadowRadius: 2,
shadowOpacity: 0.4,
shadowOffset: { height:1 },
overflow: 'hidden',
width: '100%',
height: '100%',
position: 'absolute',
backgroundColor: '#FFFFFF'
}
const styleFace = {
opacity: anim.interpolate({ inputRange:[0,.5,.5,1], outputRange:[1,1,0,0] }),
transform: [
{ rotateY:anim.interpolate({ inputRange:[0,1], outputRange:['0deg', '180deg'] }) }
]
};
const styleBack = {
opacity: anim.interpolate({ inputRange:[0,.5,.5,1], outputRange:[0,0,1,1] }),
transform: [
{ rotateY:anim.interpolate({ inputRange:[0,1], outputRange:['-180deg', '0deg'] }) }
]
};

I found it should be a bug of transform in react-native, showing view from 0.1 deg to 0.4 deg, and from -0.1 deg to -0.4 deg. Everything disappears within these degree.
That can be easily proven, by set start degree to 0.1 ~ 0.4.
transform: [
{ rotateY:anim.interpolate({ inputRange:[0,1], outputRange:['0.1deg', '180deg'] }) }
]
A quick workaround for this could be bypassing those Bermuda Degree:
const styleFace = {
transform: [
{ rotateY:this.anim.interpolate({ inputRange:[0,0.01,0.01,1], outputRange:['0deg', '0deg', '0.4deg', '180deg'] }) }
]
};
const styleBack = {
transform: [
{ rotateY:this.anim.interpolate({ inputRange:[0,0.99,0.99,1], outputRange:['-180deg', '-0.4deg', '0deg', '0deg'] }) }
]
};
Result:

Related

React native svg chart graphs

import React from 'react'
import { BarChart, Grid } from 'react-native-svg-charts'
import { Defs, LinearGradient, Stop } from "react-native-svg";
class ColorBarExample extends React.PureComponent {
render() {
const data = [
{
value: 50,
},
{
value: 10,
svg: {
fill: 'rgba(134, 65, 244, 0.5)',
},
},
{
value: 40,
svg: {
stroke: 'purple',
strokeWidth: 2,
fill: 'white',
strokeDasharray: [ 4, 2 ],
},
},
{
value: 95,
svg: {
fill: 'url(#gradient)',
},
},
{
value: 85,
svg: {
fill: 'green',
},
},
]
const Gradient = () => (
<Defs key={'gradient'}>
<LinearGradient id={'gradient'} x1={'0'} y={'0%'} x2={'100%'} y2={'0%'}>
<Stop offset={'0%'} stopColor={'rgb(134, 65, 244)'}/>
<Stop offset={'100%'} stopColor={'rgb(66, 194, 244)'}/>
</LinearGradient>
</Defs>
)
return (
<BarChart
style={{ height: 200 }}
data={data}
gridMin={0}
svg={{ fill: 'rgba(134, 65, 244, 0.8)' }}
yAccessor={({ item }) => item.value}
contentInset={{ top: 20, bottom: 20 }}
>
<Grid/>
<Gradient/>
</BarChart>
)
}
}
export default ColorBarExample
As this is giving me a simple gradient but i am need a gradient like this. How can i get it in this gradient .
Let me know how can i draw it like this image so that each gradient have some borderradius at the top and and have a custom gradient colors as per image and also can be of small width instead of long.
You can give vertical gradient by this x2={'0%'} y2={'100%'} in Gradient function and for rounded corners you can try this
To change width of bar you may try spacingInner property.
To give gradient to all bars, we should manage svg fill property of data array.
here is the link of your barchart code demo. Please check if it helps.

OnBuffer props in react-native-video is not working

I am using react-native-video for playing videos from url. I am using onBuffer prop to show loader if the video is buffering. But in my case onBuffer is not working. If the video size is large then in case of a slow network or unstable network video is taking time to play, so I want to show loader if the video is not playing but in buffering mode.
I had same issue and i was able to fix this in android by following this suggestion: https://github.com/react-native-community/react-native-video/issues/1920#issuecomment-604545238
From what i understand, by default android uses android player which dont have these features. But ExoPlayer has lots features like buffering, adaptive bitrate etc.. So, we have to explicitly specify to use ExoPlayer.
Important part is to enable multiDex.
This is what the fix suggested and it worked fine:
If you are curois how to enable exoplayer then follow these steps:
create react-native.config.js with following content:
module.exports = {
dependencies: {
'react-native-video': {
platforms: {
android: {
sourceDir: '../node_modules/react-native-video/android-exoplayer',
},
},
},
},
};
now enable multidex following this https://developer.android.com/studio/build/multidex
clean using gradlew clean and rebuild to make it work
You can use onLoad and onEnd prop of react-native-video to show loader.
For Example
<Video
poster={/*uri*/}
posterResizeMode="cover"
source={/*source*/}
onLoad={()=>/* set loader to true*/}
onEnd={()=>/* set loader to false*/}
/>
I was doing this all stuff in my application on buffering state you can simply check the state on buffer and display loader like this. You have to use a combination of onLoadStart, onBuffer and onLoadEnd, Below is an example of how you can also build your custom loader and display while buffering.
If you don't want to use my loader or this animation stuff remove that thing and render your own loader.
class VideoPlayer extends React.Component {
constructor (props) {
super(props)
this.state = {
paused: false,
buffering: true,
animated: new Animated.Value(0),
progress: 0,
duration: 0
}
}
handleMainButtonTouch = () => {
if (this.state.progress >= 1) {
this.player.seek(0)
}
this.setState(state => {
return {
paused: !state.paused
}
})
};
handleProgressPress = e => {
const position = e.nativeEvent.locationX
const progress = (position / 250) * this.state.duration
const isPlaying = !this.state.paused
this.player.seek(progress)
};
handleProgress = progress => {
this.setState({
progress: progress.currentTime / this.state.duration
})
};
onBuffer = ({isBuffering}) => {
this.setState({ buffering: isBuffering })
if (isBuffering) {
this.triggerBufferAnimation()
}
if (this.loopingAnimation && isBuffering) {
this.loopingAnimation.stopAnimation()
}
}
onLoadStart = () => {
this.triggerBufferAnimation()
}
handleLoad = ({duration}) => {
this.setState({
duration: duration
})
};
triggerBufferAnimation = () => {
this.loopingAnimation && this.loopingAnimation.stopAnimation()
this.loopingAnimation = Animated.loop(
Animated.timing(this.state.animated, {
toValue: 1,
duration: 1500
})
).start()
};
render () {
console.log('video player ', this.props)
const { isShowingDetails, hideDetails, showDetails, thumbnailUrl, url } = this.props
const { buffering } = this.state
const BufferInterpolation = this.state.animated.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '600deg']
})
const rotateStyles = { transform: [{
rotate: BufferInterpolation
}]
}
return (
<React.Fragment>
<Video
ref={ref => {
this.player = ref;
}}
source={{ uri: url }}
controls
paused={this.state.paused}
// poster={thumbnailUrl}
onBuffer={this.onBuffer}
onLoadStart={this.onLoadStart}
onLoad={this.handleLoad}
key={url}
onProgress={this.handleProgress}
style={styles.backgroundVideo}
useTextureView={false}
onVideoEnd={() => alert('Video ended')}
bufferConfig={{
minBufferMs: 15000,
maxBufferMs: 50000,
bufferForPlaybackMs: 2500,
bufferForPlaybackAfterRebufferMs: 5000
}}
/>
<View
color={Colors.appColor}
style={styles.videoCover}
>{
buffering && <Animated.View style={rotateStyles}>
<FontAwesome5 name='circle-notch' size={50} color='rgba(255, 255, 255, 0.6)' />
</Animated.View>
}</View>
</React.Fragment>
)
}
}
export default VideoPlayer
const styles = StyleSheet.create({
backgroundVideo: {
height: undefined,
width: '100%',
backgroundColor: 'black',
aspectRatio: 16 / 9,
zIndex: 100
// 2: 0
},
videoCover: {
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'transparent',
zIndex: 10
},
controls: {
backgroundColor: 'rgba(0, 0, 0, 0.5)',
height: 20,
left: 0,
bottom: 5,
right: 0,
position: 'absolute',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
paddingHorizontal: 10,
zIndex: 10
},
duration: {
color: '#FFF',
marginLeft: 15
}
})
The same happened to me.
Solution is to use exoPlayer instead of mediaPlayer.
when we install react-native-video package then by default it installs mediaPlayer which is better in case of just working with local media files.
But when we need to work with online videos then we need exoPlayer.
How to apply exoPlayer ?
This is well defined in react-native-video docs :
https://github.com/react-native-video/react-native-video

How do I set the initial offset in a PanGestureHandler from react-native-gesture-handler?

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>

Parallax/ animated Header on react native with scrollView onScroll

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

how to implement React native countdown circle

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>
);
}
}