netceteragroup / skele

Architectural framework that assists building data-driven apps with React or React Native.
MIT License
163 stars 32 forks source link

[FEATURE] support for Animated.ScrollView onScroll #147

Closed usrbowe closed 4 years ago

usrbowe commented 5 years ago

If we want to use Animated.ScrollView and handle the onScroll event in native thread, there is currently no way to pass the offsetY/X, out of the onScroll prop.

In my example I used react-native-reanimated, so the entire event is driven in native thread. I can get offset values via callback and pass it back to ViewportTracker component via ref.

I created this PR, as a temporary workaround: #146

ognen commented 4 years ago

Hello,

I've examined #146 and closed it due to it not meeting the checks, and also having too many files changed.

I'm wondering if this feature is really necessary. Looking at the code example in block #146, don't you think that you should call the scroll views' scrollTo() method? that will trigger an onScroll event which in turn will update the tracker's state.

usrbowe commented 4 years ago

@ognen I admit the PR is not correct, I just used branch I forked. I might create a separate PR with cleaner code. But anyway I'm not even sure, if this is good approach for that. So it's more like open for discussion.

So the main problem is, when using react-native-reanimated, skele component should not assign onScroll event listener, but rather get value from outside. Right now if I combine skele and react-native-reanimated it will throw error as seen in this demo: https://snack.expo.io/@usrbowe2/skele-reanimated

If you remove the <Viewport.Trcker /> all works fine.

bevkoski commented 4 years ago

Hi @usrbowe! I took a look at your Snack demo. It is true that there is an issue. Thanks for pointing it out.

However, setting the value from outside does not make much sense in the context of a "viewport tracker". Thus, I would for now consider the obstacle that you have experienced as a limitation of the tracker.

A potentially clean solution to your problem is to do a custom extension of WithEvents and call notifyViewportListeners each time you want to communicate a viewport change to the Viewport.Aware components.

You can import WithEvents in your app like so:

import { Mixins } from '@skele/components';
// use Mixins.WithEvents
hirbod commented 4 years ago

@bevkoski could you maybe provide a little example? I have the same issue currently. I am using an react-native-reanimated ScrollView to make a Cube transition and I would like to know when load the next images.

Currently I just get

n is not a functin. (In nt(t)', 'n' is an instance of _)
                <Viewport.Tracker>
                    <Animated.ScrollView
                        removeClippedSubviews={true}
                        ref={(scroller) => {this.scroller = scroller}}
                        style={StyleSheet.absoluteFillObject}
                        showsHorizontalScrollIndicator={false}
                        scrollEventThrottle={16}
                        pagingEnabled={true}
                        decelerationRate={'fast'}
                        contentContainerStyle={{width: width * stories.length}}
                        onScroll={event(
                            [
                                {
                                    nativeEvent: {
                                        contentOffset: { x },
                                    },
                                },
                            ],
                            { useNativeDriver: true },
                        )}
                        horizontal
                    />
                </Viewport.Tracker>
bevkoski commented 4 years ago

@usrbowe and @Hirbod, here is a Snack that shows how it can be done. Please check the ViewportNotifier.js file for the details.

hirbod commented 4 years ago

Thank you very much! Looks great and easy to understand. Will give it a try tomorrow!

hirbod commented 4 years ago

@bevkoski I've spent nearly 2 hours, but my Images won't change - they will be "stuck" on the placeholder.

It took my a while to figure out, that react-native-reanimated does not have "listener", but I found a way around and I log my methods, they get called (and btw, every single pixel of movement calls a setState, JS FPS drops to 4 after 20px of swiping)

The way to use it with reanimated is like this btw:

                    onScroll={event(
                        [
                            {
                                nativeEvent: {
                                    contentOffset: {
                                        x: x => block([
                                            set(this.state.x, x),
                                            call([x], this.setEventDataForNotifier)
                                        ])
                                    },
                                },
                            },
                        ],
                        {
                            useNativeDriver: true,
                        },
                    )}

and inside the function setEventDataForNotifier I set

        this.setState({
            viewportOffsetX: x[0],
            shouldMeasureLayout: false
        })

But nothing happens. My cube gets transitioned but only with placeholders.

P.S: my scrollview is horizontal, not vertical. I set the contentContainer to screenWidth * items

hirbod commented 4 years ago

P.S. some side information: The images take 100% of width and height, so I display 1 image on the full screen and when I scroll horizontically, I snap to the next image... but from my point of view it should work, while it doesn't

bevkoski commented 4 years ago

@Hirbod, the Snack shows that it is possible to use Skele Components together with React Native's Animated. As it seems according to your findings, react-native-reanimated does not work exactly the same, so that might be one source of issues.

It is true that setting the state as it is done in the Snack is not optimal in terms of performance. The approach is used for clarity's sake. For optimal performance, Mixins.WithEvents should be extended in the class containing the ScrollView (in the case of the Snack, App). That way, unnecessary re-rendering will be avoided.

The images take 100% of width and height, so I display 1 image on the full screen and when I scroll horizontically, I snap to the next image...

The 100% size images could affect the whole situation. You can check whether setting the preTriggerRatio parameter to 0.5 helps.

I'm afraid I cannot offer you much more assistance beyond this. Thanks and good luck!

hirbod commented 4 years ago

Thanks for your help though. I played around few more hours and got it work, but the performance is draining so badly, that my efforts to optimize everything, were much more worse. I ended up dropping skele and kinda switch over to FlatList