Skip to content

Commit 8d27359

Browse files
authored
feat: RTL support (#391)
* feat(android): rtl support * feat(ios): rtl support * docs: update readme with layoutDirection prop * fix: emit correct rtl progress values * refactor: rename variable Co-authored-by: Piotr Trocki
1 parent 1938972 commit 8d27359

File tree

9 files changed

+62
-12
lines changed

9 files changed

+62
-12
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ For advanced usage please take a look into our [example project](https://github.
153153
| `overScrollMode: OverScollMode` | Used to override default value of overScroll mode. Can be `auto`, `always` or `never`. Defaults to `auto` | Android |
154154
| `offscreenPageLimit: number` | Set the number of pages that should be retained to either side of the currently visible page(s). Pages beyond this limit will be recreated from the adapter when needed. Defaults to RecyclerView's caching strategy. The given value must either be larger than 0. | Android |
155155
| `overdrag: boolean` | Allows for overscrolling after reaching the end or very beginning or pages. Defaults to `false` | iOS |
156+
| `layoutDirection: ('ltr' / 'rtl' / 'locale')` | Specifies layout direction. Use `ltr` or `rtl` to set explicitly or `locale` to deduce from the default language script of a locale. Defaults to `locale` | both |
156157

157158
| Method | Description | Platform |
158159
| ------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: |

android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.reactnativepagerview
22

3+
import android.util.LayoutDirection
34
import android.view.View
45
import androidx.fragment.app.FragmentActivity
56
import androidx.viewpager2.widget.ViewPager2
@@ -163,6 +164,24 @@ class PagerViewViewManager : ViewGroupManager<ViewPager2>() {
163164
}
164165
}
165166
}
167+
168+
@ReactProp(name = "layoutDirection")
169+
fun setLayoutDirection(viewPager: ViewPager2, value: String) {
170+
when (value) {
171+
"rtl" -> {
172+
viewPager.layoutDirection = View.LAYOUT_DIRECTION_RTL
173+
}
174+
"ltr" -> {
175+
viewPager.layoutDirection = View.LAYOUT_DIRECTION_LTR
176+
}
177+
"locale" -> {
178+
viewPager.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
179+
}
180+
else -> {
181+
viewPager.layoutDirection = View.LAYOUT_DIRECTION_INHERIT
182+
}
183+
}
184+
}
166185

167186
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Map<String, String>> {
168187
return MapBuilder.of(

example/android/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
android:icon="@mipmap/ic_launcher"
88
android:label="@string/app_name"
99
android:usesCleartextTraffic="true"
10+
android:supportsRtl="true"
1011
android:roundIcon="@mipmap/ic_launcher_round"
1112
android:theme="@style/AppTheme">
1213
<activity

example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ PODS:
185185
- React-cxxreact (= 0.63.4)
186186
- React-jsi (= 0.63.4)
187187
- React-jsinspector (0.63.4)
188-
- react-native-pager-view (5.2.0):
188+
- react-native-pager-view (5.2.1):
189189
- React-Core
190190
- react-native-safe-area-context (3.2.0):
191191
- React-Core
@@ -382,7 +382,7 @@ SPEC CHECKSUMS:
382382
React-jsi: a0418934cf48f25b485631deb27c64dc40fb4c31
383383
React-jsiexecutor: 93bd528844ad21dc07aab1c67cb10abae6df6949
384384
React-jsinspector: 58aef7155bc9a9683f5b60b35eccea8722a4f53a
385-
react-native-pager-view: b72d689fb81435f2cab3d556136c83ed124426b0
385+
react-native-pager-view: 2864530d861ab2970beb2cd0b4cf2292cd30cd84
386386
react-native-safe-area-context: f0906bf8bc9835ac9a9d3f97e8bde2a997d8da79
387387
React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336
388388
React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b

example/src/BasicPagerViewExample.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export function BasicPagerViewExample() {
2020
ref={ref}
2121
style={styles.PagerView}
2222
initialPage={0}
23+
layoutDirection="ltr"
2324
overdrag={navigationPanel.overdragEnabled}
2425
scrollEnabled={navigationPanel.scrollEnabled}
2526
onPageScroll={navigationPanel.onPageScroll}

ios/ReactNativePageView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ NS_ASSUME_NONNULL_BEGIN
2222
@property(nonatomic, copy) RCTDirectEventBlock onPageScroll;
2323
@property(nonatomic, copy) RCTDirectEventBlock onPageScrollStateChanged;
2424
@property(nonatomic) BOOL overdrag;
25+
@property(nonatomic) NSString* layoutDirection;
2526

2627

2728
- (void)goTo:(NSInteger)index animated:(BOOL)animated;

ios/ReactNativePageView.m

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#import "RCTOnPageScrollEvent.h"
88
#import "RCTOnPageScrollStateChanged.h"
99
#import "RCTOnPageSelected.h"
10+
#import <math.h>
1011

1112
@interface ReactNativePageView () <UIPageViewControllerDataSource, UIPageViewControllerDelegate, UIScrollViewDelegate>
1213

@@ -45,6 +46,7 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher {
4546
_eventDispatcher = eventDispatcher;
4647
_cachedControllers = [NSHashTable weakObjectsHashTable];
4748
_overdrag = NO;
49+
_layoutDirection = @"ltr";
4850
}
4951
return self;
5052
}
@@ -217,7 +219,8 @@ - (void)goTo:(NSInteger)index animated:(BOOL)animated {
217219
return;
218220
}
219221

220-
UIPageViewControllerNavigationDirection direction = (index > self.currentIndex) ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse;
222+
BOOL isForward = (index > self.currentIndex && [self isLtrLayout]) || (index < self.currentIndex && ![self isLtrLayout]);
223+
UIPageViewControllerNavigationDirection direction = isForward ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse;
221224

222225
NSInteger indexToDisplay = index;
223226

@@ -296,12 +299,14 @@ - (void)pageViewController:(UIPageViewController *)pageViewController
296299

297300
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
298301
viewControllerAfterViewController:(UIViewController *)viewController {
299-
return [self nextControllerForController:viewController inDirection:UIPageViewControllerNavigationDirectionForward];
302+
UIPageViewControllerNavigationDirection direction = [self isLtrLayout] ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse;
303+
return [self nextControllerForController:viewController inDirection:direction];
300304
}
301305

302306
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
303307
viewControllerBeforeViewController:(UIViewController *)viewController {
304-
return [self nextControllerForController:viewController inDirection:UIPageViewControllerNavigationDirectionReverse];
308+
UIPageViewControllerNavigationDirection direction = [self isLtrLayout] ? UIPageViewControllerNavigationDirectionReverse : UIPageViewControllerNavigationDirectionForward;
309+
return [self nextControllerForController:viewController inDirection:direction];
305310
}
306311

307312
#pragma mark - UIPageControlDelegate
@@ -342,8 +347,9 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi
342347
[self.eventDispatcher sendEvent:[[RCTOnPageScrollStateChanged alloc] initWithReactTag:self.reactTag state:@"settling" coalescingKey:_coalescingKey++]];
343348

344349
if (!_overdrag) {
345-
BOOL isFirstPage = _currentIndex == 0;
346-
BOOL isLastPage = _currentIndex == _reactPageIndicatorView.numberOfPages - 1;
350+
NSInteger maxIndex = _reactPageIndicatorView.numberOfPages - 1;
351+
BOOL isFirstPage = [self isLtrLayout] ? _currentIndex == 0 : _currentIndex == maxIndex;
352+
BOOL isLastPage = [self isLtrLayout] ? _currentIndex == maxIndex : _currentIndex == 0;
347353
CGFloat contentOffset =[self isHorizontal] ? scrollView.contentOffset.x : scrollView.contentOffset.y;
348354
CGFloat topBound = [self isHorizontal] ? scrollView.bounds.size.width : scrollView.bounds.size.height;
349355

@@ -381,14 +387,17 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
381387

382388
NSInteger position = self.currentIndex;
383389

384-
if(offset<0 && position>0){
390+
391+
BOOL isAnimatingBackwards = ([self isLtrLayout] && offset<0) || (![self isLtrLayout] && offset > 0.05f);
392+
if(isAnimatingBackwards && position > 0){
385393
position = self.currentIndex - 1;
386-
absoluteOffset = 1 - absoluteOffset;
394+
absoluteOffset = fmax(0, 1 - absoluteOffset);
387395
}
388396

389397
if (!_overdrag) {
390-
BOOL isFirstPage = _currentIndex == 0;
391-
BOOL isLastPage = _currentIndex == _reactPageIndicatorView.numberOfPages - 1;
398+
NSInteger maxIndex = _reactPageIndicatorView.numberOfPages - 1;
399+
BOOL isFirstPage = [self isLtrLayout] ? _currentIndex == 0 : _currentIndex == maxIndex;
400+
BOOL isLastPage = [self isLtrLayout] ? _currentIndex == maxIndex : _currentIndex == 0;
392401
CGFloat contentOffset =[self isHorizontal] ? scrollView.contentOffset.x : scrollView.contentOffset.y;
393402
CGFloat topBound = [self isHorizontal] ? scrollView.bounds.size.width : scrollView.bounds.size.height;
394403

@@ -420,4 +429,12 @@ - (NSString *)determineScrollDirection:(UIScrollView *)scrollView {
420429
}
421430
return scrollDirection;
422431
}
423-
@end
432+
433+
- (BOOL)isLtrLayout {
434+
if ([_layoutDirection isEqualToString:@"locale"]) {
435+
return [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionLeftToRight;
436+
} else {
437+
return [_layoutDirection isEqualToString:@"ltr"];
438+
}
439+
}
440+
@end

ios/ReactViewPagerManager.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ @implementation ReactViewPagerManager
1616
RCT_EXPORT_VIEW_PROPERTY(onPageScroll, RCTDirectEventBlock)
1717
RCT_EXPORT_VIEW_PROPERTY(onPageScrollStateChanged, RCTDirectEventBlock)
1818
RCT_EXPORT_VIEW_PROPERTY(overdrag, BOOL)
19+
RCT_EXPORT_VIEW_PROPERTY(layoutDirection, NSString)
1920

2021

2122
- (void) goToPage

src/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,15 @@ export interface PagerViewProps {
7171
*/
7272
keyboardDismissMode?: 'none' | 'on-drag';
7373

74+
/**
75+
* A class for defining layout directions. A layout direction can be left-to-right (LTR) or right-to-left (RTL).
76+
* It can also be deduced from the default language script of a locale.
77+
* - 'rtl' right to left
78+
* - 'ltr', left to right
79+
* - 'locale', default language script of a locale
80+
*/
81+
layoutDirection?: 'rtl' | 'ltr' | 'locale';
82+
7483
/**
7584
* Blank space to show between pages. This is only visible while scrolling, pages are still
7685
* edge-to-edge.

0 commit comments

Comments
 (0)