I am developing the react native application which contains the wheel of fortune. There are two wheels in my application. Wheel#1 is running smoothly but in Wheel#2 is being required to add Inner & Outer wheels. I am using D3 shape and React Native SVG packages to achieve my goal.
import React, {Component} from 'react';
import {
View,
StyleSheet,
Dimensions,
Animated,
TouchableOpacity,
Image,
} from 'react-native';
import * as d3Shape from 'd3-shape';
import Svg, {G, Text, TSpan, Path, Pattern} from 'react-native-svg';
const AnimatedSvg = Animated.createAnimatedComponent(Svg);
const {width, height} = Dimensions.get('screen');
class WheelOfFortune extends Component {
constructor(props) {
super(props);
this.state = {
enabled: false,
started: false,
finished: false,
winner: null,
gameScreen: new Animated.Value(width - 40),
wheelOpacity: new Animated.Value(1),
imageLeft: new Animated.Value(width / 2 - 30),
imageTop: new Animated.Value(height / 2 - 70),
};
this.angle = 0;
this.prepareWheel();
}
prepareWheel = () => {
this.Rewards = this.props.options.rewards;
this.RewardCount = this.Rewards.length;
this.numberOfSegments = this.RewardCount;
this.fontSize = 20;
this.oneTurn = 360;
this.angleBySegment = this.oneTurn / this.numberOfSegments;
this.angleOffset = this.angleBySegment / 2;
this.winner = this.props.options.winner
? this.props.options.winner
: Math.floor(Math.random() * this.numberOfSegments);
this._wheelPaths = this.makeWheel();
this._angle = new Animated.Value(0);
this.props.options.onRef(this);
};
resetWheelState = () => {
this.setState({
enabled: false,
started: false,
finished: false,
winner: null,
gameScreen: new Animated.Value(width - 40),
wheelOpacity: new Animated.Value(1),
imageLeft: new Animated.Value(width / 2 - 30),
imageTop: new Animated.Value(height / 2 - 70),
});
};
_tryAgain = () => {
this.prepareWheel();
this.resetWheelState();
this.angleListener();
this._onPress();
};
angleListener = () => {
this._angle.addListener(event => {
if (this.state.enabled) {
this.setState({
enabled: false,
finished: false,
});
}
this.angle = event.value;
});
};
componentWillUnmount() {
this.props.options.onRef(undefined);
}
componentDidMount() {
this.angleListener();
}
makeWheel = () => {
const data = Array.from({length: this.numberOfSegments}).fill(1);
const arcs = d3Shape.pie()(data);
var colors = this.props.options.colors
? this.props.options.colors
: [
'#E07026',
'#E8C22E',
'#ABC937',
'#4F991D',
'#22AFD3',
'#5858D0',
'#7B48C8',
'#D843B9',
'#E23B80',
'#D82B2B',
];
return arcs.map((arc, index) => {
const instance = d3Shape
.arc()
.padAngle(0.01)
.outerRadius(width / 2)
.innerRadius(this.props.options.innerRadius || 100);
return {
path: instance(arc),
color: colors[index % colors.length],
value: this.Rewards[index],
centroid: instance.centroid(arc),
};
});
};
_getWinnerIndex = () => {
const deg = Math.abs(Math.round(this.angle % this.oneTurn));
// wheel turning counterclockwise
if (this.angle < 0) {
return Math.floor(deg / this.angleBySegment);
}
// wheel turning clockwise
return (
(this.numberOfSegments - Math.floor(deg / this.angleBySegment)) %
this.numberOfSegments
);
};
_onPress = () => {
const duration = this.props.options.duration || 10000;
this.setState({
started: true,
});
Animated.timing(this._angle, {
toValue:
365 -
this.winner * (this.oneTurn / this.numberOfSegments) +
360 * (duration / 1000),
duration: duration,
useNativeDriver: true,
}).start(() => {
const winnerIndex = this._getWinnerIndex();
this.setState({
finished: true,
winner: this._wheelPaths[winnerIndex].value,
});
this.props.getWinner(this._wheelPaths[winnerIndex].value, winnerIndex);
});
};
_textRender = (x, y, number, i) => (
<Text
x={x - number.length * 5}
y={y - 80}
fill={
this.props.options.textColor ? this.props.options.textColor : '#fff'
}
textAnchor="middle"
fontSize={this.fontSize}>
{Array.from({length: number.length}).map((_, j) => {
// Render reward text vertically
if (this.props.options.textAngle === 'vertical') {
return (
<TSpan x={x} dy={this.fontSize} key={`arc-${i}-slice-${j}`}>
{number.charAt(j)}
</TSpan>
);
}
// Render reward text horizontally
else {
return (
<TSpan
y={y - 40}
dx={this.fontSize * 0.07}
key={`arc-${i}-slice-${j}`}>
{number.charAt(j)}
</TSpan>
);
}
})}
</Text>
);
_renderSvgWheel = () => {
return (
<View style={styles.container}>
{this._renderKnob()}
<Animated.View
style={{
alignItems: 'center',
justifyContent: 'center',
transform: [
{
rotate: this._angle.interpolate({
inputRange: [-this.oneTurn, 0, this.oneTurn],
outputRange: [
`-${this.oneTurn}deg`,
`0deg`,
`${this.oneTurn}deg`,
],
}),
},
],
backgroundColor: this.props.options.backgroundColor
? this.props.options.backgroundColor
: '#fff',
width: width - 20,
height: width - 20,
borderRadius: (width - 20) / 2,
borderWidth: this.props.options.borderWidth
? this.props.options.borderWidth
: 2,
borderColor: this.props.options.borderColor
? this.props.options.borderColor
: '#fff',
opacity: this.state.wheelOpacity,
}}>
<AnimatedSvg
width={this.state.gameScreen}
height={this.state.gameScreen}
viewBox={`0 0 ${width} ${width}`}
style={{
transform: [{rotate: `-${this.angleOffset}deg`}],
margin: 10,
}}>
<G y={width / 2} x={width / 2}>
{this._wheelPaths.map((arc, i) => {
const [x, y] = arc.centroid;
const number = arc.value.toString();
return (
<G key={`arc-${i}`}>
<Path d={arc.path} strokeWidth={2} fill={arc.color} />
<G
rotation={
(i * this.oneTurn) / this.numberOfSegments +
this.angleOffset
}
origin={`${x}, ${y}`}>
{this._textRender(x, y, number, i)}
</G>
</G>
);
})}
</G>
</AnimatedSvg>
</Animated.View>
</View>
);
};
_renderKnob = () => {
const knobSize = this.props.options.knobSize
? this.props.options.knobSize
: 20;
// [0, this.numberOfSegments]
const YOLO = Animated.modulo(
Animated.divide(
Animated.modulo(
Animated.subtract(this._angle, this.angleOffset),
this.oneTurn,
),
new Animated.Value(this.angleBySegment),
),
1,
);
return (
<Animated.View
style={{
width: knobSize,
height: knobSize * 2,
justifyContent: 'flex-end',
zIndex: 1,
opacity: this.state.wheelOpacity,
transform: [
{
rotate: YOLO.interpolate({
inputRange: [-1, -0.5, -0.0001, 0.0001, 0.5, 1],
outputRange: [
'0deg',
'0deg',
'35deg',
'-35deg',
'0deg',
'0deg',
],
}),
},
],
}}>
<Svg
width={knobSize}
height={(knobSize * 100) / 57}
viewBox={`0 0 57 100`}
style={{
transform: [{translateY: 8}],
}}>
<Image
source={
this.props.options.knobSource
? this.props.options.knobSource
: require('../assets/images/knob.png')
}
style={{ width: knobSize, height: (knobSize * 100) / 57 }}
/>
</Svg>
</Animated.View>
);
};
_renderTopToPlay() {
if (this.state.started == false) {
return (
<TouchableOpacity onPress={() => this._onPress()}>
{this.props.options.playButton()}
</TouchableOpacity>
);
}
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity
style={{
position: 'absolute',
width: width,
height: height / 2,
justifyContent: 'center',
alignItems: 'center',
}}>
<Animated.View style={[styles.content, {padding: 10}]}>
{this._renderSvgWheel()}
</Animated.View>
</TouchableOpacity>
{this.props.options.playButton ? this._renderTopToPlay() : null}
</View>
);
}
}
export default WheelOfFortune;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
content: {},
startText: {
fontSize: 50,
color: '#fff',
fontWeight: 'bold',
textShadowColor: 'rgba(0, 0, 0, 0.4)',
textShadowOffset: {width: -1, height: 1},
textShadowRadius: 10,
},
});
End Result Look like this
Now I want to add an Inner wheel to it Like this
Related
There is a class components work piece. I want to convert it into a function component and implement it in the same way, but there's a problem.
Unlike the class type, the function type returns to the place where it was created without staying after the picture has moved.
This is the function components.
const [dogs, setDogs] = useState([])
function addDog(){
// setDogs([...dogs, {id: dogCount++}])
dogCount++;
console.log(dogCount)
// console.log(dogs)
const newDog = {id: dogCount, bottom: 125}
setDogs([...dogs, newDog])
}
return (
<View style={styles.entirebottomsheet}>
<View style={styles.container}>
<TouchableOpacity onPress={()=>{addDog();}} style={styles.dogButton}>
<Text>Dog</Text>
</TouchableOpacity>
{dogs.map(dog => {
return (
<DogContainer
key={dog.id}
style={{bottom: dog.bottom, position: 'absolute'}}
/>
);
})}
</View>
</View>
);
function DogContainer () {
const positionX = new Animated.Value(0);
const positionY = new Animated.Value(0);
// DogContainer.defaultProps = {
// onComplete() {}
// }
const getDogStyle = () => ({
transform: [{translateX: positionX}],
});
useEffect(() => {
Animated.spring(positionX, {
duration: 1000,
toValue: width / 3,
easing: Easing.ease,
useNativeDriver: true,
}).start();
Animated.spring(positionY, {
duration: 1000,
toValue: width / 3,
easing: Easing.ease,
useNativeDriver: true,
}).start();
return (
console.log('finish')
)
}, []);
return (
<Animated.View style={[{ bottom: 125},{
transform: [{translateX: positionX}, {translateY: positionY}],
}]}>
<Image source={dogImg} style={{width: 60, height: 60}}/>
</Animated.View>
);
}
This is the class components.
export default class App extends React.Component {
// let {up} = this.props.up;
state = {
dogs: [],
cats: [],
chicks: [],
};
addDog = () => {
console.log(...this.state.dogs);
this.setState(
{
dogs: [
...this.state.dogs,
{
id: dogCount,
bottom: 125,
},
],
},
() => {
dogCount++;
},
);
};
removeDog = id => {
this.setState({
dogs: this.state.dogs.filter(dog => {
return dog.id !== id;
}),
});
};
addCat = () => {
console.log(...this.state.cats);
this.setState(
{
cats: [
...this.state.cats,
{
id: catCount,
bottom: 125,
},
],
},
() => {
catCount++;
},
);
};
removeCat = id => {
this.setState({
cats: this.state.cats.filter(cat => {
return cat.id !== id;
}),
});
};
addChick = () => {
console.log(...this.state.chicks);
this.setState(
{
chicks: [
...this.state.chicks,
{
id: chickCount,
bottom: 125,
},
],
},
() => {
chickCount++;
},
);
};
removeChick = id => {
this.setState({
chicks: this.state.chicks.filter(chick => {
return chick.id !== id;
}),
});
};
render() {
return (
<GestureHandlerRootView>
<View style={styles.entirebottomsheet}>
<View style={styles.container}>
<TouchableOpacity onPress={this.addDog} style={styles.dogButton}>
<Text>Dog</Text>
</TouchableOpacity>
{this.state.dogs.map(dog => {
return (
<DogContainer
key={dog.id}
style={{bottom: dog.bottom, position: 'absolute'}}
onComplete={() => this.removeDog(dog.id)}
/>
);
})}
</View>
<View style={styles.container}>
<TouchableOpacity onPress={this.addCat} style={styles.catButton}>
<Text>Cat</Text>
</TouchableOpacity>
{this.state.cats.map(cat => {
return (
<CatContainer
key={cat.id}
style={{bottom: cat.bottom, position: 'absolute'}}
onComplete={() => this.removeCat(cat.id)}
/>
);
})}
</View>
<View style={styles.container}>
<TouchableOpacity onPress={this.addChick} style={styles.chickButton}>
<Text style={{position: 'absolute'}}>Chick</Text>
</TouchableOpacity>
{this.state.chicks.map(chick => {
return (
<ChickContainer
key={chick.id}
style={{bottom: chick.bottom, position: 'absolute'}}
onComplete={() => this.removeChick(chick.id)}
/>
);
})}
</View>
</View>
</GestureHandlerRootView>
);
}
}
class DogContainer extends React.Component {
state = {
position: new Animated.ValueXY({
x: 0,
y: 0,
}),
};
static defaultProps = {
onComplete() {},
};
componentDidMount() {
Animated.spring(this.state.position.x, {
duration: 1000,
toValue: width / 3,
easing: Easing.ease,
useNativeDriver: true,
}).start(this.props.onComplete);
Animated.spring(this.state.position.y, {
duration: 1000,
toValue: -340,
easing: Easing.ease,
useNativeDriver: true,
}).start(this.props.onComplete);
}
getDogStyle() {
return {
transform: [
{translateY: this.state.position.y},
{translateX: this.state.position.x},
],
};
}
render() {
return (
<Animated.View style={[this.getDogStyle(), this.props.style]}>
<Image source={dogImg} style={{width: 60, height: 60, borderRadius: 30}} />
</Animated.View>
);
}
}
Try to use useRef() hook
function DogContainer () {
const positionX = useRef(new Animated.Value(0)).current;
const positionY = useRef(new Animated.Value(0)).current;
...
In the Class-based version of DogContainer the position coordinate was a part of state.
class DogContainer extends React.Component {
state = {
position: new Animated.ValueXY({
x: 0,
y: 0,
}),
};
static defaultProps = {
onComplete() {},
};
componentDidMount() {
Animated.spring(this.state.position.x, {
duration: 1000,
toValue: width / 3,
easing: Easing.ease,
useNativeDriver: true,
}).start(this.props.onComplete);
Animated.spring(this.state.position.y, {
duration: 1000,
toValue: -340,
easing: Easing.ease,
useNativeDriver: true,
}).start(this.props.onComplete);
}
getDogStyle() {
return {
transform: [
{translateY: this.state.position.y},
{translateX: this.state.position.x},
],
};
}
render() {
return (
<Animated.View style={[this.getDogStyle(), this.props.style]}>
<Image source={dogImg} style={{width: 60, height: 60, borderRadius: 30}} />
</Animated.View>
);
}
}
Function component using the useState hook
function DogContainer ({ onComplete = () => {} }) {
const [position, setPosition] = React.useState(
new Animated.ValueXY({
x: 0,
y: 0,
})
);
const getDogStyle = () => ({
transform: [{
translateX: position.x,
translateY: position.y,
}],
});
useEffect(() => {
Animated.spring(position.x, {
duration: 1000,
toValue: width / 3,
easing: Easing.ease,
useNativeDriver: true,
}).start(onComplete);
Animated.spring(position.y, {
duration: 1000,
toValue: width / 3,
easing: Easing.ease,
useNativeDriver: true,
}).start(onComplete);
console.log('finish');
}, []);
return (
<Animated.View
style={[
{ bottom: 125},
{ transform: [
{ translateX: position.x },
{ translateY: position.y }
],
}
]}
>
<Image source={dogImg} style={{ width: 60, height: 60 }} />
</Animated.View>
);
}
I've been trying to build an Image carousel with a bunch of randomly selected images. I wanted to maintain their aspect ratios so I set the resizeMode to 'contain'. Somehow that step leads to the loss of any set borderRadius! What could be the reason? And if that step doesn't work at all, any other ideas on how to maintain the correct aspect ratio + get the corners rounded?
Thanks a lot for your help!
here's the code:
import React, { useCallback, memo, useRef, useState } from "react";
import {
FlatList,
View,
Dimensions,
Text,
StyleSheet,
Image,
} from "react-native";
const images = [
Image1,
Image2,
Image3,
Image4,
Image5,
Image6,
Image7,
Image8,
Image9,
Image10,
Image11,
Image12,
Image13,
Image14,
Image15,
Image16,
Image17,
Image18,
Image19,
Image20,
Image21,
Image22,
Image23,
Image24,
Image25,
Image26,
Image27,
Image28,
Image29,
Image30,
Image31,
Image32,
Image33,
Image34,
Image35,
Image36,
Image37,
Image38,
Image39,
Image40,
Image41,
]
const { width: windowWidth, height: windowHeight } = Dimensions.get("window");
const randomImage = () =>
images[Math.floor(Math.random() * images.length)];
const styles = StyleSheet.create({
slide: {
height: windowHeight,
width: windowWidth,
//justifyContent: "center",
alignItems: "center",
},
slideImage: {
height: '70%',
width: '90%',
borderRadius: 20,
marginTop: 20,
},
slideTitle: {
fontSize: 24,
marginTop: 0,
},
slideSubtitle: {
fontSize: 18,
marginTop: 10,
},
pagination: {
position: "absolute",
bottom: 8,
justifyContent: "center",
flexDirection: "row",
marginBottom: 12
},
paginationDot: {
width: 8,
height: 8,
borderRadius: 4,
marginHorizontal: 2,
},
paginationDotActive: { backgroundColor: "lightblue" },
paginationDotInactive: { backgroundColor: "gray" },
carousel: {},
});
const slideList = Array.from({ length: 999 }).map((_, i) => {
return {
id: i,
image: randomImage,
title: `This is the title ${i + 1}!`,
subtitle: `This is the subtitle ${i + 1}!`,
};
});
const Slide = memo(function Slide({ data }) {
return (
<View style={styles.slide}>
<Image resizeMode = 'contain' source = {randomImage()} style={styles.slideImage}></Image>
<Text style={styles.slideTitle}>{data.title}</Text>
<Text style={styles.slideSubtitle}>{data.subtitle}</Text>
</View>
);
});
function Pagination({ index }) {
return (
<View style={styles.pagination} pointerEvents="none">
{slideList.map((_, i) => {
return (
<View
key={i}
style={[
styles.paginationDot,
index === i
? styles.paginationDotActive
: styles.paginationDotInactive,
]}
/>
);
})}
</View>
);
}
export default function Carousel() {
const [index, setIndex] = useState(0);
const indexRef = useRef(index);
indexRef.current = index;
const onScroll = useCallback((event) => {
const slideSize = event.nativeEvent.layoutMeasurement.width;
const index = event.nativeEvent.contentOffset.x / slideSize;
const roundIndex = Math.round(index);
const distance = Math.abs(roundIndex - index);
// Prevent one pixel triggering setIndex in the middle
// of the transition. With this we have to scroll a bit
// more to trigger the index change.
const isNoMansLand = 0.4 < distance;
if (roundIndex !== indexRef.current && !isNoMansLand) {
setIndex(roundIndex);
}
}, []);
const flatListOptimizationProps = {
initialNumToRender: 0,
maxToRenderPerBatch: 1,
removeClippedSubviews: true,
scrollEventThrottle: 16,
windowSize: 2,
keyExtractor: useCallback(s => String(s.id), []),
getItemLayout: useCallback(
(_, index) => ({
index,
length: windowWidth,
offset: index * windowWidth,
}),
[]
),
};
const renderItem = useCallback(function renderItem({ item }) {
return <Slide data={item} />;
}, []);
return (
<>
<FlatList
data={slideList}
style={styles.carousel}
renderItem={renderItem}
pagingEnabled
horizontal
showsHorizontalScrollIndicator={false}
bounces={false}
onScroll={onScroll}
{...flatListOptimizationProps}
/>
<Pagination index={index}></Pagination>
</>
);
}
``
Actually borderRadius works but you can't see it because of an incorrect ratio.
If your image has a 16:9 ratio, for example, 1600x900 dimensions, then you need to set width and height with the same ratio.
<Image
source={ 1600x900 }
resizeMode="contain"
style={{
width: 300,
height: 300,
borderRadius: 15,
backgroundColor: 'red'
}} />
The result will be:
Because the image has width and height 300, ie 1:1 ratio. If you modify width and height like 320 and 180, ie 16:9, then the image fills all the space and borders will be visible.
Another workaround is to wrap your image with view that hides the overflow
<View
style={{
width: 300,
height: 300,
borderRadius: 150,
overflow: 'hidden',
}}
>
<Image
source={item.image}
style={{
width: 300,
height: 300,
}}
resizeMode='contain'
/>
</View>
Hey everyone :) Should be a function for a navigation avatar which sticks to the closest corner. While coding I used a simple circle as a placeholder. The problem is that the following code works perfectly fine when imported to another screen, but when I replace <View style={styles.circle} /> with an image <Image source={require("../assets/dude.png")} resizeMode="contain" style={{width: 180, height: 240,}}/> it doesnt work anymore? Like I can see the image, but the animations work extremely buggy and it just goes anywhere, nothing like it's supposed to do?
I tried it also with Animated.Image instead of the view and giving it all the parameters, still no change. The weird thing is that the Image works perfectly fine if I were to run this code as a screen itself, but when I import it only the circle view works, not the image?
EDIT: Just found the issue: if the Animated.Image is in front of a Scrollable View, even if it isn't part of that View, it bugs. If I replace the image with anything else (like a Box), it works fine, only the image bugs in that manner :) which leads me to my next question: How can I fix that?
so this is my code:
import React from "react";
import {
StyleSheet,
View,
Dimensions,
Animated,
PanResponder,
Image,
} from "react-native";
export default class Avatar extends React.Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
screenMeasurements: {
width: Screen.width / 2,
height: Screen.height / 2,
},
};
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
this.state.pan.setOffset({
x: this.state.pan.x._value,
y: this.state.pan.y._value,
});
},
onPanResponderMove: Animated.event([
null,
{
dx: this.state.pan.x,
dy: this.state.pan.y,
},
]),
onPanResponderRelease: (e, gesture) => {
if (this.whichField(gesture) == 1) {
console.log("top left");
this.state.pan.flattenOffset();
Animated.spring(this.state.pan, {
toValue: {
x: (Screen.width * 0.5 - 90) * -1,
y: (Screen.height * 0.5 - 120) * -1,
},
}).start();
} else if (this.whichField(gesture) == 2) {
console.log("top right");
this.state.pan.flattenOffset();
Animated.spring(this.state.pan, {
toValue: {
x: Screen.width * 0.5 - 90,
y: (Screen.height * 0.5 - 120) * -1,
},
}).start();
} else if (this.whichField(gesture) == 3) {
console.log("bottom left");
this.state.pan.flattenOffset();
Animated.spring(this.state.pan, {
toValue: {
x: (Screen.width * 0.5 - 90) * -1,
y: Screen.height * 0.5 - 150,
},
}).start();
} else {
console.log("bottom right");
this.state.pan.flattenOffset();
Animated.spring(this.state.pan, {
toValue: {
x: Screen.width * 0.5 - 90,
y: Screen.height * 0.5 - 150,
},
}).start();
}
},
});
}
whichField(gesture) {
var sm = this.state.screenMeasurements;
let field;
{
gesture.moveY < sm.height && gesture.moveX < sm.width
? (field = 1)
: gesture.moveY < sm.height && gesture.moveX > sm.width
? (field = 2)
: gesture.moveY > sm.height && gesture.moveX < sm.width
? (field = 3)
: (field = 4);
}
return field;
}
render() {
return (
<View style={styles.draggableContainer}>
<Animated.View
style={[this.state.pan.getLayout()]}
{...this.panResponder.panHandlers}
>
<View style={styles.circle} />
</Animated.View>
</View>
);
}
}
let Screen = Dimensions.get("screen");
let CIRCLE_RADIUS = 45;
const styles = StyleSheet.create({
text: {
marginTop: 25,
marginLeft: 5,
marginRight: 5,
textAlign: "center",
color: "#fff",
},
draggableContainer: {
position: "absolute",
top: Screen.height / 2 - CIRCLE_RADIUS,
left: Screen.width / 2 - CIRCLE_RADIUS,
},
circle: {
backgroundColor: "#1abc9c",
width: CIRCLE_RADIUS * 2,
height: CIRCLE_RADIUS * 2,
borderRadius: CIRCLE_RADIUS,
},
});
Check out this similar animation of badge Above ScrollView.
You need to place the Image inside an Animated View.
Example code:
import React, {Component} from 'react';
import {
StyleSheet,
View,
PanResponder,
Animated,
Dimensions,
ScrollView,
Image,
} from 'react-native';
const {height, width} = Dimensions.get('screen');
export default class SampleApp extends Component {
constructor() {
super();
this._animatedValue = new Animated.ValueXY({x: 20, y: 20});
this._value = {x: 20, y: 20};
this._animatedValue.addListener((value) => (this._value = value));
this._panResponder = PanResponder.create({
onMoveShouldSetResponderCapture: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderGrant: (e, gestureState) => {
this._animatedValue.setOffset({x: this._value.x, y: this._value.y});
this._animatedValue.setValue({x: 0, y: 0});
},
onPanResponderMove: Animated.event([
null,
{dx: this._animatedValue.x, dy: this._animatedValue.y},
]),
onPanResponderRelease: (e, gesture) => {
this._animatedValue.flattenOffset();
if (this.whichField(gesture) == 1) {
Animated.spring(this._animatedValue, {
toValue: {x: 20, y: 20},
}).start();
} else if (this.whichField(gesture) == 2) {
Animated.spring(this._animatedValue, {
toValue: {x: width - 120, y: 20},
}).start();
} else if (this.whichField(gesture) == 3) {
Animated.spring(this._animatedValue, {
toValue: {x: 20, y: height - 150},
}).start();
} else {
Animated.spring(this._animatedValue, {
toValue: {x: width - 120, y: height - 150},
}).start();
}
},
});
}
whichField(gesture) {
var sm = {height, width};
let field;
{
gesture.moveY < sm.height / 2 && gesture.moveX < sm.width / 2
? (field = 1)
: gesture.moveY < sm.height / 2 && gesture.moveX > sm.width / 2
? (field = 2)
: gesture.moveY > sm.height / 2 && gesture.moveX < sm.width / 2
? (field = 3)
: (field = 4);
}
return field;
}
render() {
return (
<View style={styles.container}>
<ScrollView>
{['', '', '', '', '', ''].map(() => (
<View style={styles.scrollItem} />
))}
</ScrollView>
<Animated.View
style={[
styles.box,
{
transform: [
{translateX: this._animatedValue.x},
{translateY: this._animatedValue.y},
],
},
]}
{...this._panResponder.panHandlers}>
<Image
source={{
uri:
'https://pluspng.com/img-png/user-png-icon-male-user-icon-512.png',
}}
style={StyleSheet.absoluteFill}
/>
</Animated.View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
box: {
width: 100,
height: 100,
borderRadius: 50,
backgroundColor: '#fff',
position: 'absolute',
},
scrollItem: {
height: 300,
width: '100%',
backgroundColor: 'grey',
marginBottom: 10,
},
});
Check in Expo
I hope it will help you.
I need to pass the details of the currently active slide to another js file. how can I do this (there are 8 slides)?
The details that I need to pass is the slide's name and Index,
Here is my carousel js file :
import React, { Component } from 'react';
import { Dimensions, View, Image } from 'react-native';
import Carousel from 'react-native-snap-carousel';
const { height, width } = Dimensions.get('window');
class TeamScroll extends Component {
render() {
return (
<View >
<View style={{ transform: [{ rotate: '-14deg' }] }}>
<Carousel
ref={(c) => { this.props.carouselRef = c; }}
inactiveSlideOpacity={0.6}
inactiveSlideScale={0.65}
firstItem={1}
sliderWidth={width}
itemWidth={width / 3} >
<Image
source={require('./Images/logo-chepauk.png')}
style={styles.logoStyle} />
<Image
source={require('./Images/logo-dindigul.png')}
style={styles.logoStyle} />
<Image
source={require('./Images/logo-kanchi.png')}
style={styles.logoStyle} />
<Image
source={require('./Images/logo-karaikudi.png')}
style={styles.logoStyle} />
<Image
source={require('./Images/logo-kovai.png')}
style={styles.logoStyle} />
<Image
source={require('./Images/logomadurai.png')}
style={styles.logoStyle} />
<Image
source={require('./Images/logothiruvallur.png')}
style={styles.logoStyle} />
<Image
source={require('./Images/logotuti.png')}
style={styles.logoStyle} />
</Carousel>
</View>
</View>
);
}
}
const styles = {
logoStyle: {
transform: [{ rotate: '14deg' }],
width: width / 3,
height: width / 3
}
};
export default TeamScroll;
Here is one of the files where I need to use these details
import React, { Component } from 'react';
import { Image, Text, View, TouchableWithoutFeedback, Animated, Dimensions } from 'react-native';
import { Actions } from 'react-native-router-flux';
import TeamScroll from './TeamScroll';
const a = require('./Images/over3_selected.png');
const b = require('./Images/over3.png');
const c = require('./Images/over5_selected.png');
const d = require('./Images/over5.png');
const e = require('./Images/over10_selected.png');
const f = require('./Images/over10.png');
const Sound = require('react-native-sound');
const btnSound = new Sound('btn_sound.mp3', Sound.MAIN_BUNDLE);
const w = Dimensions.get('window').width;
const h = Dimensions.get('window').height;
class ChallengeScreen extends Component {
state = {
threePressed: false,
fivePressed: false,
tenPressed: false,
}
componentWillMount() {
this.slide1 = new Animated.Value(0);
this.slide2 = new Animated.Value(0);
this.slide3 = new Animated.Value(0);
this.ball1();
this.ball2();
this.ball3();
}
ball1() {
Animated.timing(
this.slide1, {
delay: 100,
toValue: -(w / 2.57),
duration: 700,
}
).start();
}
ball2() {
Animated.timing(
this.slide2, {
delay: 200,
toValue: -(w / 2.25),
duration: 900,
}
).start();
}
ball3() {
Animated.timing(
this.slide3, {
delay: 300,
toValue: -(w / 2),
duration: 1100,
}
).start();
}
render() {
return (
<Image
source={require('./Images/bg_inner.png')} style={styles.backgroundStyle}>
<Text style={styles.chooseteamtextStyle}>
CHOOSE YOUR TEAM
</Text>
<Image source={require('./Images/team-logo-band.png')} style={styles.band1Style}>
<TeamScroll carouselRef />
</Image>
<Text style={styles.opponentStyle}>
YOUR OPPONENT
</Text>
<Image source={require('./Images/team-logo-band.png')} style={styles.band2Style}>
<TeamScroll carouselRef />
</Image>
<Text style={styles.overstextStyle}>
OVERS
</Text>
<View style={styles.viewStyle}>
<TouchableWithoutFeedback
onPress={() => {
btnSound.play();
playFunc(3, this.props.challenge); }
}
onPressIn={() => {
this.setState({ threePressed: true });
}}
onPressOut={() => {
this.setState({ threePressed: false });
}}
>
<Animated.Image source={this.state.threePressed ? a : b}
style={[styles.over3Style, { transform: [{ translateY: this.slide1 }] }]} />
</ TouchableWithoutFeedback>
<TouchableWithoutFeedback
onPress={() => {
btnSound.play();
playFunc(5, this.props.challenge); }
}
onPressIn={() => {
this.setState({ fivePressed: true });
}}
onPressOut={() => {
this.setState({ fivePressed: false });
}}>
<Animated.Image source={this.state.fivePressed ? c : d}
style={[styles.over5Style, { transform: [{ translateY: this.slide2 }] }]} />
</ TouchableWithoutFeedback>
<TouchableWithoutFeedback
onPress={() => {
btnSound.play();
playFunc(10, this.props.challenge); }
}
onPressIn={() => {
this.setState({ tenPressed: true });
}}
onPressOut={() => {
this.setState({ tenPressed: false });
}}>
<Animated.Image source={this.state.tenPressed ? e : f}
style={[styles.over10Style, { transform: [{ translateY: this.slide3 }] }]} />
</ TouchableWithoutFeedback>
</View>
</ Image>
);
}
}
function playFunc(num, param) {
if (num === 3 && param === 'Computer') {
Actions.screen4({ balls: 18 });
}
else if (num === 5 && param === 'Computer') {
Actions.screen4({ balls: 30 });
}
else if (num === 10 && param === 'Computer') {
Actions.screen4({ balls: 60 });
}
else if (num === 3 && param === 'Team') {
Actions.screen3({ balls: 18 });
}
else if (num === 5 && param === 'Team') {
Actions.screen3({ balls: 30 });
}
else if (num === 10 && param === 'Team') {
Actions.screen3({ balls: 60 });
}
}
const styles = {
viewStyle: {
flexDirection: 'row',
justifyContent: 'flex-start'
},
backgroundStyle: {
flex: 1,
width: w,
height: h,
flexWrap: 'wrap',
},
chooseteamtextStyle: {
textAlign: 'center',
marginTop: h / 6.57,
fontSize: 22,
color: 'white',
fontFamily: 'Switzerland-Cond-Black-Italic',
transform: [{ rotate: '-14deg' }]
},
band1Style: {
resizeMode: 'stretch',
width: (w / 0.947),
height: (h / 3.93),
},
opponentStyle: {
textAlign: 'center',
marginTop: -(h / 59.2),
fontSize: 22,
color: 'white',
fontFamily: 'Switzerland-Cond-Black-Italic',
transform: [{ rotate: '-15deg' }]
},
band2Style: {
resizeMode: 'stretch',
width: (w / 0.947),
height: (h / 4),
},
overstextStyle: {
textAlign: 'center',
bottom: (h / 59.2),
fontSize: 22,
color: 'white',
fontFamily: 'Switzerland-Cond-Black-Italic',
transform: [{ rotate: '-15deg' }]
},
over3Style: {
flexDirection: 'row',
alignItems: 'flex-start',
width: (w / 4.5),
height: (h / 7.4),
top: (h / 3.482),
left: (w / 5.142),
},
over5Style: {
flexDirection: 'row',
alignItems: 'center',
width: (w / 4.5),
height: (h / 7.4),
bottom: -(h / 3.48),
left: (h / 8.45)
},
over10Style: {
flexDirection: 'row',
alignItems: 'flex-end',
width: (w / 4.5),
height: (h / 7.4),
top: (h / 3.48),
right: -(w / 5.42)
}
};
export default ChallengeScreen;
I have tried using state and props for doing it, and also using getters like currentIndex using carousel's reference but couldn't get the details
What about adding a dedicated prop to <TeamScroll />?
TeamScroll.js
class TeamScroll extends Component {
static propTypes = {
snapCallback: PropTypes.func
}
static defaultProps = {
snapCallback: () => {}
}
constructor (props) {
super(props);
this._onSnapToItem = this._onSnapToItem.bind(this);
}
_onSnapToItem (index) {
this.props.snapCallback(index, this.state.customData);
}
render() {
return (
<View >
<View style={{ transform: [{ rotate: '-14deg' }] }}>
<Carousel onSnapToItem={this._onSnapToItem}>
// images
</Carousel>
</View>
</View>
);
}
}
ChallengeScreen.js
class ChallengeScreen extends Component {
onSnap (index, data) {
console.log('CAROUSEL INDEX', { index, data });
}
render() {
return (
<Image source={require('./Images/team-logo-band.png')} style={styles.band2Style}>
<TeamScroll carouselRef snapCallback={onSnap} />
</Image>
);
}
}
I'm using this
class TeamScroll extends Component {
constructor(props) {
super(props);
this.state = {
currentIndex: 0,
};
}
changePage(nextIndex, isLast) {
this.setState({ currentIndex: nextIndex });
this.props.onChangePage(nextIndex + 1, isLast);
}
render() {
return (
<Page>
<Carousel
ref={(carousel) => { this.carousel = carousel; }}
firstItem={this.state.currentIndex}
onSnapToItem={(index) => this.changePage(index, index === screens.length - 1)}
data={screens}
renderItem={this.renderCarouselItem}
/>
);
}
}
Note that I'm using the new syntax introduced in version 3, but it works as well in version 2.
The class has onChangePage prop that gets called when you snap to another item.
You can use the onChangePage with
<TeamScroll onChangePage={(pageIndex, isLastPage) => {
// do something here, maybe
this.setState({ currentPage: pageIndex });
}} />
I have the following code for my index.io.js file. I am new to react native and don't quite know how to port it to index.android.js. Is there a certain standard that I can follow to migrate this to the android version? What needs to be done exactly?
'use strict';
var ShakeEvent = require('react-native-shake-event-ios');
var NetworkImage = require('react-native-image-progress');
var Progress = require('react-native-progress');
var Swiper = require('react-native-swiper');
var RandManager = require('./RandManager.js');
var Utils = require('./Utils.js');
var React = require('react-native');
// Components
var ProgressHUD = require('./ProgressHUD.js');
var {
AppRegistry,
StyleSheet,
Text,
View,
Component,
ActivityIndicatorIOS,
Dimensions,
PanResponder,
CameraRoll,
AlertIOS
} = React;
var {width, height} = Dimensions.get('window');
const NUM_WALLPAPERS = 5;
const DOUBLE_TAP_DELAY = 300; // milliseconds
const DOUBLE_TAP_RADIUS = 20;
class SplashWalls extends Component{
constructor(props) {
super(props);
this.state = {
wallsJSON: [],
isLoading: true,
isHudVisible: false
};
this.imagePanResponder = {};
// Previous touch information
this.prevTouchInfo = {
prevTouchX: 0,
prevTouchY: 0,
prevTouchTimeStamp: 0
};
this.currentWallIndex = 0;
// Binding this to methods
this.handlePanResponderGrant = this.handlePanResponderGrant.bind(this);
this.onMomentumScrollEnd = this.onMomentumScrollEnd.bind(this);
}
componentDidMount() {
this.fetchWallsJSON();
}
componentWillMount() {
this.imagePanResponder = PanResponder.create({
onStartShouldSetPanResponder: this.handleStartShouldSetPanResponder,
onPanResponderGrant: this.handlePanResponderGrant,
onPanResponderRelease: this.handlePanResponderEnd,
onPanResponderTerminate: this.handlePanResponderEnd
});
// Fetch new wallpapers on shake
ShakeEvent.addEventListener('shake', () => {
this.initialize();
this.fetchWallsJSON();
});
}
render() {
var {isLoading} = this.state;
if(isLoading)
return this.renderLoadingMessage();
else
return this.renderResults();
}
initialize() {
this.setState({
wallsJSON: [],
isLoading: true,
isHudVisible: false
});
this.currentWallIndex = 0;
}
fetchWallsJSON() {
var url = 'http://unsplash.it/list';
fetch(url)
.then( response => response.json() )
.then( jsonData => {
var randomIds = RandManager.uniqueRandomNumbers(NUM_WALLPAPERS, 0, jsonData.length);
var walls = [];
randomIds.forEach(randomId => {
walls.push(jsonData[randomId]);
});
this.setState({
isLoading: false,
wallsJSON: [].concat(walls)
});
})
.catch( error => console.log('JSON Fetch error : ' + error) );
}
renderLoadingMessage() {
return (
<View style={styles.loadingContainer}>
<ActivityIndicatorIOS
animating={true}
color={'#fff'}
size={'small'}
style={{margin: 15}} />
<Text style={{color: '#fff'}}>Contacting Unsplash</Text>
</View>
);
}
saveCurrentWallpaperToCameraRoll() {
// Make Progress HUD visible
this.setState({isHudVisible: true});
var {wallsJSON} = this.state;
var currentWall = wallsJSON[this.currentWallIndex];
var currentWallURL = `http://unsplash.it/${currentWall.width}/${currentWall.height}?image=${currentWall.id}`;
CameraRoll.saveImageWithTag(currentWallURL, (data) => {
// Hide Progress HUD
this.setState({isHudVisible: false});
AlertIOS.alert(
'Saved',
'Wallpaper successfully saved to Camera Roll',
[
{text: 'High 5!', onPress: () => console.log('OK Pressed!')}
]
);
},(err) =>{
console.log('Error saving to camera roll', err);
});
}
onMomentumScrollEnd(e, state, context) {
this.currentWallIndex = state.index;
}
renderResults() {
var {wallsJSON, isHudVisible} = this.state;
return (
<View>
<Swiper
dot={<View style={{backgroundColor:'rgba(255,255,255,.4)', width: 8, height: 8,borderRadius: 10, marginLeft: 3, marginRight: 3, marginTop: 3, marginBottom: 3,}} />}
activeDot={<View style={{backgroundColor: '#fff', width: 13, height: 13, borderRadius: 7, marginLeft: 7, marginRight: 7}} />}
onMomentumScrollEnd={this.onMomentumScrollEnd}
index={this.currentWallIndex}
loop={false}>
{wallsJSON.map((wallpaper, index) => {
return(
<View key={wallpaper.id}>
<NetworkImage
source={{uri: `https://unsplash.it/${wallpaper.width}/${wallpaper.height}?image=${wallpaper.id}`}}
indicator={Progress.Circle}
indicatorProps={{
color: 'rgba(255, 255, 255)',
size: 60,
thickness: 7
}}
threshold={0}
style={styles.wallpaperImage}
{...this.imagePanResponder.panHandlers}>
<Text style={styles.label}>Photo by</Text>
<Text style={styles.label_authorName}>{wallpaper.author}</Text>
</NetworkImage>
</View>
);
})}
</Swiper>
<ProgressHUD width={width} height={height} isVisible={isHudVisible}/>
</View>
);
}
// Pan Handler methods
handleStartShouldSetPanResponder(e, gestureState){
return true;
}
handlePanResponderGrant(e, gestureState) {
var currentTouchTimeStamp = Date.now();
if( this.isDoubleTap(currentTouchTimeStamp, gestureState) )
this.saveCurrentWallpaperToCameraRoll();
this.prevTouchInfo = {
prevTouchX: gestureState.x0,
prevTouchY: gestureState.y0,
prevTouchTimeStamp: currentTouchTimeStamp
};
}
handlePanResponderEnd(e, gestureState) {
// When finger is pulled up from the screen
}
isDoubleTap(currentTouchTimeStamp, {x0, y0}) {
var {prevTouchX, prevTouchY, prevTouchTimeStamp} = this.prevTouchInfo;
var dt = currentTouchTimeStamp - prevTouchTimeStamp;
return (dt < DOUBLE_TAP_DELAY && Utils.distance(prevTouchX, prevTouchY, x0, y0) < DOUBLE_TAP_RADIUS);
}
};
var styles = StyleSheet.create({
loadingContainer: {
flexDirection: 'row',
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#000'
},
wallpaperImage: {
flex: 1,
width: width,
height: height,
backgroundColor: '#000'
},
label: {
position: 'absolute',
color: '#fff',
fontSize: 13,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 2,
paddingLeft: 5,
top: 20,
left: 20,
width: width/2
},
label_authorName: {
position: 'absolute',
color: '#fff',
fontSize: 15,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 2,
paddingLeft: 5,
top: 41,
left: 20,
fontWeight: 'bold',
width: width/2
}
});
AppRegistry.registerComponent('SplashWalls', () => SplashWalls);
Copy content of index.ios.js to index.android.js first. If it does not contain any iOS specific code/libs, it will just work. If not, go to step 2.
Adjust code to use Android specific code, or lib API - if such code exists. If not, go to step 3.
Either swap iOS code/libs for Android ones, or write required code yourself.