Related
I want to implement this design using React Native. For navigation
purpose I am using React Navigation 5. I tried using react-native SVG. Not having good knowledge in svg designing.
Here is the sample code.
import Svg, { Path } from 'react-native-svg';
import * as shape from 'd3-shape';
const { width } = Dimensions.get('window');
const height = 64;
const tabWidth = width / 4;
const AnimatedSvg = Animated.createAnimatedComponent(Svg)
const left = shape.line()
.x(d => d.x)
.y(d => d.y)
([
{ x: 0, y: 0 },
{ x: width, y: 0 }
]);
const right = shape.line()
.x(d => d.x)
.y(d => d.y)
([
{ x: width + tabWidth, y: 0 },
{ x: width * 2, y: 0 },
{ x: width * 2, y: height },
{ x: 0, y: height },
{ x: 0, y: 0 },
])
const tab = shape.line()
.x(d => d.x)
.y(d => d.y)
.curve(shape.curveBasis)
([
{ x: width, y: 0 },
{ x: width + 5, y: 0 },
{ x: width + 10, y: 0 },
{ x: width + 15, y: height },
{ x: width + tabWidth - 15, y: height },
{ x: width + tabWidth - 10, y: 10 },
{ x: width + tabWidth -5, y: 0 },
])
const d = `${right} ${left} ${tab}`;
const Test = () => {
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={{ flex: 1, backgroundColor: '#ea3345', justifyContent: 'flex-end' }}>
<View style={{ height }}>
<AnimatedSvg
style={{
transform:[
{
translateX:-100
}
]
}}
width={width*2}
{...{height}}>
<Path {...{ d }} fill="white" />
</AnimatedSvg>
</View>
</View>
</SafeAreaView>
)
}
export default Test
Please help me to achieve this kind of design. Thanks in advance.
I am trying to make an image slider that autoscrolls horizontally with react native scrollview, it returns error can't find error _scrollView. Who knows a fix or a better to go about it.
Right I have to manually move between the images.I tried adding a ref to the scrollView, but the ref seems to be giving error.
import React, { Component } from 'react'
import { Animated, View, StyleSheet, Image, Dimensions, ScrollView } from 'react-native'
const deviceWidth = Dimensions.get('window').width
const deviceHeight = Dimensions.get('window').height
const FIXED_BAR_WIDTH = 280
const BAR_SPACE = 10
const images = [
require("../../assets/images/banner_1.jpg"),
require("../../assets/images/banner2.jpg")
]
export default class ImgSlider extends Component {
_scrollView = React.createRef();
componentDidMount() {
const numOfBackground = 2;
let scrollValue = 0, scrolled = 0;
setInterval(function () {
scrolled++;
if(scrolled < numOfBackground)
scrollValue = scrollValue + deviceWidth;
else{
scrollValue = 0;
scrolled = 0
}
_scrollView.scrollTo({ x: scrollValue, animated: false })
}, 3000);
}
numItems = images.length
itemWidth = (FIXED_BAR_WIDTH / this.numItems) - ((this.numItems - 1) * BAR_SPACE)
animVal = new Animated.Value(0)
render() {
let imageArray = []
let barArray = []
images.forEach((image, i) => {
console.log(image, i)
const thisImage = (
<Image
key={`image${i}`}
source={image}
style={{ width: deviceWidth, height: 300 }}
/>
)
imageArray.push(thisImage)
const scrollBarVal = this.animVal.interpolate({
inputRange: [deviceWidth * (i - 1), deviceWidth * (i + 1)],
outputRange: [-this.itemWidth, this.itemWidth],
extrapolate: 'clamp',
})
const thisBar = (
<View
key={`bar${i}`}
style={[
styles.track,
{
width: this.itemWidth,
marginLeft: i === 0 ? 0 : BAR_SPACE,
},
]}
>
<Animated.View
style={[
styles.bar,
{
width: this.itemWidth,
transform: [
{ translateX: scrollBarVal },
],
},
]}
/>
</View>
)
barArray.push(thisBar)
})
return (
<View
style={styles.container}
flex={1}
>
<ScrollView
horizontal
ref={this._scrollView}
showsHorizontalScrollIndicator={false}
scrollEventThrottle={10}
pagingEnabled
// ref={(scrollView) => { _scrollView = scrollView }}
onScroll={
Animated.event(
[{ nativeEvent: { contentOffset: { x: this.animVal } } }]
)
}
>
{imageArray}
</ScrollView>
<View
style={styles.barContainer}
>
{barArray}
</View>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
barContainer: {
position: 'absolute',
zIndex: 2,
top: 290,
flexDirection: 'row',
},
track: {
backgroundColor: '#ccc',
overflow: 'hidden',
height: 2,
},
bar: {
backgroundColor: '#BB1E18',
height: 2,
position: 'absolute',
left: 0,
top: 0,
},
})
this._scrollView does not have the function scrollTo try console logging whats init and see whats going on in there.
Topic: Video Flatlist in React Native. (Normal Videos / YouTube Videos)
Any one will be displayed according to the array data simultaneously. So it will be a long flatlist containing two types of video.
Sometimes many onPress event doesn't work due to performance issue
Major Issues:
App crashes when scrolling bottom to top
Performance is very poor
Above two are the major issue need to solve. Are there any ways to solve these issues?
What solutions can I try to improve Flatlist performance and to solve Video Flatlist crashing issue?
Below is my code in App.js
import React from "react";
import {
StyleSheet,
Text,
View,
SafeAreaView,
FlatList,
TouchableWithoutFeedback,
Dimensions
} from "react-native";
import Video from "react-native-video";
import ProgressBar from "react-native-progress/Bar";
import YouTube from 'react-native-youtube';
import LightVideo from "./sample.mp4";
import Icon from "react-native-vector-icons/FontAwesome";
// console.disableYellowBox = true;
function secondsToTime(time) {
return ~~(time / 60) + ":" + (time % 60 < 10 ? "0" : "") + time % 60;
}
const { width } = Dimensions.get("window");
const height = width * 0.5625;
const list = [
{
'type': 'YouTube'
},
{
'type': 'Normal'
},
{
'type': 'YouTube'
},
{
'type': 'Normal'
},
{
'type': 'Normal'
},
];
export default class RNVideo extends React.PureComponent {
state = {
video: [],
duration: [0, 0, 0, 0, 0],
progress: [0, 0, 0, 0, 0]
};
constructor(props) {
super(props);
this.player = [];
}
componentDidMount() {
this.progress_state();
this.duration_state();
this.video_pause_state(false);
}
play_pause_button = (index) => {
if (this.state.progress >= 1) {
this.player[index].seek(0);
}
var statevisible = this.state.video;
statevisible[index] = !statevisible[index];
this.setState({
video: statevisible
});
this.forceUpdate();
};
progress_bar_touch(e, index) {
const position = e.nativeEvent.locationX;
const progress = (position / 250) * this.state.duration[index];
this.player[index].seek(progress);
};
handleProgress(progress, index) {
var statevisible = this.state.progress;
statevisible[index] = progress.currentTime / this.state.duration[index];
this.setState({
progress: statevisible
});
this.forceUpdate();
};
handleLoad(meta, index) {
var statevisible = this.state.duration;
statevisible[index] = meta.duration;
this.setState({
duration: statevisible
});
this.forceUpdate();
};
onViewableItemsChanged = async ({ viewableItems }) => {
this.video_pause_state(true);
if (viewableItems != '' && viewableItems != null) {
var indexvalue = viewableItems[0]['index'];
var statevisible = this.state.video;
statevisible[indexvalue] = !statevisible[indexvalue];
this.setState({
video: statevisible
});
this.forceUpdate();
}
}
// shouldComponentUpdate() {
// return false
// }
video_pause_state(value) {
var statevisible = [];
for (var i = 0; i < list.length; i++) {
statevisible[i] = value;
}
this.setState({
video: statevisible
});
}
progress_state() {
var statevisible = [];
for (var i = 0; i < list.length; i++) {
statevisible[i] = 0;
}
this.setState({
progress: statevisible
});
}
duration_state() {
var statevisible = [];
for (var i = 0; i < list.length; i++) {
statevisible[i] = 0;
}
this.setState({
duration: statevisible
});
}
video_end(index) {
var statevisible1 = this.state.duration;
statevisible1[index] = 0;
var statevisible2 = this.state.progress;
statevisible2[index] = 0;
this.setState({
duration: statevisible1,
progress: statevisible2
});
this.play_pause_button(index);
}
keyExtractor = (item, index) => index.toString()
renderItem = ({ item, index }) => (
<>
{item.type != 'YouTube' ?
<>
<View style={styles.container}>
<View>
<Video
paused={this.state.video[index]}
muted={this.state.video[index]}
source={LightVideo}
// controls={true}
// repeat={true}
style={styles.videos}
resizeMode="cover"
onLoad={(meta) => this.handleLoad(meta, index)}
onProgress={(progress) => this.handleProgress(progress, index)}
onEnd={() => this.video_end(index)}
ref={(ref) => {
this.player[index] = ref
}}
/>
<View style={styles.controls}>
<TouchableWithoutFeedback onPress={(event) => this.play_pause_button(index)}>
<Icon name={!this.state.video[index] ? "pause" : "play"} size={30} color="#FFF" />
</TouchableWithoutFeedback>
<TouchableWithoutFeedback onPress={(event) => this.progress_bar_touch(event, index, this)}>
<View>
<ProgressBar
progress={this.state.progress[index]}
color="#FFF"
unfilledColor="rgba(255,255,255,.5)"
borderColor="#FFF"
width={230}
height={20}
/>
</View>
</TouchableWithoutFeedback>
<Text style={styles.duration}>
{secondsToTime(Math.floor(this.state.progress[index] * this.state.duration[index]))}
</Text>
</View>
</View>
</View>
</>
:
<>
<View style={{ marginTop: 50 }}>
<YouTube
apiKey="YOUR_API_KEY"
videoId="YOUR_YOUTUBE_VIDEO_ID_FROM_URL"
play={this.state.video[index]}
style={styles.videos}
/>
</View>
</>
}
</>
)
render() {
return (
<>
<SafeAreaView style={{ flex: 1 }}>
<FlatList
keyExtractor={this.keyExtractor}
data={list}
renderItem={this.renderItem}
extraData={this.state}
onViewableItemsChanged={this.onViewableItemsChanged}
removeClippedSubviews={true}
maxToRenderPerBatch={3}
initialNumToRender={3}
legacyImplementation={true}
/>
</SafeAreaView>
</>
);
}
}
const wid = "95%";
const styles = StyleSheet.create({
container: {
flex: 1,
marginBottom: 60,
marginTop: 60,
},
controls: {
backgroundColor: "rgba(0, 0, 0, 0.5)",
height: 48,
left: 0,
bottom: 0,
right: 0,
position: "absolute",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-around",
width: wid,
margin: 8,
borderBottomLeftRadius: 10,
borderBottomRightRadius: 10,
paddingHorizontal: 10,
},
mainButton: {
marginRight: 15,
},
duration: {
color: "#FFF",
marginLeft: 15,
},
videos: {
width: wid,
backgroundColor: '#ccc',
borderRadius: 10,
overflow: 'hidden',
margin: 8,
height
},
});
Update
I have come up with this final solution. But not able to do autoplay. Need ideas on this.
import React from 'react';
import { View, StyleSheet, SafeAreaView } from 'react-native';
import YouTube from 'react-native-youtube';
import { OptimizedFlatList } from 'react-native-optimized-flatlist'
import VideoPlayer from 'react-native-video-player';
const list = [
{
'type': 'Normal'
},
{
'type': 'YouTube'
},
{
'type': 'Normal'
},
{
'type': 'Normal'
},
{
'type': 'YouTube'
},
{
'type': 'Normal'
},
{
'type': 'Normal'
},
{
'type': 'Normal'
},
];
export default class App extends React.PureComponent {
constructor() {
super();
this.player = [];
this.state = {
video: { width: undefined, height: undefined, duration: undefined },
thumbnailUrl: undefined,
videoUrl: undefined,
};
}
keyExtractor = (item, index) => index.toString()
renderItem = ({ item, index }) => (
<>
{item.type != 'YouTube' ?
<>
<View style={styles.videos}>
<VideoPlayer
// autoplay
// pauseOnPress
video={{ uri: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', type: 'mp4' }}
resizeMode={'cover'}
ref={r => this.player[index] = r}
/>
</View>
</>
:
<>
<View style={{ marginTop: 50 }}>
<YouTube
apiKey="YOUR_API_KEY"
videoId="3NQRhE772b0"
style={{...styles.videos,...styles.videos1}}
/>
</View>
</>
}
</>
)
onViewableItemsChanged = async ({ viewableItems }) => {
if (viewableItems != '' && viewableItems != null) {
var indexvalue = viewableItems[0]['index'];
indexvalue++;
if (indexvalue != 1) {
if (!this.player[indexvalue].state.isPlaying) {
this.player[indexvalue].pause();
} else {
this.player[indexvalue].resume();
}
}
}
}
render() {
return (
<>
<SafeAreaView style={{ flex: 1 }}>
<OptimizedFlatList
keyExtractor={this.keyExtractor}
data={list}
renderItem={this.renderItem}
removeClippedSubviews={true}
onViewableItemsChanged={this.onViewableItemsChanged}
/>
</SafeAreaView>
</>
);
}
}
const styles = StyleSheet.create({
videos: {
width: "95%",
backgroundColor: '#ccc',
borderRadius: 10,
overflow: 'hidden',
margin: 10,
marginBottom: 20,
marginTop: 20
},
videos1: {
height: 250
}
});
Hopefully you can help me with a bug I'm having a bit of bother sorting out. I'm working on a bug in an app built using React Native. It is building to IOS and Android. I have a ScrollView in a component that contains cards that are draggable objects.
These cards are dragged from the ScrollView they are in, up to buckets at the top of the screen. They disappear from the ScrollView and the remaining ones get reorganised so they stay ordered and neat. That works fine, you press on a box in the list and drag it to the buckets.
There is a bit of whitespace above the list of cards in the ScrollView. The ScrollView functionality works when swiping within this whitespace above the boxes, but I can't swipe on the boxes themselves without it beginning to drag the card.
Here is the component itself:
import React, { Component } from 'react';
import { StyleSheet, Text, View, ScrollView, Dimensions, Alert } from 'react-native';
import { connect } from 'react-redux';
import * as ConstStyles from '../../Consts/styleConsts';
import Bucket from '../Partials/bucketContainers';
import BusyIndicator from 'react-native-busy-indicator';
import loaderHandler from 'react-native-busy-indicator/LoaderHandler';
import CatCard from '../Partials/categoryCard';
import * as FeedActions from '../../../Redux/Feeds/actions';
import * as AuthFunctions from '../../Auth/functions';
export class SetupLikes extends Component {
static navigatorStyle = ConstStyles.standardNav;
constructor(props) {
super(props);
this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
this.card = [];
let button = {
leftButtons: [
{
title: 'Reset',
id: 'reset'
}
],
rightButtons: [
{
title: this.props.newAccount ? 'Go' : 'Done',
id: this.props.newAccount ? '' : 'skip',
disabled: this.props.newAccount
}
]
};
this.props.navigator.setButtons(button);
}
state = {
xOffset: 0,
positions: [],
placedCards: [],
loves: [],
okays: [],
hates: []
};
onNavigatorEvent(event) {
setTimeout(async () => {
if (event.type === 'NavBarButtonPress') {
if (event.id === 'skip') {
this.props.navigator.dismissModal({
animationType: 'slide-down'
});
} else if (event.id === 'reset') {
await this.imgTap();
} else if (event.id === 'go') {
await this.setInterests();
}
}
}, 0);
}
async setInterests() {
loaderHandler.showLoader('Setting your interests...');
let newInterests = [];
this.state.loves.forEach(function(element) {
let cat = this.props.Feeds.categories[element];
let newItem = {
categoryid: cat.id,
sentimentid: 1
};
newInterests.push(newItem);
}, this);
this.state.okays.forEach(function(element) {
let cat = this.props.Feeds.categories[element];
let newItem = {
categoryid: cat.id,
sentimentid: 0
};
newInterests.push(newItem);
}, this);
this.state.hates.forEach(function(element) {
let cat = this.props.Feeds.categories[element];
let newItem = {
categoryid: cat.id,
sentimentid: -1
};
newInterests.push(newItem);
}, this);
let sesId = this.props.User.sessionId;
try {
await this.props.dispatch(FeedActions.setMyInterests(sesId, newInterests));
loaderHandler.hideLoader();
} catch (err) {
loaderHandler.hideLoader();
Alert.alert('Uh oh', 'Something went wrong. Please try again later');
return;
}
await AuthFunctions.setupAppLogin(this.props.dispatch, sesId);
}
async imgTap() {
await this.setState({ placedCards: [], loves: [], okays: [], hates: [], positions: [] });
setTimeout(() => {
let cntr = 0;
this.card.forEach(function(element) {
cntr++;
if (this.state.placedCards.includes(cntr - 1)) return;
if (element) element.snapTo({ index: 0 });
}, this);
}, 5);
this.props.navigator.setButtons({
rightButtons: [
{
title: 'Go',
id: '',
disabled: true
}
],
animated: true
});
}
cardPlaced(id, droppedIndex) {
let newList = this.state.placedCards;
newList.push(id);
let cntr = 0;
let offset = 0;
let newPosIndex = [];
this.props.Feeds.categories.forEach(cats => {
let posY = (offset % 2) * -120 - 20;
let xOffset = Math.floor(offset / 2);
let posX = xOffset * 105 + 10;
newPosIndex[cntr] = {
x: posX,
y: posY,
offset: offset % 2
};
if (!newList.includes(cntr)) offset++;
cntr++;
});
if (droppedIndex === 1) {
let newLoves = this.state.loves;
newLoves.push(id);
this.setState({
loves: newLoves,
placedCards: newList,
positions: newPosIndex
});
} else if (droppedIndex === 2) {
let newOkays = this.state.okays;
newOkays.push(id);
this.setState({
okays: newOkays,
placedCards: newList,
positions: newPosIndex
});
} else if (droppedIndex === 3) {
let newHates = this.state.hates;
newHates.push(id);
this.setState({
hates: newHates,
placedCards: newList,
positions: newPosIndex
});
}
}
reShuffle() {
let cntr = 0;
this.card.forEach(function(element) {
cntr++;
if (this.state.placedCards.includes(cntr - 1)) return;
if (element) element.snapTo({ index: 0 });
}, this);
}
setButton() {
this.props.navigator.setButtons({
rightButtons: [
{
title: this.props.newAccount ? 'Go' : 'Done',
id: 'go'
}
],
animated: true
});
}
onChangeSize(scrollWidth, scrollHeight) {
let { height, width } = Dimensions.get('window');
let farRight = this.state.xOffset + width;
if (farRight > scrollWidth && farRight > 0) {
let xOffset = scrollWidth - width;
this.setState({ xOffset });
}
}
onSnap(index, id) {
this.cardPlaced(id, index);
this.reShuffle();
this.setButton();
if (this.props.Feeds.categories.length === this.state.placedCards.length)
setTimeout(async () => {
await this.setInterests();
}, 1);
}
renderCats() {
let cntr = 0;
var { height, width } = Dimensions.get('window');
let res = this.props.Feeds.categories.map(item => {
let ptr = cntr;
let posY = (cntr % 2) * -120 - 20;
let xOffset = Math.floor(cntr / 2);
let posX = xOffset * 105 + 10;
let vertPos = posY - 200 + ((cntr + 1) % 2) * -120;
posX = this.state.positions[ptr] ? this.state.positions[ptr].x : posX;
posY = this.state.positions[ptr] ? this.state.positions[ptr].y : posY;
let off = this.state.positions[ptr] ? this.state.positions[ptr].offset : ptr % 2;
cntr++;
if (this.state.placedCards.includes(cntr - 1)) return null;
item.key = cntr;
return (
<CatCard
key={ptr}
item={item}
ptr={ptr}
cntr={cntr}
xOffset={this.state.xOffset}
odd={off}
posX={posX}
posY={posY}
yDrop={vertPos}
screenWidth={width}
onSnap={(res, id) => this.onSnap(res, id)}
gotRef={ref => (this.card[ptr] = ref)}
/>
);
});
cntr = 0;
res.forEach(ele => {
if (ele !== null) ele.key = cntr++;
});
let test = this.props.Feeds.categories[0];
return res;
}
onScroll(res) {
this.setState({ xOffset: res.nativeEvent.contentOffset.x });
}
render() {
let colWidth = Math.ceil((this.props.Feeds.categories.length - this.state.placedCards.length) / 2) * 106;
return (
<View style={styles.container}>
<View style={styles.bucketContainer1}>
<Bucket
type={'Love'}
imageToUse={require('../../../img/waveLove.png')}
height={this.state.loveHeight}
count={this.state.loves.length}
backgroundColor={'rgb(238, 136, 205)'}
/>
</View>
<View style={styles.bucketContainer2}>
<Bucket
type={'OK'}
imageToUse={require('../../../img/waveOkay.png')}
height={this.state.okayHeight}
count={this.state.okays.length}
backgroundColor={'rgb(250, 179, 39)'}
/>
</View>
<View style={styles.bucketContainer3}>
<Bucket
type={'Dislike'}
imageToUse={require('../../../img/waveHate.png')}
height={this.state.hateHeight}
count={this.state.hates.length}
backgroundColor={'rgb(112, 127, 208)'}
/>
</View>
<View style={styles.descriptionContainer}>
<Text style={styles.dragLikesTitle}>Drag Likes</Text>
<View style={styles.dividingLine} />
<View>
<Text style={styles.descriptionText}>Drag your likes and dislikes into the bucket above,</Text>
<Text style={styles.descriptionText}>so we can generate your profile!</Text>
</View>
</View>
<ScrollView
ref={ref => (this.scroller = ref)}
onMomentumScrollEnd={res => this.onScroll(res)}
style={styles.scroller}
horizontal={true}
onContentSizeChange={(width, height) => this.onChangeSize(width, height)}
>
<View style={[styles.insideView, { width: colWidth }]}>{this.renderCats()}</View>
</ScrollView>
<BusyIndicator size={'large'} overlayHeight={120} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignSelf: 'stretch',
alignItems: 'center',
backgroundColor: 'white'
},
bucketContainer1: {
position: 'absolute',
height: 130,
width: 95,
left: 10,
top: 5
},
bucketContainer2: {
position: 'absolute',
height: 130,
width: 95,
top: 5
},
bucketContainer3: {
position: 'absolute',
height: 130,
width: 95,
right: 10,
top: 5
},
insideView: {
width: 2500,
justifyContent: 'flex-end',
overflow: 'visible'
},
cardContainer: {
borderWidth: 1,
borderColor: 'rgb(200,200,200)',
borderRadius: 4,
alignItems: 'center',
width: 100,
backgroundColor: 'white'
},
catImage: {
height: 100,
width: 100,
borderTopRightRadius: 4,
borderTopLeftRadius: 4
},
button: {
backgroundColor: 'rgba(255,0,0,0.2)',
width: 90,
height: 50
},
scroller: {
height: '100%',
width: '100%',
overflow: 'visible'
},
card: {
position: 'absolute',
overflow: 'visible'
},
descriptionContainer: {
top: 140,
width: '100%',
alignItems: 'center',
position: 'absolute'
},
dividingLine: {
height: 1,
width: '100%',
borderWidth: 0.5,
borderColor: 'rgb(150,150,150)',
marginBottom: 5
},
dragLikesTitle: {
fontFamily: 'Slackey',
fontSize: 20,
color: 'rgb(100,100,100)'
},
descriptionText: {
fontSize: 12,
textAlign: 'center',
marginTop: 5
}
});
function mapStateToProps(state) {
return {
User: state.User,
Feeds: state.Feeds
};
}
export default connect(mapStateToProps)(SetupLikes);
Down at the bottom of the render function is where you'll see the ScrollView. It's rendering the categories via a function called renderCats.
It may be that because the cards I am rendering are draggable, that fixing this is an impossibility but I thought I would see if anyone has a better idea of how this may be fixed!
EDIT TO INCLUDE CatCard component...
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
ScrollView,
TouchableOpacity,
FlatList,
Image,
Platform,
Animated,
Easing,
Dimensions
} from 'react-native';
import * as Consts from '../../Consts/colourConsts';
import * as ConstStyles from '../../Consts/styleConsts';
import PropTypes from 'prop-types';
import Interactable from 'react-native-interactable';
import { CachedImage } from 'react-native-img-cache';
class CatCard extends Component {
state = {
initX: this.props.posX,
initY: this.props.posY,
zIndex: 1
};
onSnap(res, point) {
if (res.nativeEvent.index === 0) {
return;
}
let index = res.nativeEvent.index;
setTimeout(() => {
this.props.onSnap(index, point);
let end = new Date();
}, 100);
Animated.timing(this.opacity, {
toValue: 0,
duration: 100,
useNativeDriver: true
}).start();
}
constructor(props) {
super(props);
this.opacity = new Animated.Value(1);
this.height = Dimensions.get('window').height;
}
gotRef(ref) {
this.props.gotRef(ref);
}
render() {
let upY = this.props.posY + this.height + (1 - this.props.odd) * -120;
upY = upY * -1;
upY += 50;
return (
<Interactable.View
ref={ref => {
this.gotRef(ref);
}}
onSnap={res => this.onSnap(res, this.props.ptr)}
style={[styles.card, { zIndex: this.state.zIndex }]}
animatedNativeDriver={true}
dragToss={0.01}
snapPoints={[
{
x: this.props.posX,
y: this.props.posY,
damping: 0.7,
tension: 300,
id: '0'
},
{
x: this.props.xOffset + 10,
y: upY,
tension: 30000,
damping: 0.1
},
{
x: this.props.xOffset + 10 + this.props.screenWidth * 0.33,
y: upY,
tension: 30000,
damping: 0.1
},
{
x: this.props.xOffset + 10 + this.props.screenWidth * 0.66,
y: upY,
tension: 30000,
damping: 0.1
}
]}
initialPosition={{ x: this.state.initX, y: this.state.initY }}
>
<Animated.View
style={[styles.cardContainer, { opacity: this.opacity }]}
>
<CachedImage
source={{ uri: this.props.item.imageUrl }}
style={styles.catImage}
/>
<Text style={styles.cardText}>{this.props.item.name}</Text>
</Animated.View>
</Interactable.View>
);
}
}
CatCard.PropTypes = {
count: PropTypes.any.isRequired,
type: PropTypes.string.isRequired,
imageToUse: PropTypes.any.isRequired,
height: PropTypes.object.isRequired,
backgroundColor: PropTypes.string.isRequired
};
const styles = StyleSheet.create({
card: {
position: 'absolute',
overflow: 'visible'
},
cardContainer: {
borderWidth: 1,
borderColor: 'rgb(200,200,200)',
borderRadius: 4,
alignItems: 'center',
width: 100,
backgroundColor: 'white'
},
cardText: {
fontFamily: 'Slackey',
fontSize: 10
},
catImage: {
height: 100,
width: 98,
borderTopRightRadius: 4,
borderTopLeftRadius: 4
}
});
export default CatCard;
Without seeing CatCard it is hard to know how the dragging is implemented. If you are doing raw PanResponder then you'll need to keep a something in state that keeps track of whether the ScrollView is scrolling and pass that down as a prop to CatCard which would then disallow the drag if the prop were true.
Alternatively, I'd suggest using react-native-interactable. It's a little to wrap your head around but it's a great abstraction from PanResponder. They have loads of examples and I have used it to make a swipeable list item that worked great, even with touchables inside the item.
I would like to center the active tab in the scrollable navbar. Currently I have a scrollable horizontal view but am stuck on updating the view. I am looking at other examples such as here on github. I would love some feedback on creating the _updateView method, there seems to be more to solving this problem than simply adding a few flexbox rules.
Here is my component code.
export default class MenuNavBar extends Component {
static propTypes: {
goToPage: React.PropTypes.func,
activeTab: React.PropTypes.number,
tabs: React.PropTypes.array,
}
constructor(props) {
super(props)
var screenWidth = Dimensions.get('window').width;
this.state = {
paddingLeft: 5,
paddingRight: 5,
titleMenuWidth: screenWidth - 100
};
this._onScroll = this._onScroll.bind(this);
this._onClickMenu = this._onClickMenu.bind(this);
componentDidMount() {
this._listener = this.props.scrollValue.addListener(this.setAnimationValue.bind(this));
}
_onClickMenu(index) {
this.props.goToPage(index);
this._updateView(index);
}
_updateView(index) {
var navContainerWidth = Dimensions.get('window').width - 100;
console.log('navContainerWidth', navContainerWidth)
}
_onTabLayout(event, i) {
this.menuTabs[i] = (this.menuTabs[i]) ? this.menuTabs[i] : event.nativeEvent.layout.width;
}
_onScroll(event) {
let {
contentSize,
contentInset,
contentOffset,
layoutMeasurement,
} = event.nativeEvent;
}
_renderMain() {
return (
<NavigationBar
title={
<ScrollView ref='menuScrollView' onScroll={this._onScroll} onLayout={this._onLayout} style={{width: this.state._titleMenuWidth}} horizontal={true} showsHorizontalScrollIndicator={false}>
{this.props.tabs.map((tab, i) => {
if (i == 0) {
ref_name = 'tab_' + i;
component_style = {
paddingTop: 5,
paddingBottom: 5,
paddingLeft: this.state.paddingLeft,
paddingRight: 5,
};
} else if (i == this.props.tabs.length - 1) {
ref_name = 'tab_' + i;
component_style = {
paddingTop: 5,
paddingBottom: 5,
paddingLeft: 5,
paddingRight: this.state.paddingRight,
};
} else {
ref_name = "tab_" + i;
component_style = styles.navbarMenuButton;
}
return <TouchableOpacity ref={ref_name} key={tab} onLayout={(event) => this._onTabLayout(event, i)} onPress={() => this._onClickMenu(i)} style={component_style}>
<Text style={this.props.activeTab === i ? styles.navbarMenuTextActive : styles.navbarMenuText}>{tab}</Text>
</TouchableOpacity>;
})}
</ScrollView>
}
leftButton={
<View>
<Image source={require('../../assets/imgs/logo.png')} style={styles.navbarLogo} />
</View>
}
style={styles.headerStyle}
statusBar={{tintColor: '#6C0104'}} />
);
}
_renderTrend() {
return (
<NavigationBar
title={
<ScrollView ref='menuScrollView' onScroll={this._onScroll} onLayout={this._onLayout} style={{width: titleMenuWidth}} horizontal={true} showsHorizontalScrollIndicator={false}>
{this.props.tabs.map((tab, i) => {
if (i == 0) {
ref_name = 'tab_' + i;
component_style = {
paddingTop: 5,
paddingBottom: 5,
paddingLeft: this.state.paddingLeft,
paddingRight: 5,
};
} else if (i == this.props.tabs.length - 1) {
ref_name = 'tab_' + i;
component_style = {
paddingTop: 5,
paddingBottom: 5,
paddingLeft: 5,
paddingRight: this.state.paddingRight,
};
} else {
ref_name = "tab_" + i;
component_style = styles.navbarMenuButton;
}
return <TouchableOpacity ref={ref_name} key={tab} onLayout={(event) => this._onTabLayout(event, i)} onPress={() => this._onClickMenu(i)} style={component_style}>
<Text style={this.props.activeTab === i ? styles.navbarMenuTextActive : styles.navbarMenuText}>{tab}</Text>
</TouchableOpacity>;
})}
</ScrollView>
}
leftButton={
<View>
<Image source={require('../../assets/imgs/logo.png')} style={styles.navbarLogo} />
</View>
}
rightButton={
<View>
<TouchableOpacity onPress={() => Actions.country()}>
<Image source={require('../../assets/imgs/ic_world.png')} style={styles.worldLogo} />
</TouchableOpacity>
</View>
}
style={styles.headerStyle}
statusBar={{tintColor: '#6C0104'}} />
);
}
render() {
var screenWidth = Dimensions.get('window').width;
var titleMenuWidth = screenWidth - 100;
return (this.props.mode == 'main') ? this._renderMain() : this._renderTrend();
}
}
P.S. I will update this question to be more clear as I fully understand it better.