Add setPageWithoutAnimation

Summary: In some cases it's desirable to set the page in the ViewPager without animating it -- we have this in ScrollView with `scrollWithoutAnimationTo`. This PR adds `setPageWithoutAnimation` on ViewPager.

cc ide kmagiera
Closes https://github.com/facebook/react-native/pull/3621

Reviewed By: svcscm

Differential Revision: D2652056

Pulled By: mkonicek

fb-gh-sync-id: 6f1f38558c41ffdd863c0ebb2f046c75b5c0392c
This commit is contained in:
Brent Vatne 2015-11-13 08:03:54 -08:00 committed by facebook-github-bot-3
parent 88a92f8f52
commit 50b8b00768
4 changed files with 121 additions and 45 deletions

View File

@ -96,23 +96,39 @@ var ViewPagerAndroidExample = React.createClass({
description: 'Container that allows to flip left and right between child views.' description: 'Container that allows to flip left and right between child views.'
}, },
getInitialState: function() { getInitialState: function() {
return {page: 0, progress: {position: 0, offset: 0}}; return {
page: 0,
animationsAreEnabled: true,
progress: {
position: 0,
offset: 0,
},
};
}, },
onPageSelected: function(e) { onPageSelected: function(e) {
this.setState({page: e.nativeEvent.position}); this.setState({page: e.nativeEvent.position});
}, },
onPageScroll: function(e) { onPageScroll: function(e) {
this.setState({progress: e.nativeEvent}); this.setState({progress: e.nativeEvent});
}, },
move: function(delta) { move: function(delta) {
var page = this.state.page + delta; var page = this.state.page + delta;
this.viewPager && this.viewPager.setPage(page); this.go(page);
this.setState({page});
}, },
go: function(page) { go: function(page) {
this.viewPager && this.viewPager.setPage(page); if (this.state.animationsAreEnabled) {
this.viewPager.setPage(page);
} else {
this.viewPager.setPageWithoutAnimation(page);
}
this.setState({page}); this.setState({page});
}, },
render: function() { render: function() {
var pages = []; var pages = [];
for (var i = 0; i < PAGES; i++) { for (var i = 0; i < PAGES; i++) {
@ -131,7 +147,7 @@ var ViewPagerAndroidExample = React.createClass({
</View> </View>
); );
} }
var page = this.state.page; var { page, animationsAreEnabled } = this.state;
return ( return (
<View style={styles.container}> <View style={styles.container}>
<ViewPagerAndroid <ViewPagerAndroid
@ -142,6 +158,19 @@ var ViewPagerAndroidExample = React.createClass({
ref={viewPager => { this.viewPager = viewPager; }}> ref={viewPager => { this.viewPager = viewPager; }}>
{pages} {pages}
</ViewPagerAndroid> </ViewPagerAndroid>
<View style={styles.buttons}>
{ animationsAreEnabled ?
<Button
text="Turn off animations"
enabled={true}
onPress={() => this.setState({animationsAreEnabled: false})}
/> :
<Button
text="Turn animations back on"
enabled={true}
onPress={() => this.setState({animationsAreEnabled: true})}
/> }
</View>
<View style={styles.buttons}> <View style={styles.buttons}>
<Button text="Start" enabled={page > 0} onPress={() => this.go(0)}/> <Button text="Start" enabled={page > 0} onPress={() => this.go(0)}/>
<Button text="Prev" enabled={page > 0} onPress={() => this.move(-1)}/> <Button text="Prev" enabled={page > 0} onPress={() => this.move(-1)}/>

View File

@ -2,24 +2,24 @@
* Copyright 2004-present Facebook. All Rights Reserved. * Copyright 2004-present Facebook. All Rights Reserved.
* *
* @providesModule ViewPagerAndroid * @providesModule ViewPagerAndroid
* @flow
*/ */
'use strict'; 'use strict';
var NativeModules = require('NativeModules');
var NativeMethodsMixin = require('NativeMethodsMixin'); var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React'); var React = require('React');
var ReactElement = require('ReactElement'); var ReactElement = require('ReactElement');
var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
var ReactPropTypes = require('ReactPropTypes'); var ReactPropTypes = require('ReactPropTypes');
var RCTUIManager = NativeModules.UIManager;
var createReactNativeComponentClass = require('createReactNativeComponentClass'); var createReactNativeComponentClass = require('createReactNativeComponentClass');
var dismissKeyboard = require('dismissKeyboard'); var dismissKeyboard = require('dismissKeyboard');
var VIEWPAGER_REF = 'viewPager'; var VIEWPAGER_REF = 'viewPager';
var ViewPagerValidAttributes = {
selectedPage: true,
};
/** /**
* Container that allows to flip left and right between child views. Each * Container that allows to flip left and right between child views. Each
* child view of the `ViewPagerAndroid` will be treated as a separate page * child view of the `ViewPagerAndroid` will be treated as a separate page
@ -97,17 +97,17 @@ var ViewPagerAndroid = React.createClass({
]), ]),
}, },
getInitialState: function() { componentDidMount: function() {
return { if (this.props.initialPage) {
selectedPage: this.props.initialPage, this.setPageWithoutAnimation(this.props.initialPage);
}; }
}, },
getInnerViewNode: function() { getInnerViewNode: function(): ReactComponent {
return this.refs[VIEWPAGER_REF].getInnerViewNode(); return this.refs[VIEWPAGER_REF].getInnerViewNode();
}, },
_childrenWithOverridenStyle: function() { _childrenWithOverridenStyle: function(): Array {
// Override styles so that each page will fill the parent. Native component // Override styles so that each page will fill the parent. Native component
// will handle positioning of elements, so it's not important to offset // will handle positioning of elements, so it's not important to offset
// them correctly. // them correctly.
@ -134,34 +134,51 @@ var ViewPagerAndroid = React.createClass({
return ReactElement.createElement(child.type, newProps); return ReactElement.createElement(child.type, newProps);
}); });
}, },
_onPageScroll: function(event) {
_onPageScroll: function(e: Event) {
if (this.props.onPageScroll) { if (this.props.onPageScroll) {
this.props.onPageScroll(event); this.props.onPageScroll(e);
} }
if (this.props.keyboardDismissMode === 'on-drag') { if (this.props.keyboardDismissMode === 'on-drag') {
dismissKeyboard(); dismissKeyboard();
} }
}, },
_onPageSelected: function(event) {
var selectedPage = event.nativeEvent.position; _onPageSelected: function(e: Event) {
this.setState({
selectedPage,
});
if (this.props.onPageSelected) { if (this.props.onPageSelected) {
this.props.onPageSelected(event); this.props.onPageSelected(e);
} }
}, },
setPage: function(selectedPage) {
this.setState({ /**
selectedPage, * A helper function to scroll to a specific page in the ViewPager.
}); * The transition between pages will be animated.
*/
setPage: function(selectedPage: number) {
RCTUIManager.dispatchViewManagerCommand(
React.findNodeHandle(this),
RCTUIManager.AndroidViewPager.Commands.setPage,
[selectedPage],
);
}, },
/**
* A helper function to scroll to a specific page in the ViewPager.
* The transition between pages will be *not* be animated.
*/
setPageWithoutAnimation: function(selectedPage: number) {
RCTUIManager.dispatchViewManagerCommand(
React.findNodeHandle(this),
RCTUIManager.AndroidViewPager.Commands.setPageWithoutAnimation,
[selectedPage],
);
},
render: function() { render: function() {
return ( return (
<NativeAndroidViewPager <NativeAndroidViewPager
ref={VIEWPAGER_REF} ref={VIEWPAGER_REF}
style={this.props.style} style={this.props.style}
selectedPage={this.state.selectedPage}
onPageScroll={this._onPageScroll} onPageScroll={this._onPageScroll}
onPageSelected={this._onPageSelected} onPageSelected={this._onPageSelected}
children={this._childrenWithOverridenStyle()} children={this._childrenWithOverridenStyle()}
@ -171,10 +188,7 @@ var ViewPagerAndroid = React.createClass({
}); });
var NativeAndroidViewPager = createReactNativeComponentClass({ var NativeAndroidViewPager = createReactNativeComponentClass({
validAttributes: { validAttributes: ReactNativeViewAttributes.UIView,
...ReactNativeViewAttributes.UIView,
...ViewPagerValidAttributes,
},
uiViewClassName: 'AndroidViewPager', uiViewClassName: 'AndroidViewPager',
}); });

View File

@ -133,6 +133,12 @@ import com.facebook.react.uimanager.events.NativeGestureUtil;
return false; return false;
} }
public void setCurrentItemFromJs(int item, boolean animated) {
mIsCurrentItemFromJs = true;
setCurrentItem(item, animated);
mIsCurrentItemFromJs = false;
}
/*package*/ void addViewToAdapter(View child, int index) { /*package*/ void addViewToAdapter(View child, int index) {
getAdapter().addView(child, index); getAdapter().addView(child, index);
} }
@ -148,10 +154,4 @@ import com.facebook.react.uimanager.events.NativeGestureUtil;
/*package*/ View getViewFromAdapter(int index) { /*package*/ View getViewFromAdapter(int index) {
return getAdapter().getViewAt(index); return getAdapter().getViewAt(index);
} }
/*package*/ void setCurrentItemFromJs(int item) {
mIsCurrentItemFromJs = true;
setCurrentItem(item);
mIsCurrentItemFromJs = false;
}
} }

View File

@ -13,11 +13,14 @@ import java.util.Map;
import android.view.View; import android.view.View;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder; import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ReactProp;
import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.ViewGroupManager;
import javax.annotation.Nullable;
/** /**
* Instance of {@link ViewManager} that provides native {@link ViewPager} view. * Instance of {@link ViewManager} that provides native {@link ViewPager} view.
*/ */
@ -25,6 +28,9 @@ public class ReactViewPagerManager extends ViewGroupManager<ReactViewPager> {
private static final String REACT_CLASS = "AndroidViewPager"; private static final String REACT_CLASS = "AndroidViewPager";
public static final int COMMAND_SET_PAGE = 1;
public static final int COMMAND_SET_PAGE_WITHOUT_ANIMATION = 2;
@Override @Override
public String getName() { public String getName() {
return REACT_CLASS; return REACT_CLASS;
@ -35,12 +41,6 @@ public class ReactViewPagerManager extends ViewGroupManager<ReactViewPager> {
return new ReactViewPager(reactContext); return new ReactViewPager(reactContext);
} }
@ReactProp(name = "selectedPage")
public void setSelectedPage(ReactViewPager view, int page) {
// TODO(8496821): Handle selectedPage property cleanup correctly, now defaults to 0
view.setCurrentItemFromJs(page);
}
@Override @Override
public boolean needsCustomLayoutForChildren() { public boolean needsCustomLayoutForChildren() {
return true; return true;
@ -54,6 +54,39 @@ public class ReactViewPagerManager extends ViewGroupManager<ReactViewPager> {
); );
} }
@Override
public Map<String,Integer> getCommandsMap() {
return MapBuilder.of(
"setPage",
COMMAND_SET_PAGE,
"setPageWithoutAnimation",
COMMAND_SET_PAGE_WITHOUT_ANIMATION);
}
@Override
public void receiveCommand(
ReactViewPager viewPager,
int commandType,
@Nullable ReadableArray args) {
Assertions.assertNotNull(viewPager);
Assertions.assertNotNull(args);
switch (commandType) {
case COMMAND_SET_PAGE: {
viewPager.setCurrentItemFromJs(args.getInt(0), true);
return;
}
case COMMAND_SET_PAGE_WITHOUT_ANIMATION: {
viewPager.setCurrentItemFromJs(args.getInt(0), false);
return;
}
default:
throw new IllegalArgumentException(String.format(
"Unsupported command %d received by %s.",
commandType,
getClass().getSimpleName()));
}
}
@Override @Override
public void addView(ReactViewPager parent, View child, int index) { public void addView(ReactViewPager parent, View child, int index) {
parent.addViewToAdapter(child, index); parent.addViewToAdapter(child, index);