diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f7494768..202500b2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,9 +4,11 @@ on: pull_request: branches: - master + - next push: branches: - master + - next concurrency: group: ${{ github.ref }}-js diff --git a/README.md b/README.md index a5e301f2..827cecf9 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,8 @@ For advanced usage please take a look into our [example project](https://github. | Prop | Description | Platform | | -------------------------------------------------------------------- | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: | +| `page: number` | Index of page to be displayed | both | +| `animated: boolean` | Should transition between pages be animated | both | | `initialPage` | Index of initial page that should be selected | both | | `scrollEnabled: boolean` | Should pager view scroll, when scroll enabled | both | | `onPageScroll: (e: PageScrollEvent) => void` | Executed when transitioning between pages (ether because the animation for the requested page has changed or when the user is swiping/dragging between pages) | both | @@ -184,8 +186,8 @@ For advanced usage please take a look into our [example project](https://github. | Method | Description | Platform | | ------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: | -| `setPage(index: number)` | Function to scroll to a specific page in the PagerView. Invalid index is ignored. | both | -| `setPageWithoutAnimation(index: number)` | Function to scroll to a specific page in the PagerView. Invalid index is ignored. | both | +| `setPage(index: number)` | Function to scroll to a specific page in the PagerView. Invalid index is ignored. The recommended way is using the `page` and `animated` props | both | +| `setPageWithoutAnimation(index: number)` | Function to scroll to a specific page in the PagerView. Invalid index is ignored. The recommended way is using the `page` and `animated` props | both | | `setScrollEnabled(scrollEnabled: boolean)` | A helper function to enable/disable scroll imperatively. The recommended way is using the scrollEnabled prop, however, there might be a case where a imperative solution is more useful (e.g. for not blocking an animation) | both | ## Contributing diff --git a/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt b/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt index f6a284bf..1145a5c1 100644 --- a/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt +++ b/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt @@ -105,6 +105,16 @@ class PagerViewViewManager : ViewGroupManager(), RNCViewPa return PagerViewViewManagerImpl.needsCustomLayoutForChildren() } + @ReactProp(name = "page") + override fun setPage(view: NestedScrollableHost?, value: Int) { + goTo(view, value, PagerViewViewManagerImpl.animated) + } + + @ReactProp(name = "animated", defaultBoolean = true) + override fun setAnimated(view: NestedScrollableHost?, value: Boolean) { + PagerViewViewManagerImpl.animated = value + } + @ReactProp(name = "scrollEnabled", defaultBoolean = true) override fun setScrollEnabled(view: NestedScrollableHost?, value: Boolean) { if (view != null) { @@ -181,7 +191,7 @@ class PagerViewViewManager : ViewGroupManager(), RNCViewPa } } - override fun setPage(view: NestedScrollableHost?, selectedPage: Int) { + override fun setPageWithAnimation(view: NestedScrollableHost?, selectedPage: Int) { goTo(view, selectedPage, true) } diff --git a/android/src/main/java/com/reactnativepagerview/PagerViewViewManagerImpl.kt b/android/src/main/java/com/reactnativepagerview/PagerViewViewManagerImpl.kt index 0abf6682..ab6d14f4 100644 --- a/android/src/main/java/com/reactnativepagerview/PagerViewViewManagerImpl.kt +++ b/android/src/main/java/com/reactnativepagerview/PagerViewViewManagerImpl.kt @@ -7,6 +7,8 @@ import com.facebook.react.uimanager.PixelUtil object PagerViewViewManagerImpl { const val NAME = "RNCViewPager" + var animated = true + fun getViewPager(view: NestedScrollableHost): ViewPager2 { if (view.getChildAt(0) is ViewPager2) { return view.getChildAt(0) as ViewPager2 diff --git a/android/src/paper/java/com/reactnativepagerview/PagerViewViewManager.kt b/android/src/paper/java/com/reactnativepagerview/PagerViewViewManager.kt index 2a093a63..89f9b2ab 100644 --- a/android/src/paper/java/com/reactnativepagerview/PagerViewViewManager.kt +++ b/android/src/paper/java/com/reactnativepagerview/PagerViewViewManager.kt @@ -95,6 +95,25 @@ class PagerViewViewManager : ViewGroupManager() { return PagerViewViewManagerImpl.needsCustomLayoutForChildren() } + @ReactProp(name = "page") + fun setPage(host: NestedScrollableHost, pageIndex: Int) { + val view = PagerViewViewManagerImpl.getViewPager(host) + Assertions.assertNotNull(view) + Assertions.assertNotNull(pageIndex) + val childCount = view.adapter?.itemCount + val animated = PagerViewViewManagerImpl.animated + val canScroll = childCount != null && childCount > 0 && pageIndex >= 0 && pageIndex < childCount + if (canScroll) { + PagerViewViewManagerImpl.setCurrentItem(view, pageIndex, animated) + eventDispatcher.dispatchEvent(PageSelectedEvent(host.id, pageIndex)) + } + } + + @ReactProp(name = "animated", defaultBoolean = true) + fun setAnimated(host: NestedScrollableHost, value: Boolean) { + PagerViewViewManagerImpl.animated = value + } + @ReactProp(name = "scrollEnabled", defaultBoolean = true) fun setScrollEnabled(host: NestedScrollableHost, value: Boolean) { PagerViewViewManagerImpl.setScrollEnabled(host, value) @@ -165,7 +184,7 @@ class PagerViewViewManager : ViewGroupManager() { } companion object { - private const val COMMAND_SET_PAGE = "setPage" + private const val COMMAND_SET_PAGE = "setPageWithAnimation" private const val COMMAND_SET_PAGE_WITHOUT_ANIMATION = "setPageWithoutAnimation" private const val COMMAND_SET_SCROLL_ENABLED = "setScrollEnabledImperatively" } diff --git a/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerComponentDescriptor.h b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerComponentDescriptor.h new file mode 100644 index 00000000..b5b1b1df --- /dev/null +++ b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerComponentDescriptor.h @@ -0,0 +1,12 @@ +#pragma once + +#include "RNCViewPagerShadowNode.h" +#include + +namespace facebook { +namespace react { + +using RNCViewPagerComponentDescriptor = ConcreteComponentDescriptor; + +} +} diff --git a/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerShadowNode.cpp b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerShadowNode.cpp new file mode 100644 index 00000000..a81506ac --- /dev/null +++ b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerShadowNode.cpp @@ -0,0 +1,35 @@ +#include "RNCViewPagerShadowNode.h" + +#include +#include + +namespace facebook { +namespace react { + +const char RNCViewPagerComponentName[] = "RNCViewPager"; + +void RNCViewPagerShadowNode::updateStateIfNeeded() { + ensureUnsealed(); + + auto contentBoundingRect = Rect{}; + for (const auto &childNode : getLayoutableChildNodes()) { + contentBoundingRect.unionInPlace(childNode->getLayoutMetrics().frame); + } + + auto state = getStateData(); + + if (state.contentBoundingRect != contentBoundingRect) { + state.contentBoundingRect = contentBoundingRect; + setStateData(std::move(state)); + } +} + +#pragma mark - LayoutableShadowNode + +void RNCViewPagerShadowNode::layout(LayoutContext layoutContext) { + ConcreteViewShadowNode::layout(layoutContext); + updateStateIfNeeded(); +} + +} +} diff --git a/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerShadowNode.h b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerShadowNode.h new file mode 100644 index 00000000..1aaece35 --- /dev/null +++ b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerShadowNode.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +extern const char RNCViewPagerComponentName[]; + +class RNCViewPagerShadowNode final : public ConcreteViewShadowNode< + RNCViewPagerComponentName, + RNCViewPagerProps, + RNCViewPagerEventEmitter, + RNCViewPagerState> { +public: + using ConcreteViewShadowNode::ConcreteViewShadowNode; + +#pragma mark - LayoutableShadowNode + + void layout(LayoutContext layoutContext) override; + +private: + void updateStateIfNeeded(); +}; + +} +} diff --git a/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerState.cpp b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerState.cpp new file mode 100644 index 00000000..a99afaff --- /dev/null +++ b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerState.cpp @@ -0,0 +1,11 @@ +#include "RNCViewPagerState.h" + +namespace facebook { +namespace react { + +Size RNCViewPagerState::getContentSize() const { + return contentBoundingRect.size; +} + +} +} diff --git a/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerState.h b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerState.h new file mode 100644 index 00000000..ac43dadf --- /dev/null +++ b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerState.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace facebook { +namespace react { + +class RNCViewPagerState final { +public: + Point contentOffset; + Rect contentBoundingRect; + + Size getContentSize() const; + +}; + +} +} diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 798085e7..e75983d6 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -302,7 +302,7 @@ PODS: - React-jsinspector (0.70.5) - React-logger (0.70.5): - glog - - react-native-pager-view (6.1.1): + - react-native-pager-view (6.1.2): - React-Core - react-native-safe-area-context (3.4.1): - React-Core @@ -617,7 +617,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 31564fa6912459921568e8b0e49024285a4d584b React-jsinspector: badd81696361249893a80477983e697aab3c1a34 React-logger: fdda34dd285bdb0232e059b19d9606fa0ec3bb9c - react-native-pager-view: 3c66c4e2f3ab423643d07b2c7041f8ac48395f72 + react-native-pager-view: 54bed894cecebe28cede54c01038d9d1e122de43 react-native-safe-area-context: 9e40fb181dac02619414ba1294d6c2a807056ab9 React-perflogger: e68d3795cf5d247a0379735cbac7309adf2fb931 React-RCTActionSheet: 05452c3b281edb27850253db13ecd4c5a65bc247 diff --git a/example/src/BasicPagerViewExample.tsx b/example/src/BasicPagerViewExample.tsx index 6c20a89e..b54ca421 100644 --- a/example/src/BasicPagerViewExample.tsx +++ b/example/src/BasicPagerViewExample.tsx @@ -18,6 +18,8 @@ export function BasicPagerViewExample() { //@ts-ignore testID="pager-view" ref={ref} + page={navigationPanel.page} + animated={navigationPanel.animated} style={styles.PagerView} initialPage={0} layoutDirection="ltr" diff --git a/example/src/NestPagerView.tsx b/example/src/NestPagerView.tsx index f455c06c..5598b1c4 100644 --- a/example/src/NestPagerView.tsx +++ b/example/src/NestPagerView.tsx @@ -20,12 +20,12 @@ export function NestPagerView() { > - + There has two Nest PagerView with horizontal and vertical. @@ -39,7 +39,7 @@ export function NestPagerView() { > @@ -47,7 +47,7 @@ export function NestPagerView() { @@ -64,7 +64,7 @@ export function NestPagerView() { > @@ -72,7 +72,7 @@ export function NestPagerView() { @@ -82,7 +82,7 @@ export function NestPagerView() { @@ -105,5 +105,8 @@ const styles = StyleSheet.create({ PagerView: { flex: 1, }, + page: { + flex: 1, + }, title: { fontSize: 22, paddingVertical: 10 }, }); diff --git a/example/src/OnPageScrollExample.tsx b/example/src/OnPageScrollExample.tsx index cbe22917..18db856b 100644 --- a/example/src/OnPageScrollExample.tsx +++ b/example/src/OnPageScrollExample.tsx @@ -10,7 +10,7 @@ const AnimatedPagerView = Animated.createAnimatedComponent(PagerView); export function OnPageScrollExample() { const { ref, ...navigationPanel } = useNavigationPanel(5); - const { activePage, setPage, progress, pages } = navigationPanel; + const { page, setPage, progress, pages } = navigationPanel; return ( @@ -22,7 +22,7 @@ export function OnPageScrollExample() { Page {index} diff --git a/example/src/PaginationDotsExample.tsx b/example/src/PaginationDotsExample.tsx index dd16f6e6..82899286 100644 --- a/example/src/PaginationDotsExample.tsx +++ b/example/src/PaginationDotsExample.tsx @@ -166,6 +166,7 @@ const styles = StyleSheet.create({ }, progressContainer: { flex: 0.1, backgroundColor: '#63a4ff' }, center: { + flex: 1, justifyContent: 'center', alignItems: 'center', alignContent: 'center', diff --git a/example/src/component/NavigationPanel/ControlPanel.tsx b/example/src/component/NavigationPanel/ControlPanel.tsx index 3d3ca71c..fb526637 100644 --- a/example/src/component/NavigationPanel/ControlPanel.tsx +++ b/example/src/component/NavigationPanel/ControlPanel.tsx @@ -5,8 +5,8 @@ import { ProgressBar } from '../ProgressBar'; import type { NavigationPanelProps } from './types'; export function ControlsPanel({ - activePage, - isAnimated, + page, + animated, pages, scrollState, scrollEnabled, @@ -21,14 +21,8 @@ export function ControlsPanel({ toggleOverdrag, }: NavigationPanelProps) { const firstPage = useCallback(() => setPage(0), [setPage]); - const prevPage = useCallback(() => setPage(activePage - 1), [ - activePage, - setPage, - ]); - const nextPage = useCallback(() => setPage(activePage + 1), [ - setPage, - activePage, - ]); + const prevPage = useCallback(() => setPage(page - 1), [page, setPage]); + const nextPage = useCallback(() => setPage(page + 1), [setPage, page]); const lastPage = useCallback(() => setPage(pages.length - 1), [ pages.length, setPage, @@ -64,7 +58,7 @@ export function ControlsPanel({