-
Notifications
You must be signed in to change notification settings - Fork 734
Infra/ migrate SegmentedControl to reanimated v2 #1567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 5 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
2d001b1
migrate to reanimated2
lidord-wix ea1a38d
Merge branch 'master' into Infra/segmentedControl_reanimated2
lidord-wix 7a63e46
throttleTime prop
lidord-wix 1c61712
pass static onPress
lidord-wix f9b68f5
remove indexRef
lidord-wix 452f7ba
remove redundant const
lidord-wix 1026d56
refactor
lidord-wix File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,25 @@ | ||
import _ from 'lodash'; | ||
import React, {useRef, useState, useCallback, useMemo} from 'react'; | ||
import React, {useRef, useState, useCallback} from 'react'; | ||
import {StyleSheet, StyleProp, ViewStyle, LayoutChangeEvent} from 'react-native'; | ||
import Reanimated, {EasingNode, Easing as _Easing} from 'react-native-reanimated'; | ||
import Reanimated, { | ||
Easing, | ||
useAnimatedReaction, | ||
useAnimatedStyle, | ||
useSharedValue, | ||
withTiming, | ||
runOnJS | ||
} from 'react-native-reanimated'; | ||
import {Colors, BorderRadiuses, Spacings} from '../../style'; | ||
import {asBaseComponent} from '../../commons/new'; | ||
import View from '../view'; | ||
import Segment, {SegmentedControlItemProps as SegmentProps} from './segment'; | ||
import {Constants} from 'helpers'; | ||
|
||
const {interpolate: _interpolate, interpolateNode} = Reanimated; | ||
const interpolate = interpolateNode || _interpolate; | ||
const Easing = EasingNode || _Easing; | ||
const BORDER_WIDTH = 1; | ||
const TIMING_CONFIG = { | ||
duration: 300, | ||
easing: Easing.bezier(0.33, 1, 0.68, 1) | ||
}; | ||
|
||
export type SegmentedControlItemProps = SegmentProps; | ||
export type SegmentedControlProps = { | ||
|
@@ -59,6 +67,10 @@ export type SegmentedControlProps = { | |
* Should the icon be on right of the label | ||
*/ | ||
iconOnRight?: boolean; | ||
/** | ||
* Trailing throttle time of changing index in ms. | ||
*/ | ||
throttleTime?: number; | ||
/** | ||
* Additional spacing styles for the container | ||
*/ | ||
|
@@ -85,36 +97,37 @@ const SegmentedControl = (props: SegmentedControlProps) => { | |
inactiveColor = Colors.grey20, | ||
outlineColor = activeColor, | ||
outlineWidth = BORDER_WIDTH, | ||
throttleTime, | ||
testID | ||
} = props; | ||
const [selectedSegment, setSelectedSegment] = useState(-1); | ||
|
||
const animatedSelectedIndex = useSharedValue(selectedSegment); | ||
const segmentsStyle = useRef([] as {x: number; width: number}[]); | ||
const segmentedControlHeight = useRef(0); | ||
const indexRef = useRef(0); | ||
const segmentsCounter = useRef(0); | ||
const animatedValue = useRef(new Reanimated.Value(initialIndex)); | ||
const delay = throttleTime || 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of having the |
||
|
||
const changeIndex = useCallback(_.throttle(() => { | ||
onChangeIndex?.(indexRef.current); | ||
onChangeIndex?.(animatedSelectedIndex.value); | ||
}, | ||
400, | ||
delay, | ||
{trailing: true, leading: false}), | ||
[]); | ||
|
||
const onSegmentPress = useCallback((index: number) => { | ||
if (selectedSegment !== index) { | ||
setSelectedSegment(index); | ||
indexRef.current = index; | ||
|
||
Reanimated.timing(animatedValue.current, { | ||
toValue: index, | ||
duration: 300, | ||
easing: Easing.bezier(0.33, 1, 0.68, 1) | ||
}).start(changeIndex); | ||
useAnimatedReaction(() => { | ||
return animatedSelectedIndex.value; | ||
}, | ||
(selected, previous) => { | ||
if (selected !== -1 && selected !== previous) { | ||
onChangeIndex && runOnJS(changeIndex)(); | ||
} | ||
}, | ||
[onChangeIndex, selectedSegment]); | ||
[]); | ||
|
||
const onSegmentPress = useCallback((index: number) => { | ||
setSelectedSegment(index); | ||
animatedSelectedIndex.value = index; | ||
}, []); | ||
|
||
const onLayout = useCallback((index: number, event: LayoutChangeEvent) => { | ||
const {x, width, height} = event.nativeEvent.layout; | ||
|
@@ -126,32 +139,25 @@ const SegmentedControl = (props: SegmentedControlProps) => { | |
}, | ||
[initialIndex, segments?.length]); | ||
|
||
const animatedStyle = useMemo(() => { | ||
const animatedStyle = useAnimatedStyle(() => { | ||
if (segmentsCounter.current === segments?.length) { | ||
const inset = interpolate(animatedValue.current, { | ||
inputRange: _.times(segmentsCounter.current), | ||
outputRange: _.map(segmentsStyle.current, segment => segment.x) | ||
}); | ||
|
||
const width = interpolate(animatedValue.current, { | ||
inputRange: _.times(segmentsCounter.current), | ||
outputRange: _.map(segmentsStyle.current, segment => segment.width - 2 * BORDER_WIDTH) | ||
}); | ||
|
||
return [{width}, Constants.isRTL ? {right: inset} : {left: inset}]; | ||
const inset = withTiming(segmentsStyle.current[selectedSegment].x, TIMING_CONFIG); | ||
const width = withTiming(segmentsStyle.current[selectedSegment].width - 2 * BORDER_WIDTH, TIMING_CONFIG); | ||
return Constants.isRTL ? {width, right: inset} : {width, left: inset}; | ||
} | ||
return undefined; | ||
}, [segmentsCounter.current, segments?.length]); | ||
return {}; | ||
}, [segmentsCounter.current, segments?.length, selectedSegment]); | ||
|
||
const renderSegments = () => | ||
_.map(segments, (_value, index) => { | ||
const isSelected = selectedSegment === index; | ||
return ( | ||
<Segment | ||
key={index} | ||
onLayout={onLayout} | ||
index={index} | ||
onPress={onSegmentPress} | ||
isSelected={selectedSegment === index} | ||
isSelected={isSelected} | ||
activeColor={activeColor} | ||
inactiveColor={inactiveColor} | ||
{...segments?.[index]} | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that you have a shared value for the selectedIndex, you can implement this component a little differently.
Right now, you keep both
animatedSelectedIndex
,selectedSegment
andindexRef
. Each one of them represent the same value which goes against the single source of truth rule..I'd suggest to try and unify them all to a single source and use only the shareValue.
It can help with performance, since you won't need to update the state anymore and clean the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see why the
indexRef
is unnecessary, but theselectedSegment
is necessary for triggering a render after theonLayout
(and after anyonPress
).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's go over it together..
Let me know when you have time.
Maybe I'm wrong, but I think it's possible to make some change and to make it work only with
animatedSelectedIndex