将分页指示器动画化为按钮
Animating a paginator indicator into a button
我一直在开发个人应用程序来构建财务应用程序。目前我正在创建一个入职屏幕,并成功运行。虽然我想给它添加一些样式,我已经创建了一个动画分页器,但是我想让最后一页指示器变成一个可触摸的按钮。
目前分页器看起来像这样:
当它到达最后一个时:
我希望最后一个动画变成一个按钮。
这是我的分页器代码:
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>
);
}
样式:
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};
`;
我尝试实现这个逻辑,但是没有动画。当然可以。
我希望它变成这样的东西:
这是调用分页器的页面:
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>
);
}
编队错误:
关键点是:
- 当我们从第
n-1
页滚动到第n
页时,
- 除nth以外的所有指标都需要调整。调整可以是
- 将所有其他指标的内容+边距缩小为0宽度。 (首选)
- 将所有指标向左移动计算量。
- 第
n
个元素应该增长到占据整个宽度。内容还应将不透明度从 0
更改为 1
。
考虑到这一点,Paginator 代码的以下更改应该很容易理解。
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>
);
}
我一直在开发个人应用程序来构建财务应用程序。目前我正在创建一个入职屏幕,并成功运行。虽然我想给它添加一些样式,我已经创建了一个动画分页器,但是我想让最后一页指示器变成一个可触摸的按钮。
目前分页器看起来像这样:
当它到达最后一个时:
我希望最后一个动画变成一个按钮。
这是我的分页器代码:
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>
);
}
样式:
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};
`;
我尝试实现这个逻辑,但是没有动画。当然可以。
我希望它变成这样的东西:
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>
);
}
编队错误:
关键点是:
- 当我们从第
n-1
页滚动到第n
页时,- 除nth以外的所有指标都需要调整。调整可以是
- 将所有其他指标的内容+边距缩小为0宽度。 (首选)
- 将所有指标向左移动计算量。
- 第
n
个元素应该增长到占据整个宽度。内容还应将不透明度从0
更改为1
。
- 除nth以外的所有指标都需要调整。调整可以是
考虑到这一点,Paginator 代码的以下更改应该很容易理解。
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>
);
}