mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-29 12:45:37 +08:00
Open source ViewPagerAndroid
Reviewed By: @foghina Differential Revision: D2513014 fb-gh-sync-id: d9bb668d76939ad85b657233c8b7beac9b244fab
This commit is contained in:
committed by
facebook-github-bot-4
parent
6244fd003d
commit
0a419650ce
@@ -32,6 +32,7 @@ var COMPONENTS = [
|
|||||||
require('./ToolbarAndroidExample'),
|
require('./ToolbarAndroidExample'),
|
||||||
require('./TouchableExample'),
|
require('./TouchableExample'),
|
||||||
require('./ViewExample'),
|
require('./ViewExample'),
|
||||||
|
require('./ViewPagerAndroidExample.android'),
|
||||||
];
|
];
|
||||||
|
|
||||||
var APIS = [
|
var APIS = [
|
||||||
|
|||||||
223
Examples/UIExplorer/ViewPagerAndroidExample.android.js
Normal file
223
Examples/UIExplorer/ViewPagerAndroidExample.android.js
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
/**
|
||||||
|
* The examples provided by Facebook are for non-commercial testing and
|
||||||
|
* evaluation purposes only.
|
||||||
|
*
|
||||||
|
* Facebook reserves all rights not expressly granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react-native');
|
||||||
|
var {
|
||||||
|
Image,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
TouchableWithoutFeedback,
|
||||||
|
TouchableOpacity,
|
||||||
|
View,
|
||||||
|
ViewPagerAndroid,
|
||||||
|
} = React;
|
||||||
|
|
||||||
|
var PAGES = 5;
|
||||||
|
var BGCOLOR = ['#fdc08e', '#fff6b9', '#99d1b7', '#dde5fe', '#f79273'];
|
||||||
|
var IMAGE_URIS = [
|
||||||
|
'http://apod.nasa.gov/apod/image/1410/20141008tleBaldridge001h990.jpg',
|
||||||
|
'http://apod.nasa.gov/apod/image/1409/volcanicpillar_vetter_960.jpg',
|
||||||
|
'http://apod.nasa.gov/apod/image/1409/m27_snyder_960.jpg',
|
||||||
|
'http://apod.nasa.gov/apod/image/1409/PupAmulti_rot0.jpg',
|
||||||
|
'http://apod.nasa.gov/apod/image/1510/lunareclipse_27Sep_beletskycrop4.jpg',
|
||||||
|
];
|
||||||
|
|
||||||
|
var LikeCount = React.createClass({
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
likes: 7,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onClick: function() {
|
||||||
|
this.setState({likes: this.state.likes + 1});
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
var thumbsUp = '\uD83D\uDC4D';
|
||||||
|
return (
|
||||||
|
<View style={styles.likeContainer}>
|
||||||
|
<TouchableOpacity onPress={this.onClick} style={styles.likeButton}>
|
||||||
|
<Text style={styles.likesText}>
|
||||||
|
{thumbsUp + ' Like'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.likesText}>
|
||||||
|
{this.state.likes + ' likes'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var Button = React.createClass({
|
||||||
|
_handlePress: function() {
|
||||||
|
if (this.props.enabled && this.props.onPress) {
|
||||||
|
this.props.onPress();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<TouchableWithoutFeedback onPress={this._handlePress}>
|
||||||
|
<View style={[styles.button, this.props.enabled ? {} : styles.buttonDisabled]}>
|
||||||
|
<Text style={styles.buttonText}>{this.props.text}</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var ProgressBar = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
var fractionalPosition = (this.props.progress.position + this.props.progress.offset);
|
||||||
|
var progressBarSize = (fractionalPosition / (PAGES - 1)) * this.props.size;
|
||||||
|
return (
|
||||||
|
<View style={[styles.progressBarContainer, {width: this.props.size}]}>
|
||||||
|
<View style={[styles.progressBar, {width: progressBarSize}]}/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var ViewPagerAndroidExample = React.createClass({
|
||||||
|
statics: {
|
||||||
|
title: '<ViewPagerAndroid>',
|
||||||
|
description: 'Container that allows to flip left and right between child views.'
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
return {page: 0, progress: {position: 0, offset: 0}};
|
||||||
|
},
|
||||||
|
onPageSelected: function(e) {
|
||||||
|
this.setState({page: e.nativeEvent.position});
|
||||||
|
},
|
||||||
|
onPageScroll: function(e) {
|
||||||
|
this.setState({progress: e.nativeEvent});
|
||||||
|
},
|
||||||
|
move: function(delta) {
|
||||||
|
var page = this.state.page + delta;
|
||||||
|
this.viewPager && this.viewPager.setPage(page);
|
||||||
|
this.setState({page});
|
||||||
|
},
|
||||||
|
go: function(page) {
|
||||||
|
this.viewPager && this.viewPager.setPage(page);
|
||||||
|
this.setState({page});
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
var pages = [];
|
||||||
|
for (var i = 0; i < PAGES; i++) {
|
||||||
|
var pageStyle = {
|
||||||
|
backgroundColor: BGCOLOR[i % BGCOLOR.length],
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 20,
|
||||||
|
};
|
||||||
|
pages.push(
|
||||||
|
<View key={i} style={pageStyle} collapsable={false}>
|
||||||
|
<Image
|
||||||
|
style={styles.image}
|
||||||
|
source={{uri: IMAGE_URIS[i % BGCOLOR.length]}}
|
||||||
|
/>
|
||||||
|
<LikeCount />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
var page = this.state.page;
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<ViewPagerAndroid
|
||||||
|
style={styles.viewPager}
|
||||||
|
initialPage={0}
|
||||||
|
onPageScroll={this.onPageScroll}
|
||||||
|
onPageSelected={this.onPageSelected}
|
||||||
|
ref={viewPager => { this.viewPager = viewPager; }}>
|
||||||
|
{pages}
|
||||||
|
</ViewPagerAndroid>
|
||||||
|
<View style={styles.buttons}>
|
||||||
|
<Button text="Start" enabled={page > 0} onPress={() => this.go(0)}/>
|
||||||
|
<Button text="Prev" enabled={page > 0} onPress={() => this.move(-1)}/>
|
||||||
|
<Text style={styles.buttonText}>Page {page + 1} / {PAGES}</Text>
|
||||||
|
<ProgressBar size={100} progress={this.state.progress}/>
|
||||||
|
<Button text="Next" enabled={page < PAGES - 1} onPress={() => this.move(1)}/>
|
||||||
|
<Button text="Last" enabled={page < PAGES - 1} onPress={() => this.go(PAGES - 1)}/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
buttons: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: 30,
|
||||||
|
backgroundColor: 'black',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
flex: 1,
|
||||||
|
width: 0,
|
||||||
|
margin: 5,
|
||||||
|
borderColor: 'gray',
|
||||||
|
borderWidth: 1,
|
||||||
|
backgroundColor: 'gray',
|
||||||
|
},
|
||||||
|
buttonDisabled: {
|
||||||
|
backgroundColor: 'black',
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: 'white',
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
width: 300,
|
||||||
|
height: 200,
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
likeButton: {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
borderColor: '#333333',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 5,
|
||||||
|
flex: 1,
|
||||||
|
margin: 8,
|
||||||
|
padding: 8,
|
||||||
|
},
|
||||||
|
likeContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
likesText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 18,
|
||||||
|
alignSelf: 'center',
|
||||||
|
},
|
||||||
|
progressBarContainer: {
|
||||||
|
height: 10,
|
||||||
|
margin: 10,
|
||||||
|
borderColor: '#eeeeee',
|
||||||
|
borderWidth: 2,
|
||||||
|
},
|
||||||
|
progressBar: {
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#eeeeee',
|
||||||
|
},
|
||||||
|
viewPager: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ViewPagerAndroidExample;
|
||||||
178
Libraries/Components/ViewPager/ViewPagerAndroid.android.js
Normal file
178
Libraries/Components/ViewPager/ViewPagerAndroid.android.js
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* @providesModule ViewPagerAndroid
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||||
|
var React = require('React');
|
||||||
|
var ReactElement = require('ReactElement');
|
||||||
|
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||||
|
var ReactPropTypes = require('ReactPropTypes');
|
||||||
|
|
||||||
|
var createReactNativeComponentClass = require('createReactNativeComponentClass');
|
||||||
|
var dismissKeyboard = require('dismissKeyboard');
|
||||||
|
|
||||||
|
var VIEWPAGER_REF = 'viewPager';
|
||||||
|
|
||||||
|
var ViewPagerValidAttributes = {
|
||||||
|
selectedPage: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container that allows to flip left and right between child views. Each
|
||||||
|
* child view of the `ViewPagerAndroid` will be treated as a separate page
|
||||||
|
* and will be streched to fill the `ViewPagerAndroid`.
|
||||||
|
*
|
||||||
|
* It is important all children are `<View>`s and not composite components.
|
||||||
|
* You can set style properties like `padding` or `backgroundColor` for each
|
||||||
|
* child.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* render: function() {
|
||||||
|
* return (
|
||||||
|
* <ViewPagerAndroid
|
||||||
|
* style={styles.viewPager}
|
||||||
|
* initialPage={0}>
|
||||||
|
* <View style={styles.pageStyle}>
|
||||||
|
* <Text>First page</Text>
|
||||||
|
* </View>
|
||||||
|
* <View style={styles.pageStyle}>
|
||||||
|
* <Text>Second page</Text>
|
||||||
|
* </View>
|
||||||
|
* </ViewPagerAndroid>
|
||||||
|
* );
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* ...
|
||||||
|
*
|
||||||
|
* var styles = {
|
||||||
|
* ...
|
||||||
|
* pageStyle: {
|
||||||
|
* alignItems: 'center',
|
||||||
|
* padding: 20,
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
var ViewPagerAndroid = React.createClass({
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
/**
|
||||||
|
* Index of initial page that should be selected. Use `setPage` method to
|
||||||
|
* update the page, and `onPageSelected` to monitor page changes
|
||||||
|
*/
|
||||||
|
initialPage: ReactPropTypes.number,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executed when transitioning between pages (ether because of animation for
|
||||||
|
* the requested page change or when user is swiping/dragging between pages)
|
||||||
|
* The `event.nativeEvent` object for this callback will carry following data:
|
||||||
|
* - position - index of first page from the left that is currently visible
|
||||||
|
* - offset - value from range [0,1) describing stage between page transitions.
|
||||||
|
* Value x means that (1 - x) fraction of the page at "position" index is
|
||||||
|
* visible, and x fraction of the next page is visible.
|
||||||
|
*/
|
||||||
|
onPageScroll: ReactPropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback will be caleld once ViewPager finish navigating to selected page
|
||||||
|
* (when user swipes between pages). The `event.nativeEvent` object passed to this
|
||||||
|
* callback will have following fields:
|
||||||
|
* - position - index of page that has been selected
|
||||||
|
*/
|
||||||
|
onPageSelected: ReactPropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the keyboard gets dismissed in response to a drag.
|
||||||
|
* - 'none' (the default), drags do not dismiss the keyboard.
|
||||||
|
* - 'on-drag', the keyboard is dismissed when a drag begins.
|
||||||
|
*/
|
||||||
|
keyboardDismissMode: ReactPropTypes.oneOf([
|
||||||
|
'none', // default
|
||||||
|
'on-drag',
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
selectedPage: this.props.initialPage,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getInnerViewNode: function() {
|
||||||
|
return this.refs[VIEWPAGER_REF].getInnerViewNode();
|
||||||
|
},
|
||||||
|
|
||||||
|
_childrenWithOverridenStyle: function() {
|
||||||
|
// Override styles so that each page will fill the parent. Native component
|
||||||
|
// will handle positioning of elements, so it's not important to offset
|
||||||
|
// them correctly.
|
||||||
|
return React.Children.map(this.props.children, function(child) {
|
||||||
|
var newProps = {
|
||||||
|
...child.props,
|
||||||
|
style: [child.props.style, {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: undefined,
|
||||||
|
height: undefined,
|
||||||
|
}],
|
||||||
|
collapsable: false,
|
||||||
|
};
|
||||||
|
if (child.type && child.type.displayName && (child.type.displayName !== 'View')) {
|
||||||
|
console.warn('Each ViewPager child must be a <View>. Was ' + child.type.displayName);
|
||||||
|
}
|
||||||
|
return ReactElement.createElement(child.type, newProps);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_onPageScroll: function(event) {
|
||||||
|
if (this.props.onPageScroll) {
|
||||||
|
this.props.onPageScroll(event);
|
||||||
|
}
|
||||||
|
if (this.props.keyboardDismissMode === 'on-drag') {
|
||||||
|
dismissKeyboard();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_onPageSelected: function(event) {
|
||||||
|
var selectedPage = event.nativeEvent.position;
|
||||||
|
this.setState({
|
||||||
|
selectedPage,
|
||||||
|
});
|
||||||
|
if (this.props.onPageSelected) {
|
||||||
|
this.props.onPageSelected(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setPage: function(selectedPage) {
|
||||||
|
this.setState({
|
||||||
|
selectedPage,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<NativeAndroidViewPager
|
||||||
|
ref={VIEWPAGER_REF}
|
||||||
|
style={this.props.style}
|
||||||
|
selectedPage={this.state.selectedPage}
|
||||||
|
onPageScroll={this._onPageScroll}
|
||||||
|
onPageSelected={this._onPageSelected}
|
||||||
|
children={this._childrenWithOverridenStyle()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var NativeAndroidViewPager = createReactNativeComponentClass({
|
||||||
|
validAttributes: {
|
||||||
|
...ReactNativeViewAttributes.UIView,
|
||||||
|
...ViewPagerValidAttributes,
|
||||||
|
},
|
||||||
|
uiViewClassName: 'AndroidViewPager',
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ViewPagerAndroid;
|
||||||
13
Libraries/Components/ViewPager/ViewPagerAndroid.ios.js
Normal file
13
Libraries/Components/ViewPager/ViewPagerAndroid.ios.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
* @providesModule ViewPagerAndroid
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = require('UnimplementedView');
|
||||||
1
Libraries/react-native/react-native.js
vendored
1
Libraries/react-native/react-native.js
vendored
@@ -48,6 +48,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
|
|||||||
TouchableOpacity: require('TouchableOpacity'),
|
TouchableOpacity: require('TouchableOpacity'),
|
||||||
TouchableWithoutFeedback: require('TouchableWithoutFeedback'),
|
TouchableWithoutFeedback: require('TouchableWithoutFeedback'),
|
||||||
View: require('View'),
|
View: require('View'),
|
||||||
|
ViewPagerAndroid: require('ViewPagerAndroid'),
|
||||||
WebView: require('WebView'),
|
WebView: require('WebView'),
|
||||||
|
|
||||||
// APIs
|
// APIs
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import com.facebook.react.views.text.ReactVirtualTextViewManager;
|
|||||||
import com.facebook.react.views.textinput.ReactTextInputManager;
|
import com.facebook.react.views.textinput.ReactTextInputManager;
|
||||||
import com.facebook.react.views.toolbar.ReactToolbarManager;
|
import com.facebook.react.views.toolbar.ReactToolbarManager;
|
||||||
import com.facebook.react.views.view.ReactViewManager;
|
import com.facebook.react.views.view.ReactViewManager;
|
||||||
|
import com.facebook.react.views.viewpager.ReactViewPagerManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Package defining basic modules and view managers.
|
* Package defining basic modules and view managers.
|
||||||
@@ -68,6 +69,7 @@ public class MainReactPackage implements ReactPackage {
|
|||||||
new ReactTextViewManager(),
|
new ReactTextViewManager(),
|
||||||
new ReactToolbarManager(),
|
new ReactToolbarManager(),
|
||||||
new ReactViewManager(),
|
new ReactViewManager(),
|
||||||
|
new ReactViewPagerManager(),
|
||||||
new ReactVirtualTextViewManager());
|
new ReactVirtualTextViewManager());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.react.views.viewpager;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event emitted by {@link ReactViewPager} when user scrolls between pages (or when animating
|
||||||
|
* between pages).
|
||||||
|
*
|
||||||
|
* Additional data provided by this event:
|
||||||
|
* - position - index of first page from the left that is currently visible
|
||||||
|
* - offset - value from range [0,1) describing stage between page transitions. Value x means that
|
||||||
|
* (1 - x) fraction of the page at "position" index is visible, and x fraction of the next page
|
||||||
|
* is visible.
|
||||||
|
*/
|
||||||
|
/* package */ class PageScrollEvent extends Event<PageScrollEvent> {
|
||||||
|
|
||||||
|
public static final String EVENT_NAME = "topPageScroll";
|
||||||
|
|
||||||
|
private final int mPosition;
|
||||||
|
private final float mOffset;
|
||||||
|
|
||||||
|
protected PageScrollEvent(int viewTag, long timestampMs, int position, float offset) {
|
||||||
|
super(viewTag, timestampMs);
|
||||||
|
mPosition = position;
|
||||||
|
mOffset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return EVENT_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeEventData() {
|
||||||
|
WritableMap eventData = Arguments.createMap();
|
||||||
|
eventData.putInt("position", mPosition);
|
||||||
|
eventData.putDouble("offset", mOffset);
|
||||||
|
return eventData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.react.views.viewpager;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event emitted by {@link ReactViewPager} when selected page changes.
|
||||||
|
*
|
||||||
|
* Additional data provided by this event:
|
||||||
|
* - position - index of page that has been selected
|
||||||
|
*/
|
||||||
|
/* package */ class PageSelectedEvent extends Event<PageSelectedEvent> {
|
||||||
|
|
||||||
|
public static final String EVENT_NAME = "topPageSelected";
|
||||||
|
|
||||||
|
private final int mPosition;
|
||||||
|
|
||||||
|
protected PageSelectedEvent(int viewTag, long timestampMs, int position) {
|
||||||
|
super(viewTag, timestampMs);
|
||||||
|
mPosition = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return EVENT_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeEventData() {
|
||||||
|
WritableMap eventData = Arguments.createMap();
|
||||||
|
eventData.putInt("position", mPosition);
|
||||||
|
return eventData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.react.views.viewpager;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.support.v4.view.PagerAdapter;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReactContext;
|
||||||
|
import com.facebook.react.uimanager.UIManagerModule;
|
||||||
|
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||||
|
import com.facebook.react.uimanager.events.NativeGestureUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper view for {@link ViewPager}. It's forwarding calls to {@link ViewGroup#addView} to add
|
||||||
|
* views to custom {@link PagerAdapter} instance which is used by {@link NativeViewHierarchyManager}
|
||||||
|
* to add children nodes according to react views hierarchy.
|
||||||
|
*/
|
||||||
|
/* package */ class ReactViewPager extends ViewPager {
|
||||||
|
|
||||||
|
private class Adapter extends PagerAdapter {
|
||||||
|
|
||||||
|
private List<View> mViews = new ArrayList<>();
|
||||||
|
|
||||||
|
void addView(View child, int index) {
|
||||||
|
mViews.add(index, child);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
// This will prevent view pager from detaching views for pages that are not currently selected
|
||||||
|
// We need to do that since {@link ViewPager} relies on layout passes to position those views
|
||||||
|
// in a right way (also thanks to {@link ReactViewPagerManager#needsCustomLayoutForChildren}
|
||||||
|
// returning {@code true}). Currently we only call {@link View#measure} and
|
||||||
|
// {@link View#layout} after CSSLayout step.
|
||||||
|
|
||||||
|
// TODO(7323049): Remove this workaround once we figure out a way to re-layout some views on
|
||||||
|
// request
|
||||||
|
setOffscreenPageLimit(mViews.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return mViews.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object instantiateItem(ViewGroup container, int position) {
|
||||||
|
View view = mViews.get(position);
|
||||||
|
container.addView(view, 0, generateDefaultLayoutParams());
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||||
|
View view = mViews.get(position);
|
||||||
|
container.removeView(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isViewFromObject(View view, Object object) {
|
||||||
|
return view == object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PageChangeListener implements OnPageChangeListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||||
|
mEventDispatcher.dispatchEvent(
|
||||||
|
new PageScrollEvent(getId(), SystemClock.uptimeMillis(), position, positionOffset));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageSelected(int position) {
|
||||||
|
if (!mIsCurrentItemFromJs) {
|
||||||
|
mEventDispatcher.dispatchEvent(
|
||||||
|
new PageSelectedEvent(getId(), SystemClock.uptimeMillis(), position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageScrollStateChanged(int state) {
|
||||||
|
// don't send events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final EventDispatcher mEventDispatcher;
|
||||||
|
private boolean mIsCurrentItemFromJs;
|
||||||
|
|
||||||
|
public ReactViewPager(ReactContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
mEventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
|
||||||
|
mIsCurrentItemFromJs = false;
|
||||||
|
setOnPageChangeListener(new PageChangeListener());
|
||||||
|
setAdapter(new Adapter());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Adapter getAdapter() {
|
||||||
|
return (Adapter) super.getAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||||
|
if (super.onInterceptTouchEvent(ev)) {
|
||||||
|
NativeGestureUtil.notifyNativeGestureStarted(this, ev);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void addViewToAdapter(View child, int index) {
|
||||||
|
getAdapter().addView(child, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void setCurrentItemFromJs(int item) {
|
||||||
|
mIsCurrentItemFromJs = true;
|
||||||
|
setCurrentItem(item);
|
||||||
|
mIsCurrentItemFromJs = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.react.views.viewpager;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.facebook.react.common.MapBuilder;
|
||||||
|
import com.facebook.react.uimanager.ReactProp;
|
||||||
|
import com.facebook.react.uimanager.ThemedReactContext;
|
||||||
|
import com.facebook.react.uimanager.ViewGroupManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instance of {@link ViewManager} that provides native {@link ViewPager} view.
|
||||||
|
*/
|
||||||
|
public class ReactViewPagerManager extends ViewGroupManager<ReactViewPager> {
|
||||||
|
|
||||||
|
private static final String REACT_CLASS = "AndroidViewPager";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return REACT_CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ReactViewPager createViewInstance(ThemedReactContext 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
|
||||||
|
public boolean needsCustomLayoutForChildren() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map getExportedCustomDirectEventTypeConstants() {
|
||||||
|
return MapBuilder.of(
|
||||||
|
PageScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageScroll"),
|
||||||
|
PageSelectedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageSelected")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addView(ReactViewPager parent, View child, int index) {
|
||||||
|
parent.addViewToAdapter(child, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user