-
-
Save mmazzarolo/cfd467436f9d110e94a685b06eb3900f to your computer and use it in GitHub Desktop.
// 1. Define a state variable for showing/hiding the action-button | |
state = { | |
isActionButtonVisible: true | |
} | |
// 2. Define a variable that will keep track of the current scroll position | |
_listViewOffset = 0 | |
// 3. Add an onScroll listener to your listview/scrollview | |
<ListView | |
... | |
onScroll={this._onScroll} | |
... | |
/> | |
// 3. Add some logic in the scroll listener for hiding the action button when scrolling down | |
_onScroll = (event) => { | |
// Simple fade-in / fade-out animation | |
const CustomLayoutLinear = { | |
duration: 100, | |
create: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity }, | |
update: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity }, | |
delete: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity } | |
} | |
// Check if the user is scrolling up or down by confronting the new scroll position with your own one | |
const currentOffset = event.nativeEvent.contentOffset.y | |
const direction = (currentOffset > 0 && currentOffset > this._listViewOffset) | |
? 'down' | |
: 'up' | |
// If the user is scrolling down (and the action-button is still visible) hide it | |
const isActionButtonVisible = direction === 'up' | |
if (isActionButtonVisible !== this.state.isActionButtonVisible) { | |
LayoutAnimation.configureNext(CustomLayoutLinear) | |
this.setState({ isActionButtonVisible }) | |
} | |
// Update your scroll position | |
this._listViewOffset = currentOffset | |
} | |
// 4. In you render show you action-button only if state.isActionButtonVisible === true | |
<View style={styles.container}> | |
{yourListView} | |
{this.state.isActionButtonVisible ? <ActionButton /> : null} | |
</View> |
mmazzarolo
commented
Sep 19, 2016
It can be tweaked further for handling the bottom "bounce" of the iOS scrollview (the top one is handled by currentOffset > 0
.
The tweak might consist in a mix of checking if the bottom of the scrollview is visible and checking the scrollview content height.
for android put in constructor:
UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
I am given this error but I don't really understand what I should do.
You specified onScroll
on a but not scrollEventThrottle
. You will only receive one event. Using 16
you get all the events but be aware that it may cause frame drops, use a bigger number if you don't need as much precision.
@ruxiang05 this warning is about update frequency on iOS. The less the more often (docs)
@mmazzarolo, thanks for code!
I have fixed the bottom bounce issue based on this
https://gist.github.com/hisokakei/25ace65e3df656313990b710354e1541
How to show alphabet bubble in scroll bar indicator
On iOS we have bounce effect. So I improved a bit:
const isBottomBounce =
event.nativeEvent.layoutMeasurement.height -
event.nativeEvent.contentSize.height +
event.nativeEvent.contentOffset.y >=
0;
let direction = currentOffset > 0 && currentOffset > this.listViewOffset ? 'down' : 'up';
if (direction === 'up' && isBottomBounce) {
direction = 'down';
}
For those using functional components you could turn it into a hook like this
import { useState, useRef, useCallback } from 'react';
import { NativeSyntheticEvent, NativeScrollEvent, LayoutAnimation } from 'react-native';
const useHandleScroll = () => {
const [showButton, setShowButton] = useState(true);
const scrollOffset = useRef(0);
const handleScroll = useCallback(
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
const CustomLayoutLinear = {
duration: 100,
create: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity },
update: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity },
delete: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity },
};
// Check if the user is scrolling up or down by confronting the new scroll position with your own one
const currentOffset = event.nativeEvent.contentOffset.y;
const direction = currentOffset > 0 && currentOffset > scrollOffset.current ? 'down' : 'up';
// If the user is scrolling down (and the action-button is still visible) hide it
const isActionButtonVisible = direction === 'up';
if (isActionButtonVisible !== showButton) {
LayoutAnimation.configureNext(CustomLayoutLinear);
setShowButton(isActionButtonVisible);
}
// Update your scroll position
scrollOffset.current = currentOffset;
},
[showButton]
);
return { handleScroll, showButton };
};
export default useHandleScroll;
and then use it like this
const { handleScroll, showButton } = useHandleScroll();
<ScrollView onScroll={handleScroll} />
{showButton && <Button />}
For those using functional components you could turn it into a hook like this
import { useState, useRef, useCallback } from 'react'; import { NativeSyntheticEvent, NativeScrollEvent, LayoutAnimation } from 'react-native'; const useHandleScroll = () => { const [showButton, setShowButton] = useState(true); const scrollOffset = useRef(0); const handleScroll = useCallback( (event: NativeSyntheticEvent<NativeScrollEvent>) => { const CustomLayoutLinear = { duration: 100, create: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity }, update: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity }, delete: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity }, }; // Check if the user is scrolling up or down by confronting the new scroll position with your own one const currentOffset = event.nativeEvent.contentOffset.y; const direction = currentOffset > 0 && currentOffset > scrollOffset.current ? 'down' : 'up'; // If the user is scrolling down (and the action-button is still visible) hide it const isActionButtonVisible = direction === 'up'; if (isActionButtonVisible !== showButton) { LayoutAnimation.configureNext(CustomLayoutLinear); setShowButton(isActionButtonVisible); } // Update your scroll position scrollOffset.current = currentOffset; }, [showButton] ); return { handleScroll, showButton }; }; export default useHandleScroll;
and then use it like this
const { handleScroll, showButton } = useHandleScroll(); <ScrollView onScroll={handleScroll} /> {showButton && <Button />}
Could you please help me in converting the above code into Class Component unable to use the Hook in class.
Thanks, @hmoule
On iOS we have bounce effect. So I improved a bit:
const isBottomBounce = event.nativeEvent.layoutMeasurement.height - event.nativeEvent.contentSize.height + event.nativeEvent.contentOffset.y >= 0; let direction = currentOffset > 0 && currentOffset > this.listViewOffset ? 'down' : 'up'; if (direction === 'up' && isBottomBounce) { direction = 'down'; }
By adding this code with @hmoule 's code i am getting a weird jump effect in android when started scrolling. Any way to avoid that? it works perfectly on iOS
Thanks, @hmoule
did you faced the same ?
https://gist.github.com/mmazzarolo/cfd467436f9d110e94a685b06eb3900f#gistcomment-3532617
Its run perfect on my android @aravi365
make sure on your scrollview Props looks like this
onScroll={handleScroll.bind(this)}
it happens only when scrolling slowly. Works like normal when scrolled in normal speed. My usecase is not an action button, instead it is a tabbar which autohides on scrolldown. That might be the root cause of this issue? any guesses
For those using functional components you could turn it into a hook like this
import { useState, useRef, useCallback } from 'react'; import { NativeSyntheticEvent, NativeScrollEvent, LayoutAnimation } from 'react-native'; const useHandleScroll = () => { const [showButton, setShowButton] = useState(true); const scrollOffset = useRef(0); const handleScroll = useCallback( (event: NativeSyntheticEvent<NativeScrollEvent>) => { const CustomLayoutLinear = { duration: 100, create: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity }, update: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity }, delete: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity }, }; // Check if the user is scrolling up or down by confronting the new scroll position with your own one const currentOffset = event.nativeEvent.contentOffset.y; const direction = currentOffset > 0 && currentOffset > scrollOffset.current ? 'down' : 'up'; // If the user is scrolling down (and the action-button is still visible) hide it const isActionButtonVisible = direction === 'up'; if (isActionButtonVisible !== showButton) { LayoutAnimation.configureNext(CustomLayoutLinear); setShowButton(isActionButtonVisible); } // Update your scroll position scrollOffset.current = currentOffset; }, [showButton] ); return { handleScroll, showButton }; }; export default useHandleScroll;
and then use it like this
const { handleScroll, showButton } = useHandleScroll(); <ScrollView onScroll={handleScroll} /> {showButton && <Button />}
i have a problem
when i use Animated.Scrollview and when i pass the parameter (handleScroll ) some other components in the screen also hid
thanks
any solution with Reanimated?? I dont want to useState + LayoutAnimation because it's may took glitch with Android