diff --git a/Examples/UIExplorer/ListViewPagingExample.js b/Examples/UIExplorer/ListViewPagingExample.js
new file mode 100644
index 000000000..4818da998
--- /dev/null
+++ b/Examples/UIExplorer/ListViewPagingExample.js
@@ -0,0 +1,250 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule ListViewPagingExample
+ * @flow
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+ Image,
+ LayoutAnimation,
+ ListView,
+ ListViewDataSource,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} = React;
+
+var PAGE_SIZE = 4;
+var THUMB_URLS = ['https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851549_767334479959628_274486868_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851561_767334496626293_1958532586_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851579_767334503292959_179092627_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851589_767334513292958_1747022277_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851563_767334559959620_1193692107_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851593_767334566626286_1953955109_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851591_767334523292957_797560749_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851567_767334529959623_843148472_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851548_767334489959627_794462220_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851575_767334539959622_441598241_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851573_767334549959621_534583464_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851583_767334573292952_1519550680_n.png'];
+var NUM_SECTIONS = 100;
+var NUM_ROWS_PER_SECTION = 10;
+
+var Thumb = React.createClass({
+ getInitialState: function() {
+ return {thumbIndex: this._getThumbIdx(), dir: 'row'};
+ },
+ _getThumbIdx: function() {
+ return Math.floor(Math.random() * THUMB_URLS.length);
+ },
+ _onPressThumb: function() {
+ var config = layoutAnimationConfigs[this.state.thumbIndex % layoutAnimationConfigs.length];
+ LayoutAnimation.configureNext(config);
+ this.setState({
+ thumbIndex: this._getThumbIdx(),
+ dir: this.state.dir === 'row' ? 'column' : 'row',
+ });
+ },
+ render: function() {
+ return (
+
+
+
+
+
+ {this.state.dir === 'column' ?
+
+ Oooo, look at this new text! So awesome it may just be crazy.
+ Let me keep typing here so it wraps at least one line.
+ :
+
+ }
+
+
+ );
+ }
+});
+
+var ListViewPagingExample = React.createClass({
+ statics: {
+ title: ' - Paging',
+ description: 'Floating headers & layout animations.'
+ },
+
+ getInitialState: function() {
+ var getSectionData = (dataBlob, sectionID) => {
+ return dataBlob[sectionID];
+ };
+ var getRowData = (dataBlob, sectionID, rowID) => {
+ return dataBlob[rowID];
+ };
+
+ var dataSource = new ListViewDataSource({
+ getRowData: getRowData,
+ getSectionHeaderData: getSectionData,
+ rowHasChanged: (row1, row2) => row1 !== row2,
+ sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
+ });
+
+ var dataBlob = {};
+ var sectionIDs = [];
+ var rowIDs = [];
+ for (var ii = 0; ii < NUM_SECTIONS; ii++) {
+ var sectionName = 'Section ' + ii;
+ sectionIDs.push(sectionName);
+ dataBlob[sectionName] = sectionName;
+ rowIDs[ii] = [];
+
+ for (var jj = 0; jj < NUM_ROWS_PER_SECTION; jj++) {
+ var rowName = 'S' + ii + ', R' + jj;
+ rowIDs[ii].push(rowName);
+ dataBlob[rowName] = rowName;
+ }
+ }
+ return {
+ dataSource: dataSource.cloneWithRowsAndSections(dataBlob, sectionIDs, rowIDs),
+ headerPressCount: 0,
+ };
+ },
+
+ renderRow: function(rowData, sectionID, rowID) {
+ return ();
+ },
+
+ renderSectionHeader: function(sectionData, sectionID) {
+ return (
+
+
+ {sectionData}
+
+
+ );
+ },
+
+ renderHeader: function() {
+ var headerLikeText = this.state.headerPressCount % 2 ?
+ 1 Like :
+ null;
+ return (
+
+
+ {headerLikeText}
+
+
+ Table Header (click me)
+
+
+
+
+ );
+ },
+
+ renderFooter: function() {
+ return (
+
+ console.log('Footer!')} style={styles.text}>
+ Table Footer
+
+
+ );
+ },
+
+ render: function() {
+ return (
+ console.log({visibleRows, changedRows})}
+ renderHeader={this.renderHeader}
+ renderFooter={this.renderFooter}
+ renderSectionHeader={this.renderSectionHeader}
+ renderRow={this.renderRow}
+ initialListSize={10}
+ pageSize={PAGE_SIZE}
+ scrollRenderAheadDistance={2000}
+ />
+ );
+ },
+
+ _onPressHeader: function() {
+ var config = layoutAnimationConfigs[Math.floor(this.state.headerPressCount / 2) % layoutAnimationConfigs.length];
+ LayoutAnimation.configureNext(config);
+ this.setState({headerPressCount: this.state.headerPressCount + 1});
+ },
+
+});
+
+var styles = StyleSheet.create({
+ listview: {
+ backgroundColor: '#B0C4DE',
+ },
+ header: {
+ height: 40,
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: '#3B5998',
+ flexDirection: 'row',
+ },
+ text: {
+ color: 'white',
+ paddingHorizontal: 8,
+ },
+ rowText: {
+ color: '#888888',
+ },
+ thumbText: {
+ fontSize: 20,
+ color: '#888888',
+ },
+ buttonContents: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginHorizontal: 5,
+ marginVertical: 3,
+ padding: 5,
+ backgroundColor: '#EAEAEA',
+ borderRadius: 3,
+ paddingVertical: 10,
+ },
+ img: {
+ width: 64,
+ height: 64,
+ marginHorizontal: 10,
+ },
+ section: {
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: 'flex-start',
+ padding: 6,
+ backgroundColor: '#5890ff',
+ },
+});
+
+var animations = {
+ layout: {
+ spring: {
+ duration: 0.75,
+ create: {
+ duration: 0.3,
+ type: LayoutAnimation.Types.easeInEaseOut,
+ property: LayoutAnimation.Properties.opacity,
+ },
+ update: {
+ type: LayoutAnimation.Types.spring,
+ springDamping: 0.4,
+ },
+ },
+ easeInEaseOut: {
+ duration: 0.3,
+ create: {
+ type: LayoutAnimation.Types.easeInEaseOut,
+ property: LayoutAnimation.Properties.scaleXY,
+ },
+ update: {
+ delay: 0.1,
+ type: LayoutAnimation.Types.easeInEaseOut,
+ },
+ },
+ },
+};
+
+var layoutAnimationConfigs = [
+ animations.layout.spring,
+ animations.layout.easeInEaseOut,
+];
+
+module.exports = ListViewPagingExample;
diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js
index af31617e3..581f34b36 100644
--- a/Examples/UIExplorer/UIExplorerList.js
+++ b/Examples/UIExplorer/UIExplorerList.js
@@ -24,6 +24,7 @@ var EXAMPLES = [
require('./ExpandingTextExample'),
require('./ImageExample'),
require('./ListViewSimpleExample'),
+ require('./ListViewPagingExample'),
require('./NavigatorIOSExample'),
require('./StatusBarIOSExample'),
require('./PointerEventsExample'),
diff --git a/Libraries/Animation/LayoutAnimation.js b/Libraries/Animation/LayoutAnimation.js
new file mode 100644
index 000000000..5ce5648b3
--- /dev/null
+++ b/Libraries/Animation/LayoutAnimation.js
@@ -0,0 +1,89 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule LayoutAnimation
+ */
+'use strict';
+
+var PropTypes = require('ReactPropTypes');
+var RKUIManager = require('NativeModules').RKUIManager;
+
+var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker');
+var keyMirror = require('keyMirror');
+
+var Types = keyMirror({
+ spring: true,
+ linear: true,
+ easeInEaseOut: true,
+ easeIn: true,
+ easeOut: true,
+});
+
+var Properties = keyMirror({
+ opacity: true,
+ scaleXY: true,
+});
+
+var animChecker = createStrictShapeTypeChecker({
+ duration: PropTypes.number,
+ delay: PropTypes.number,
+ springDamping: PropTypes.number,
+ initialVelocity: PropTypes.number,
+ type: PropTypes.oneOf(
+ Object.keys(Types)
+ ),
+ property: PropTypes.oneOf( // Only applies to create/delete
+ Object.keys(Properties)
+ ),
+});
+
+var configChecker = createStrictShapeTypeChecker({
+ duration: PropTypes.number.isRequired,
+ create: animChecker,
+ update: animChecker,
+ delete: animChecker,
+});
+
+var LayoutAnimation = {
+ configureNext(config, onAnimationDidEnd, onError) {
+ configChecker({config}, 'config', 'LayoutAnimation.configureNext');
+ RKUIManager.configureNextLayoutAnimation(config, onAnimationDidEnd, onError);
+ },
+ create(duration, type, creationProp) {
+ return {
+ duration,
+ create: {
+ type,
+ property: creationProp,
+ },
+ update: {
+ type,
+ },
+ };
+ },
+ Types: Types,
+ Properties: Properties,
+ configChecker: configChecker,
+};
+
+LayoutAnimation.Presets = {
+ easeInEaseOut: LayoutAnimation.create(
+ 0.3, Types.easeInEaseOut, Properties.opacity
+ ),
+ linear: LayoutAnimation.create(
+ 0.5, Types.linear, Properties.opacity
+ ),
+ spring: {
+ duration: 0.7,
+ create: {
+ type: Types.linear,
+ property: Properties.opacity,
+ },
+ update: {
+ type: Types.spring,
+ springDamping: 0.4,
+ },
+ },
+};
+
+module.exports = LayoutAnimation;
diff --git a/Libraries/Animation/POPAnimationMixin.js b/Libraries/Animation/POPAnimationMixin.js
new file mode 100644
index 000000000..a3f4b7def
--- /dev/null
+++ b/Libraries/Animation/POPAnimationMixin.js
@@ -0,0 +1,250 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule POPAnimationMixin
+ * @flow
+ */
+'use strict';
+
+var POPAnimation = require('POPAnimation');
+if (!POPAnimation) {
+ // POP animation isn't available in the OSS fork - this is a temporary
+ // workaround to enable its availability to be determined at runtime.
+ module.exports = null;
+} else {
+
+var invariant = require('invariant');
+var warning = require('warning');
+
+var POPAnimationMixin = {
+ /**
+ * Different ways to interpolate between beginning and end states
+ * of properties during animation, such as spring, linear, and decay.
+ */
+ AnimationTypes: POPAnimation.Types,
+ AnimationProperties: POPAnimation.Properties,
+
+ getInitialState: function(): Object {
+ return {
+ _currentAnimationsByNodeHandle: {},
+ };
+ },
+
+ _ensureBookkeepingSetup: function(nodeHandle: any) {
+ if (!this.state._currentAnimationsByNodeHandle[nodeHandle]) {
+ this.state._currentAnimationsByNodeHandle[nodeHandle] = [];
+ }
+ },
+
+ /**
+ * Start animating the View with ref `refKey`.
+ *
+ * @param {key} refKey The key to reference the View to be animated.
+ *
+ * @param {number|Object} anim Either the identifier returned by
+ * POPAnimation.create* or an object defining all the necessary
+ * properties of the animation you wish to start (including type, matching
+ * an entry in AnimationTypes).
+ *
+ * @param {func} doneCallback A callback fired when the animation is done, and
+ * is passed a `finished` param that indicates whether the animation
+ * completely finished, or was interrupted.
+ */
+ startAnimation: function(
+ refKey: string,
+ anim: number | {type: number; property: number;},
+ doneCallback: (finished: bool) => void
+ ) {
+ var animID: number = 0;
+ if (typeof anim === 'number') {
+ animID = anim;
+ } else {
+ invariant(
+ anim instanceof Object &&
+ anim.type !== undefined &&
+ anim.property !== undefined,
+ 'Animation definitions must specify a type of animation and a ' +
+ 'property to animate.'
+ );
+ animID = POPAnimation.createAnimation(anim.type, anim);
+ }
+ invariant(
+ this.refs[refKey],
+ 'Invalid refKey ' + refKey + ' for anim:\n' + JSON.stringify(anim) +
+ '\nvalid refs: ' + JSON.stringify(Object.keys(this.refs))
+ );
+ var refNodeHandle = this.refs[refKey].getNodeHandle();
+ this.startAnimationWithNodeHandle(refNodeHandle, animID, doneCallback);
+ },
+
+ /**
+ * Starts an animation on a native node.
+ *
+ * @param {NodeHandle} nodeHandle Handle to underlying native node.
+ * @see `startAnimation`.
+ */
+ startAnimationWithNodeHandle: function(
+ nodeHandle: any,
+ animID: number,
+ doneCallback: (finished: bool) => void
+ ) {
+ this._ensureBookkeepingSetup(nodeHandle);
+ var animations = this.state._currentAnimationsByNodeHandle[nodeHandle];
+ var animIndex = animations.length;
+ animations.push(animID);
+ var cleanupWrapper = (finished) => {
+ if (!this.isMounted()) {
+ return;
+ }
+ animations[animIndex] = 0; // zero it out so we don't try to stop it
+ var allDone = true;
+ for (var ii = 0; ii < animations.length; ii++) {
+ if (animations[ii]) {
+ allDone = false;
+ break;
+ }
+ }
+ if (allDone) {
+ this.state._currentAnimationsByNodeHandle[nodeHandle] = undefined;
+ }
+ doneCallback && doneCallback(finished);
+ };
+ POPAnimation.addAnimation(nodeHandle, animID, cleanupWrapper);
+ },
+
+ /**
+ * Starts multiple animations with one shared callback that is called when all
+ * animations complete.
+ *
+ * @param {Array(Object} animations Array of objects defining all the
+ * animations to start, each with shape `{ref|nodeHandle, anim}`.
+ * @param {func} onSuccess A callback fired when all animations have returned,
+ * and is passed a finished arg that is true if all animations finished
+ * completely.
+ * @param {func} onFailure Not supported yet.
+ */
+ startAnimations: function(
+ animations: Array