dohooo / react-native-reanimated-carousel

🎠 React Native swiper/carousel component, fully implemented using reanimated v2, support to iOS/Android/Web. (Swiper/Carousel)
https://react-native-reanimated-carousel.vercel.app
MIT License
2.58k stars 295 forks source link

【Android only】autoPlay does not work when scrolling up or down in a scroll view #587

Open lchenfox opened 2 months ago

lchenfox commented 2 months ago

Describe the bug

As described above, autoPlay settings does not work when scrolling up or down with touches on carousel in a scroll view on Android. It works well on iOS.

To Reproduce Steps to reproduce the behavior:

  1. Install and import react-native-reanimated-carousel.
  2. Wrapping Carousel in a scroll view.
  3. Sets autoPlay and loop as true.
  4. Scrolling up or down.
  5. The autoPlay does not work on android, in other words, the autoPlay stops.

Expected behavior autoPlay works well on Android even if when scrolling up or down.

Screenshots

https://github.com/dohooo/react-native-reanimated-carousel/assets/22851821/664bde20-a7a2-415a-b032-bb42ddfcce92

Versions (please complete the following information):

Smartphone (please complete the following information):

Additional context

Codes

import { Dimensions, ScrollView, Image, View } from "react-native";
import Carousel from "react-native-reanimated-carousel";
import { GestureHandlerRootView } from "react-native-gesture-handler";

export default function App(props) {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <ScrollView>
        <View style={{ height: 500, backgroundColor: "blue" }} />
        <Carousel
          data={[
            "https://picnew13.photophoto.cn/20190309/chuntianlvsedianshanghaibaobeijingbanner-32416436_1.jpg",
            "https://img1.baidu.com/it/u=3661953276,17905528&fm=253&fmt=auto&app=138&f=JPEG?w=768&h=260",
          ]}
          width={Dimensions.get("window").width}
          height={250}
          loop={true}
          autoPlay={true}
          panGestureHandlerProps={{
            activeOffsetX: [-10, 10],
            failOffsetY: [-20, 20],
          }}
          renderItem={({ item }) => {
            return (
              <View>
                <Image style={{ width: "100%", height: "100%" }} source={{ uri: item }} />
              </View>
            );
          }}
        />
        <View style={{ height: 200, backgroundColor: "red" }} />
      </ScrollView>
    </GestureHandlerRootView>
  );
}
dosubot[bot] commented 2 months ago

The issue you're experiencing with autoPlay not working on Android when used within a ScrollView is a known problem. A common workaround is to ensure the entire app is wrapped in GestureHandlerRootView from react-native-gesture-handler. This adjustment has helped others with similar issues:

import { Dimensions, ScrollView, Image, View } from "react-native";
import Carousel from "react-native-reanimated-carousel";
import { GestureHandlerRootView } from "react-native-gesture-handler";

export default function App(props) {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <ScrollView>
        ...
        <Carousel
          ...
        />
        ...
      </ScrollView>
    </GestureHandlerRootView>
  );
}

This solution is based on feedback from similar issues reported in the react-native-reanimated-carousel repository. If this doesn't resolve the problem, consider manually controlling the autoPlay feature based on the ScrollView's scroll activity. This involves pausing autoPlay when scrolling starts and resuming it once scrolling stops. You can refer to the useAutoPlay hook in the carousel's source code for guidance on implementing this behavior.

Try these approaches and see if they address the issue. If you encounter further difficulties or need more assistance, feel free to ask.

Sources

#### About Dosu This response is meant to be useful and save you time. It is **not meant to be a precise solution**, but rather a starting point for your own research. Help me be more useful! Please leave a 👍 if this is helpful and 👎 if it is irrelevant. If you want to continue the conversation, start your reply with @dosu-bot.
minhtet-ko commented 1 month ago

I also faced this issue.

My Carousel Component is inside Scrollview. (BannerImageSlider Component)

Problem 1 -> This slider stop between 2 items when scrolling initialize horizontally with a little bit of vertical scroll. Problem 2 -> After fixed Problem 1, another problem is auto play doesn't work if pan gesture of slider ended by cancel ( onCancelled props of panGestureHandlerProps)

Both Problem 1 and Problem 2 are solved.

This solution may help you :)

Problem 1

https://github.com/dohooo/react-native-reanimated-carousel/assets/125124625/be4e7c70-02a3-4dae-bfe0-0cd50cef7aa9

Problem 2

https://github.com/dohooo/react-native-reanimated-carousel/assets/125124625/6303e077-983e-4fad-ae6c-ea7664f925f2

After fixed Problem 1 and Problem 2

https://github.com/dohooo/react-native-reanimated-carousel/assets/125124625/751f450b-5d8d-4d49-b543-39dd321c4d93

// Solution

import React, { useRef, useState } from "react";
import { Dimensions, View } from "react-native";
import { Text } from "react-native-paper";
import Carousel from "react-native-reanimated-carousel";

export default function BannerImageSlider({
  leftSpacing = 0,
  rightSpacing = 0,
}) {
  const width = Dimensions.get("window").width;
  const containerWidth = width - leftSpacing - rightSpacing;

  const sliderRef = useRef(null);
  const absoluteProgressRef = useRef(0);
  const isScrolling = useRef(false);

  const [isFailedGesture, setIsFailedGesture] = useState(false);

  return (
    <View style={{ alignItems: "center" }}>
      <Carousel
        loop
        ref={sliderRef}
        width={containerWidth}
        height={containerWidth / 2}
        // Manually control autoPlay based on isFailedGesture (state)
        autoPlay={!isFailedGesture}
        data={[...Array(5)]}
        scrollAnimationDuration={300}
        autoPlayInterval={1000}
        onSnapToItem={(index) => {}}
        windowSize={20}
        onProgressChange={(offsetProgress, absoluteProgress) => {
          absoluteProgressRef.current = absoluteProgress;
        }}
        panGestureHandlerProps={{
          activeOffsetX: [-10, 10],

          // Fixed Problem 1 by comparing absoluteProgress (Ref) and
          // currentIndex then manually scroll item
          onCancelled: (event) => {
            const currentIndex = sliderRef.current.getCurrentIndex();
            const absoluteProgress = absoluteProgressRef.current;
            if (absoluteProgress > currentIndex) {
              sliderRef.current.next();
            } else {
              sliderRef.current.prev();
            }
            setIsFailedGesture(true);
          },
        }}
        // Fixed Problem 2 by setting isFailedGesture (state) on scroll begin
        // and on scroll end then manually control autoPlay value based on
        // this state
        onScrollBegin={() => setIsFailedGesture(false)}
        onScrollEnd={() => setIsFailedGesture(false)}
        snapEnabled={false}
        pagingEnabled={true}
        renderItem={({ item, index }) => (
          <View
            style={{
              flex: 1,
              justifyContent: "center",
            }}
          >
            <Text variant="titleLarge" style={{ textAlign: "center" }}>
              {index}
            </Text>
          </View>
        )}
      />
    </View>
  );
}
lchenfox commented 1 month ago

@minhtet-ko Thank you for your answer. However, although the autoPlay works after using your solution. But it introduces another problem that each time I put my finger on it and drag it, sliderRef.current.next() is always triggered first. The expected result should be that the sliderRef.current.next() and setIsFailedGesture(true) are called after releasing my finger(Gesture). Anyway, thanks again.