I have been developing a personal application to build a finance app. At the moment I'm creating an Onboarding screen, with is successfully working. Although I want to add some styles to it, I have created an animated paginator, but I want to make the last page indicator turn into a Touchable button.
At the moment the paginator looks like this:
I want that last animation turn into a button.
This is my code for Paginator:
import React from 'react';
import {
Container,
CurrentSelectedPageIndicator,
ButtonContainer
} from './styles';
import { useWindowDimensions } from 'react-native';
interface PaginatorProps {
data: any;
scrollX: any;
currentIndex: any;
}
export function Paginator({ data, scrollX, currentIndex }: PaginatorProps){
const { width } = useWindowDimensions();
return (
<Container>
{data.map((_: any, index: any) => {
const inputRange = [(index - 1) * width, index * width, (index 1) * width];
let dotWidth = scrollX.interpolate({
inputRange,
outputRange: [10, 20, 10],
extrapolate: 'clamp'
});
const opacity = scrollX.interpolate({
inputRange,
outputRange: [0.3, 1, 0.3],
extrapolate: 'clamp'
});
if (currentIndex.toString() === '2') {
dotWidth = scrollX.interpolate({
[1,2,3],
outputRange: [10, 20, 10],
extrapolate: 'clamp'
});
}
return <CurrentSelectedPageIndicator key={index.toString()} style={{ width: dotWidth, opacity }} />;
})}
</Container>
);
}
Styles:
import { RFValue } from "react-native-responsive-fontsize";
import styled from "styled-components/native";
import { Animated } from 'react-native';
export const Container = styled.View`
flex-direction: row;
height: ${RFValue(64)}px;
`;
export const CurrentSelectedPageIndicator = styled(Animated.View).attrs({
shadowOffset: { width: 1, height: 3 }
})`
shadow-color: ${({ theme }) => theme.colors.text_dark };
elevation: 1;
shadow-opacity: 0.3;
shadow-radius: 1px;
height: ${RFValue(10)}px;
width: ${RFValue(10)}px;
border-radius: 10px;
background-color: ${({ theme }) => theme.colors.blue };
margin-horizontal: ${RFValue(8)}px;
`;
export const ButtonContainer = styled(Animated.View)`
width: 100%;
height: ${RFValue(50)}px;
background-color: ${({ theme }) => theme.colors.blue};
border-radius: 10px;
align-items: center;
justify-content: center;
`;
export const ButtonTitle = styled.Text`
font-family: ${({ theme }) => theme.fonts.medium};
font-size: ${RFValue(14)}px;
color: ${({ theme }) => theme.colors.shapeColor};
`;
I tried implementing this logic, but there was no animation. Of course.
I want it to turn into something like this:
This is the page with calls the paginator:
import React, { useState, useRef } from 'react';
import {
Container,
FlatListContainer
} from './styles';
import {
FlatList,
Animated
} from 'react-native'
import OnboardingData from '../../utils/onboarding';
import { OnboardingItem } from '../../components/OnboardingItem';
import { Paginator } from '../../components/Paginator';
export function Onboarding(){
const [currentIndex, setCurrentIndex] = useState(0);
const scrollX = useRef(new Animated.Value(0)).current;
const onboardingDataRef = useRef(null);
const viewableItemsChanged = useRef(({ viewableItems }: any) => {
setCurrentIndex(viewableItems[0].index);
}).current;
const viewConfig = useRef({ viewAreaCoveragePercentThreshold: 50 }).current;
return (
<Container>
<FlatListContainer>
<FlatList
data={OnboardingData}
renderItem={({ item }) => <OnboardingItem image={item.image} title={item.title} description={item.description}/>}
horizontal
showsHorizontalScrollIndicator={false}
pagingEnabled={true}
bounces={false}
keyExtractor={(item) => String(item.id)}
onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: scrollX } }}], {
useNativeDriver: false
})}
scrollEventThrottle={32}
onViewableItemsChanged={viewableItemsChanged}
viewabilityConfig={viewConfig}
ref={onboardingDataRef}
/>
</FlatListContainer>
<Paginator data={OnboardingData} scrollX={scrollX} currentIndex={currentIndex}/>
</Container>
);
}
CodePudding user response:
The key points were:
- When we scroll from
n-1
th ton
th page,- All indicators except nth need to be adjusted. The adjustment could be either of
- Shrink content margin of all other indicators to 0 width. ( preferred )
- Move all indicators to left by calculated amount.
- The
n
th element should grow to occupy full width. The contents should also change opacity from0
to1
.
- All indicators except nth need to be adjusted. The adjustment could be either of
With this points in mind, it should be easy to understand following changes in Paginator code.
import React from 'react';
import {
Container,
CurrentSelectedPageIndicator,
ButtonContainer,
ButtonTitle,
} from './styles';
import { useWindowDimensions } from 'react-native';
import { RFValue } from 'react-native-responsive-fontsize';
interface PaginatorProps {
data: any;
scrollX: any;
currentIndex: any;
}
const inactiveSize = RFValue(10)
const activeSize = RFValue(20)
export function Paginator({ data, scrollX, currentIndex }: PaginatorProps) {
const { width } = useWindowDimensions();
return (
<Container>
{data.map((_: any, index: any) => {
const inputRange = Array(data.length)
.fill(0)
.map((_, i) => i * width);
const isLastElement = index === data.length - 1;
const widthRange = Array(data.length)
.fill(inactiveSize)
.map((v, i) => {
if (i === data.length - 1) {
if (isLastElement) return width;
return 0;
}
if (i === index) return activeSize;
return v;
});
// optionally, reduce the length of inputRange & widthRange
// while loop may be removed
let i = 0;
while (i < inputRange.length - 1) {
if (widthRange[i] === widthRange[i 1]) {
let toRemove = -1;
if (i === 0) toRemove = i;
else if (i === inputRange.length - 2) toRemove = i 1;
else if (
i < inputRange.length - 2 &&
widthRange[i] === widthRange[i 2]
)
toRemove = i 1;
if (toRemove > -1) {
inputRange.splice(toRemove, 1);
widthRange.splice(toRemove, 1);
continue;
}
}
i ;
}
console.log(index, inputRange, widthRange);
let height = inactiveSize;
let buttonOpacity = 0;
let dotWidth = scrollX.interpolate({
inputRange,
outputRange: widthRange,
extrapolate: 'clamp',
});
const opacity = scrollX.interpolate({
inputRange,
outputRange: widthRange.map((v) => ( v? v >= activeSize ? 1 : 0.3: 0)),
extrapolate: 'clamp',
});
if (isLastElement) {
dotWidth = scrollX.interpolate({
inputRange,
outputRange: widthRange,
extrapolate: 'clamp',
});
height = dotWidth.interpolate({
inputRange: [inactiveSize, width],
outputRange: [inactiveSize, RFValue(50)],
extrapolate: 'clamp',
});
buttonOpacity = dotWidth.interpolate({
inputRange: [inactiveSize, width],
outputRange: [0, 1],
extrapolate: 'clamp',
});
}
const marginHorizontal = dotWidth.interpolate({
inputRange: [0, inactiveSize],
outputRange: [0, RFValue(8)],
extrapolate: 'clamp',
});
return (
<CurrentSelectedPageIndicator
key={index.toString()}
style={{ width: dotWidth, opacity, marginHorizontal, height }}>
{isLastElement && (
<ButtonContainer
style={{ opacity: buttonOpacity, backgroundColor: '#5636D3' }}>
<ButtonTitle style={{ color: 'white' }}>NEXT</ButtonTitle>
</ButtonContainer>
)}
</CurrentSelectedPageIndicator>
);
})}
</Container>
);
}