react native multiple video with Swiper component play pause issue - reactjs

I have multiple videos in the swiper to show videos one by one, but all the videos are loaded and playing at the same time and audios are messed up, I want current video only play at a time.
import * as React from 'react';
import { Text, View, StyleSheet,Image, Dimensions } from 'react-native';
import { Constants } from 'expo';
import { Video } from 'expo';
import Swiper from './Swiper';
import InViewPort from './InViewport';
const screenWidth = Dimensions.get('window').width ;
const screenHeight = Dimensions.get('window').height;
export default class App extends React.Component {
constructor(props) {
super(props);
// Your source data
this.state = {
images: {},
muted : false,
paused: true,
};
this.player = Array();
this.onChangeImage = this.onChangeImage.bind(this);
}
videoError(err){
console.warn(err);
}
pauseVideo = () => {
var curr = this.state.currentIndex;
console.warn(curr);
if(this.player[curr]) {
this.setState({paused: true });
}
}
playVideo = () => {
var curr = this.state.currentIndex;
console.warn(curr);
if(this.player[curr]) {
this.setState({paused: false});
}
}
handlePlaying = (isVisible) => {
isVisible ? this.playVideo() : this.pauseVideo();
}
onChangeImage (index) {
this.setState({ currentIndex: index});
}
render() {
let items = Array.apply(null, Array(15)).map((v, i) => {
return {
id: i,
caption: i + 1,
source: { uri: 'http://placehold.it/200x200?text=' + (i + 1) },
dimension: '{ width: 150, height: 150 }',
};
});
return(
<View style={styles.DefaultView}>
<Swiper
showsPagination={false}
onIndexChanged={this.onChangeImage}
index={0}
>
{items.map((item, key) => {
if(key==1 || key ==5){
return (
<InViewPort onChange={this.handlePlaying} key={key}>
<Video onError={this.videoError}
muted={this.state.muted}
paused={this.state.paused}
source={{uri: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4' }}
style={styles.backgroundVideo}
ref={(ref) => {
this.player[key] = ref;
}}
controls={true}
/>
</InViewPort>
)
}else{
return(
<Image
resizeMode='contain'
style={{width:screenWidth, height: screenHeight}}
source={item.source}
key={key}
/>
)
}
})}
</Swiper>
</View>
)
}
}
const styles = StyleSheet.create({
scrollView: {
flex: 1,
flexDirection: 'row',
},
DefaultView: {
flex: 1,
backgroundColor: '#000',
width: screenWidth,
justifyContent:'center',
alignItems:'center'
},
iconContainer: {
flexDirection: "row",
justifyContent: "space-evenly",
width: 150,
},
backgroundVideo: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
width: screenWidth,
height: 300,
marginTop:'50%',
position:'absolute',
},
});
I need some idea on this, we have a player reference to be used, also swiper component have onIndexChanged which will trigger when we moved to next video, how we can link the reference of the player to onIndexChanged and when we do swipe how we make it current video only to play?
As per Andrew suggestion I have used InPortView component too determine the current view of swipe, but still I am not sure how to make reference for video elements to be used in the functions for play and pause the concern video.
Components used:
For video react-native-video
For Swiper : react-native-swiper
Updated Full code with Expo example : Expo Snack

So taking your snack. I managed to get it to work.
I moved the Video out into its own component and passed a few additional props to it, the index in the array and the currentIndex showing.
export default class App extends React.Component {
constructor(props) {
super(props);
// Your source data
this.state = {
images: {},
muted : false,
paused: true,
currentIndex: 0
};
}
onChangeImage = (index) => {
console.log('currentIndex ', index)
this.setState({ currentIndex: index});
}
render() {
let items = Array.apply(null, Array(15)).map((v, i) => {
return {
id: i,
caption: i + 1,
source: { uri: 'http://placehold.it/200x200?text=' + (i + 1) },
dimension: '{ width: 150, height: 150 }',
};
});
return(
<View style={styles.DefaultView}>
<Swiper
showsPagination={false}
onIndexChanged={this.onChangeImage}
index={0}
>
{items.map((item, key) => {
if(key==1 || key ==5){
return (
<VideoPlayer key={key} index={key} currentIndex={this.state.currentIndex}/>
)
}else{
return(
<Image
resizeMode='contain'
style={{width:screenWidth, height: screenHeight}}
source={item.source}
key={key}
/>
)
}
})}
</Swiper>
</View>
)
}
}
The video component uses react-native-inviewport to help handle whether or not it is in the viewport. However it doesn't play nicely with react-native-swiper but it is possible to get it to work.
export default class VideoPlayer extends React.Component {
pauseVideo = () => {
if(this.video) {
this.video.pauseAsync();
}
}
playVideo = () => {
if(this.video) {
this.video.playAsync();
}
}
handlePlaying = (isVisible) => {
this.props.index === this.props.currentIndex ? this.playVideo() : this.pauseVideo();
}
render() {
return (
<View style={styles.container}>
<InViewPort onChange={this.handlePlaying}>
<Video
ref={ref => {this.video = ref}}
source={{ uri: 'http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4' }}
rate={1.0}
volume={1.0}
isMuted={false}
resizeMode="cover"
shouldPlay
style={{ width: WIDTH, height: 300 }}
/>
</InViewPort>
</View>
)
}
}
When I used the InViewPort component alone it seemed to think that the video in position 6 was in the viewport and would play it. So what I use the InviewPort is to perform a check to compare the index of the video with the currentIndex if they match play the video otherwise pause. I suppose this could be updated to use componentDidUpdate to handle the changes in the props. However, additional checks will need to be performed when the component mounts so that it doesn't play the video.
Here is my snack with it working. https://snack.expo.io/#andypandy/swiper-video

Related

React Native Expo AV - Implementing SeekBar

I am attempting to use react-native-slider with Expo AV to create a seekbar, but am having trouble updating the 'value' state of slider. When I try to set it to currentPosition/durationPosition, it errors out, likely because initially these values are NaN. I CAN display current/duration however.
My best guess is that I need a way to wait until my mp3 is loaded before rendering the SeekBar. I probably also need to do a better job of separating components and keep PlayerScreen very minimal. I've messed around with this code so much I can barely remember what I've tried... Getting close to ditching Expo because react-native-track-player looks easier to work with and I've heard some bad things about Expo. Anyways, here's where I'm at now
export default class PlayerScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
isPlaying: false,
playbackObject: null,
volume: 1.0,
isBuffering: false,
paused: true,
currentIndex: 0,
durationMillis: 1,
positionMillis:0,
sliderValue:0,
isSeeking:false,
}
}
async componentDidMount() {
try {
await Audio.setAudioModeAsync({
allowsRecordingIOS: false,
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
playsInSilentModeIOS: true,
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
shouldDuckAndroid: true,
staysActiveInBackground: true,
playThroughEarpieceAndroid: true
})
this.loadAudio()
} catch (e) {
console.log(e)
}
}
async loadAudio() {
const { currentIndex, isPlaying, volume} = this.state
try {
const playbackObject = new Audio.Sound()
const source = {
uri: this.props.route.params.item.uri
}
const status = {
shouldPlay: isPlaying,
volume,
}
playbackObject.setOnPlaybackStatusUpdate(this.onPlaybackStatusUpdate)
await playbackObject.loadAsync(source, status, true)
this.setState({playbackObject})
var sliderValue = this.state.positionMillis/this.state.durationMillis
} catch (e) {
console.log(e)
}
}
handlePlayPause = async () => {
const { isPlaying, playbackObject } = this.state
isPlaying ? await playbackObject.pauseAsync() : await playbackObject.playAsync()
this.setState({
isPlaying: !isPlaying
})
}
onPlaybackStatusUpdate = status => {
this.setState({
isBuffering: status.isBuffering,
durationMillis: status.durationMillis,
positionMillis: status.positionMillis,
})
}
render() {
const { item } = this.props.route.params;
return (
<View style={globalStyles.container}>
<Header />
<View style={globalStyles.subHeader}>
<Text style={globalStyles.title}>{ item.title }</Text>
</View>
<View style={styles.text}>
<Text>{ item.text }</Text>
</View>
<SeekBar
durationMillis={this.state.durationMillis}
positionMillis={this.state.positionMillis}
sliderValue={this.state.sliderValue}
/>
And here's the SeekBar component:
const SeekBar = ({
positionMillis,
durationMillis,
sliderValue
}) => {
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row' }}>
<View style={{ flex: 1 }} />
<Text style={[styles.text, { width: 40 }]}>
{positionMillis + ' / ' + durationMillis}
</Text>
</View>
<Slider
minimumValue={0}
maximumValue={1}
value={sliderValue}
style={styles.slider}
minimumTrackTintColor='#fff'
maximumTrackTintColor='rgba(255, 255, 255, 0.14)'
/>
</View>
);
};
export default SeekBar;
put
<SeekBar
durationMillis={this.state.durationMillis}
positionMillis={this.state.positionMillis}
sliderValue={this.state.sliderValue}
/>
in the screen component and
const SeekBar = ({
positionMillis,
durationMillis,
sliderValue
}) => {
sliderValue = positionMillis/durationMillis
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row' }}>
<View style={{ flex: 1 }} />
in the SeekBar component

FlatList renderItem not being highlighted when clicked the first time

Basically, I'm trying to setup a Flatlist in which multiple values can be selected.
My problem is with the styling of elements, when clicked the first time they don't get highlighted but when clicked the 2nd time they get highlighted.
FlatList Code
renderRow = ({item}) => (
<RowItem data={item} />
)
data = [
{
value: 'element1'
},
{
value: 'element2'
}
]
render(){
return (
<FlatList
data={this.data}
renderItem={this.renderRow}
keyExtractor={(item, index) => item + index}/>
)
}
RowItem Code
export default class RowItem extends React.Component {
state = {
isElementActive: false,
}
highlightElement = () => {
this.setState(prevState => ({
isElementActive: !prevState.isElementActive
}))
}
render() {
return (
<TouchableOpacity
activeOpacity={0.7}
onPress={this.highlightElement}
style={[styles.container, this.state.isElementActive ? styles.activeContainer : styles.inactiveContainer]}>
<Text>{this.props.data.value}</Text>
</TouchableOpacity>
)
}
}
const styles = Stylesheet.create({
container: {
height: 100,
width: 300,
backgroundColor: 'red',
},
activeContainer: {
opacity: 0.7,
},
inactiveContainer: {
opacity: 1,
}
});
When clicking on the element once, the value of the isElementActive returns true (when I console.log it) but the styling "activeContainer" does not apply. However, when I click it again, the styling applies even though the value of isElementActive then becomes false.
By default the value starts off as false, and they are not highlighted (i.e. have opacity of 1) and for this reason I'm kind of confused when clicked the first time, the value of isElementActive changes but the styling does not apply.
I was able to make it work with setOpacityTo after the setState.
Working example: https://snack.expo.io/SJNSKQPIB
import React from 'react';
import {TouchableOpacity, FlatList, StyleSheet, Text} from 'react-native';
type State = {
active: boolean;
};
type Props = {
value: string;
};
class RowItem extends React.Component<Props, State> {
state = {
active: null,
};
ref = null;
highlightElement = () => {
this.setState(
prevState => ({
active: !prevState.active,
}),
() => {
this.ref.setOpacityTo(this.state.active ? 0.7 : 1);
},
);
};
render() {
return (
<TouchableOpacity
ref={ref => (this.ref = ref)}
onPress={this.highlightElement}
style={[styles.container]}>
<Text>{this.props.value}</Text>
</TouchableOpacity>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
height: 100,
backgroundColor: 'red',
},
});
export default class App extends React.Component {
data = [
{
value: 'element1',
},
{
value: 'element2',
},
];
render() {
return (
<FlatList
keyExtractor={(_, index) => index.toString()}
data={this.data}
renderItem={({item}) => <RowItem value={item.value} />}
/>
);
}
}

Barcode scanner scans once and shows the same result when scanned for second time

I have a react native app in which i'm using react-native-camera and i can scan once and the scan result shows without any issue. But when i go for the second scan it shows the same result.I have my barcode scanner in one tab and after scanning it navigates to a different tab with the result.How can i scan and show results more than once?
Here's the barcode scanner tab component i have:
class BarScannerView extends Component {
constructor(props) {
super(props);
this.camera = null;
this.barcodeCodes = [];
this.state = {
changeScreen: false,
camera: {
type: RNCamera.Constants.Type.back,
flashMode: RNCamera.Constants.FlashMode.auto,
barcodeFinderVisible: true
},
focusedScreen: false
};
}
onBarCodeRead = (scanResult) => {
if (scanResult.data !== null) {
let bacodeScanResult = scanResult.data
AsyncStorage.setItem('barcodeValue', bacodeScanResult)
this.props.navigation.navigate('Stock')
}
return;
}
componentDidMount() {
console.log(' BarScanner componentDidMount was called...', this.props)
const { navigation } = this.props;
navigation.addListener('willFocus', () =>
this.setState({ focusedScreen: true })
);
navigation.addListener('willBlur', () =>
this.setState({ focusedScreen: false })
);
}
componentWillUnmount() {
console.log('BarScanner componentWillUnmount was called..')
this.onBarCodeRead()
}
render() {
const { focusedScreen } = this.state;
if (focusedScreen) {
return (
<View style={styles.container}>
<RNCamera
ref={ref => {
this.camera = ref;
}}
barcodeFinderVisible={this.state.camera.barcodeFinderVisible}
barcodeFinderWidth={280}
barcodeFinderHeight={220}
barcodeFinderBorderColor="white"
barcodeFinderBorderWidth={2}
defaultTouchToFocus
flashMode={this.state.camera.flashMode}
onBarCodeRead={this.onBarCodeRead}
onFocusChanged={() => { }}
onZoomChanged={() => { }}
permissionDialogTitle={'Permission to use camera'}
permissionDialogMessage={'We need your permission to use your camera phone'}
style={styles.preview}
type={this.state.camera.type}
/>
<View style={[styles.overlay, styles.topOverlay]}>
<Text style={styles.scanScreenMessage}>Please scan the barcode.</Text>
</View>
<View style={{ position: 'absolute', top: 150, left: '12%' }}>
<View
style={{
width: 300,
height: 300,
backgroundColor: 'transparent',
borderColor: 'white',
borderWidth: 1
}}
>
</View>
</View>
<View style={[styles.overlay, styles.bottomOverlay]}>
<Button
disabled
onPress={() => { console.log('scan clicked'); }}
style={styles.enterBarcodeManualButton}
title="Choose Barcode"
/>
</View>
</View>
);
} else {
return <Stock/>
}
}
}
export default BarScannerView
How can i remove previous result when user goes to this barcode scanner tab for the second time?
Additional info:
React Native version:"0.57.8"
React Navigation v3
React-Native-Camera "^1.6.4"
You need to use this.props.navigation.push instead of this.props.navigation.navigate in order to navigate to the same screen and show new content.
Navigation Push
React navigation just push a new screen to the stack without hesitation.
Navigation navigate
React navigation will search for the screen in the stack, if the screen is present it will navigate to it, otherwise it will add it to the stack and navigate.

Stacking Cards in react native

Currently I am learning react native and I have some problem while I am making stack of the cards.
Here is my code:
constructor(props) {
super(props);
const position = new Animated.ValueXY();
const panResponder = new PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (event, gesture) => {
position.setValue({ x: gesture.dx, y: gesture.dy })
},
});
this.state = { panResponder, position, index: 0 };
}
renderCards() {
if(this.state.index >= this.props.data.length) {
return this.props.renderNoMoreCard();
}
return this.props.data.map((item, i) => {
if( i < this.state.index) { return null}
if(i === this.state.index) {
return (
<Animated.View
key={item.id}
style={[ styles.cardStyle, this.getCardStyle()]}
{...this.state.panResponder.panHandlers}
>
{this.props.renderCard(item)}
</Animated.View>
);
}
return (
<Animated.View key={item.id} style={styles.cardStyle}>
{this.props.renderCard(item)}
</Animated.View>
);
}).reverse();
}
render() {
return(
<View>
{this.renderCards()}
</View>
);
}
}
const styles = {
cardStyle: {
position: 'absolute',
width: SCREEN_WIDTH,
top: 0,
}
};
export default Deck;
The title card 2 is showing instead of card 1 but when I swipe the card. Card 1 is removed from view and title is changed to 3.
The mistake was that while using PanResponder which is use to reconciles several touches into a single gesture. So,
I was declaring a object const panResponder = new PanResponder.create
but the code should be
const panResponder = PanResponder.create and by Removing this single word "new" it worked!

How do I make a list (FlatList) automatically scroll through the elements using Animated?

I have a horizontal FlatList, where each time it reaches the end, it automatically adds new elements to the list, so it kind of is an infinite list. I want the app to scroll through the list by itself automatically, while the user must still be able to scroll back and forth. This is what I have to far
export default class ImageCarousel extends Component {
constructor(props) {
super(props);
this.scrollX = 0;
this.offset = new Animated.Value(0);
this.scrollTo = this.scrollTo.bind(this);
this.handleScroll = this.handleScroll.bind(this);
this.stopAnimation = this.stopAnimation.bind(this);
// Listener to call the scrollToOffset function
this.offset.addListener(this.scrollTo);
}
_scroller() {
toValue = this.scrollX + 10; // Scroll 10 pixels in each loop
this.animation = Animated.timing(
this.offset,
{
toValue: toValue,
duration: 1000, // A loop takes a second
easing: Easing.linear,
}
);
this.animation.start(() => this._scroller()); //Repeats itself when done
}
scrollTo(e) {
this.carousel.scrollToOffset({offset: e.value});
}
handleScroll(event) {
// Save the x (horizontal) value each time a scroll occurs
this.scrollX = event.nativeEvent.contentOffset.x;
}
componentDidMount() {
this._scroller();
}
render() {
return (
<View>
<FlatList
ref={el => this.carousel = el}
data={someData}
renderItem={renderFunction}
horizontal={true}
keyExtractor={someKeyFunction}
onEndReached={loadMoreElementsFunction}
onScroll={this.handleScroll}
/>
</View>
);
}
}
It works in the sense that it is automatically scrolling through the list, the problem however, is I cannot manually scroll through the list, since the scroll position is constantly updated by the scrollTo listener. I have tried to add an onPress callback to disable the animation when the FlatList is pressed, I have however not been able to get it to work.
This is my Data.
Blockquote
state = {
link: [
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
],};
Define FlatList Ref
flatList = createRef();
FlatList component
<FlatList
style={{flex: 1}}
data={this.state.link}
keyExtractor={this._keyExtractor.bind(this)}
renderItem={this._renderItem.bind(this)}
horizontal={true}
flatListRef={React.createRef()}
ref={this.flatList}
/>
Next slide
_goToNextPage = () => {
if (CurrentSlide >= this.state.link.length-1) CurrentSlide = 0;
this.flatList.current.scrollToIndex({
index: ++CurrentSlide,
animated: true,
});
};
Start and stop Interval
_startAutoPlay = () => {
this._timerId = setInterval(this._goToNextPage, IntervalTime);
};
_stopAutoPlay = () => {
if (this._timerId) {
clearInterval(this._timerId);
this._timerId = null;
}
};
Associated function
componentDidMount() {
this._stopAutoPlay();
this._startAutoPlay();
}
componentWillUnmount() {
this._stopAutoPlay();
}
_renderItem({item, index}) {
return <Image source={{uri: item}} style={styles.sliderItems} />;
}
_keyExtractor(item, index) {
return index.toString();
}
Full Code:
import React, {Component, createRef} from 'react';
import {
Text,
View,
ScrollView,
Image,
StyleSheet,
Dimensions,
FlatList,
} from 'react-native';
let CurrentSlide = 0;
let IntervalTime = 4000;
export default class Slider extends Component {
flatList = createRef();
// TODO _goToNextPage()
_goToNextPage = () => {
if (CurrentSlide >= this.state.link.length-1) CurrentSlide = 0;
this.flatList.current.scrollToIndex({
index: ++CurrentSlide,
animated: true,
});
};
_startAutoPlay = () => {
this._timerId = setInterval(this._goToNextPage, IntervalTime);
};
_stopAutoPlay = () => {
if (this._timerId) {
clearInterval(this._timerId);
this._timerId = null;
}
};
componentDidMount() {
this._stopAutoPlay();
this._startAutoPlay();
}
componentWillUnmount() {
this._stopAutoPlay();
}
// TODO _renderItem()
_renderItem({item, index}) {
return <Image source={{uri: item}} style={styles.sliderItems} />;
}
// TODO _keyExtractor()
_keyExtractor(item, index) {
// console.log(item);
return index.toString();
}
state = {
link: [
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
// 'https://picsum.photos/200/300',
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
],
};
render() {
return (
<View style={{marginTop: 10, marginBottom: 10}}>
<FlatList
style={{
flex: 1,
// TODO Remove extera global padding
// marginLeft: -size.padding,
// marginRight: -size.padding,
}}
data={this.state.link}
keyExtractor={this._keyExtractor.bind(this)}
renderItem={this._renderItem.bind(this)}
horizontal={true}
flatListRef={React.createRef()}
ref={this.flatList}
/>
</View>
);
}
}
const styles = StyleSheet.create({
sliderItems: {
marginLeft: 5,
marginRight: 5,
height: 200,
width: Dimensions.get('window').width,
},
});
Just in case you're still not found the answer yet,
this is my approach to create autoscroll carousel using FlatList
import React, { Component } from 'react'
import {
StyleSheet,
View,
FlatList,
ScrollView,
Dimensions,
Image
} from 'react-native'
const { width } = Dimensions.get('window');
const height = width * 0.2844;
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
search: '',
sliderIndex: 0,
maxSlider: 2,
banners: [
{_id: 1, imageUrl: 'https://www.do-cart.com/img/slider/1.jpg'},
{_id: 2, imageUrl: 'https://www.do-cart.com/img/slider/2.jpg'},
{_id: 3, imageUrl: 'https://www.do-cart.com/img/slider/3.jpg'},
],
}
}
setRef = (c) => {
this.listRef = c;
}
scrollToIndex = (index, animated) => {
this.listRef && this.listRef.scrollToIndex({ index, animated })
}
componentWillMount() {
setInterval(function() {
const { sliderIndex, maxSlider } = this.state
let nextIndex = 0
if (sliderIndex < maxSlider) {
nextIndex = sliderIndex + 1
}
this.scrollToIndex(nextIndex, true)
this.setState({sliderIndex: nextIndex})
}.bind(this), 3000)
}
render() {
return (
<View style={styles.container}>
<View style={{height: 80, backgroundColor: '#123866', width:'100%'}}></View>
<ScrollView style={styles.scrollContainer} showsVerticalScrollIndicator={false}>
<FlatList
ref={this.setRef}
data={this.state.banners}
horizontal
showsHorizontalScrollIndicator={false}
pagingEnabled
keyExtractor={item => item._id}
renderItem={({item, i}) => (
<View key={i} style={{ height, width}}>
<Image style={{ height, width }} source={{ uri: item.imageUrl }} />
</View>
)}
onMomentumScrollEnd={(event) => {
let sliderIndex = event.nativeEvent.contentOffset.x ? event.nativeEvent.contentOffset.x/width : 0
this.setState({sliderIndex})
}}
/>
<View style={styles.sliderContainer}>
{
this.state.banners.map(function(item, index) {
return (
<View key={index} style={styles.sliderBtnContainer}>
<View style={styles.sliderBtn}>
{
this.state.sliderIndex == index ? <View style={styles.sliderBtnSelected}/> : null
}
</View>
</View>
)
}.bind(this))
}
</View>
</ScrollView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
scrollContainer: {
flex: 1
},
sliderContainer: {
flexDirection: 'row',
position: 'absolute',
top: 80,
alignSelf: 'center'
},
sliderBtn: {
height: 13,
width: 13,
borderRadius: 12,
borderWidth: 1,
borderColor: 'white',
alignItems: 'center',
justifyContent: 'center',
marginRight: 10
},
sliderBtnSelected: {
height: 12,
width: 12,
borderRadius: 6,
backgroundColor: 'white',
},
sliderBtnContainer: {
flexDirection: 'row', marginBottom: 24
},
});
https://snack.expo.io/rJ9DOn0Ef
For those looking for a function-based component, this is my approach. The user can interact with the carousel and the automatic scroller will simply continue from the current slide.
The trick to achieving this is using an "onViewableItemsChanged" callback, where the "itemVisiblePercentThreshold" is >= 50%. This ensures the callback fires after the scroll to the new page is more than 50% complete (otherwise the automatic scroller triggers the callback to early and makes it scroll back).
import { useCallback, useEffect, useRef, useState } from "react";
import { Dimensions } from "react-native";
import { FlatList, Image, StyleSheet } from "react-native";
const width = Dimensions.get("screen").width;
export const CarouselAutoScroll = ({ data, interval }) => {
const imageRef = useRef();
const [active, setActive] = useState(0);
const indexRef = useRef(active);
indexRef.current = active;
useInterval(() => {
if (active < Number(data?.length) - 1) {
setActive(active + 1);
} else {
setActive(0);
}
}, interval);
useEffect(() => {
imageRef.current.scrollToIndex({ index: active, animated: true });
}, [active]);
const onViewableItemsChangedHandler = useCallback(
({ viewableItems, changed }) => {
if (active != 0) {
setActive(viewableItems[0].index);
}
},
[]
);
return (
<FlatList
showsHorizontalScrollIndicator={false}
onViewableItemsChanged={onViewableItemsChangedHandler}
viewabilityConfig={{
itemVisiblePercentThreshold: 50,
}}
ref={imageRef}
pagingEnabled
data={data}
horizontal
renderItem={({ item, index }) => (
<Image
key={index}
source={item.image}
resizeMode={"contain"}
style={{
flex: 1,
height: "100%",
width: width,
}}
/>
)}
style={{ ...StyleSheet.AbsoluteFill }}
/>
);
};
const useInterval = (callback, delay) => {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const tick = () => {
savedCallback.current();
};
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
};

Resources