Cross platform ActivityIndicator

Summary:
The API for `ActivityIndiatorIOS` and `ProgressBarAndroid` is very similar and can be merged in a cross platform component that displays a circular indeterminate loading indicator.

This deprecates `ActivityIndiatorIOS` and non-horizontal `ProgressBarAndroid` in favor of this new component.

**Test plan (required)**

Tested with the ActivityIndicator example in UIExplorer on android and ios. Also made sure that `ActivityIndicatorIOS` still works and displays a deprecation warning. Also tested that `ProgressBarAndroid` with `indeterminate == true` and `styleAttr != 'Horizontal'` displays a deprecation warning.
Closes https://github.com/facebook/react-native/pull/6897

Differential Revision: D3351607

Pulled By: dmmiller

fbshipit-source-id: b107ce99d966359003e8b3118cd97b90fa1d3d7d
This commit is contained in:
Janic Duplessis 2016-05-26 13:46:58 -07:00 committed by Facebook Github Bot 1
parent 98dd91825f
commit 26e8426248
14 changed files with 229 additions and 133 deletions

View File

@ -15,41 +15,38 @@
*/
'use strict';
var React = require('react');
var ReactNative = require('react-native');
var {
ActivityIndicatorIOS,
const React = require('react');
const ReactNative = require('react-native');
const {
ActivityIndicator,
StyleSheet,
View,
} = ReactNative;
var TimerMixin = require('react-timer-mixin');
const TimerMixin = require('react-timer-mixin');
var ToggleAnimatingActivityIndicator = React.createClass({
const ToggleAnimatingActivityIndicator = React.createClass({
mixins: [TimerMixin],
getInitialState: function() {
getInitialState() {
return {
animating: true,
};
},
setToggleTimeout: function() {
this.setTimeout(
() => {
this.setState({animating: !this.state.animating});
this.setToggleTimeout();
},
1200
);
setToggleTimeout() {
this.setTimeout(() => {
this.setState({animating: !this.state.animating});
this.setToggleTimeout();
}, 2000);
},
componentDidMount: function() {
componentDidMount() {
this.setToggleTimeout();
},
render: function() {
render() {
return (
<ActivityIndicatorIOS
<ActivityIndicator
animating={this.state.animating}
style={[styles.centering, {height: 80}]}
size="large"
@ -60,31 +57,31 @@ var ToggleAnimatingActivityIndicator = React.createClass({
exports.displayName = (undefined: ?string);
exports.framework = 'React';
exports.title = '<ActivityIndicatorIOS>';
exports.title = '<ActivityIndicator>';
exports.description = 'Animated loading indicators.';
exports.examples = [
{
title: 'Default (small, white)',
render: function() {
render() {
return (
<ActivityIndicatorIOS
style={[styles.centering, styles.gray, {height: 40}]}
<ActivityIndicator
style={[styles.centering, styles.gray]}
color="white"
/>
/>
);
}
},
{
title: 'Gray',
render: function() {
render() {
return (
<View>
<ActivityIndicatorIOS
style={[styles.centering, {height: 40}]}
<ActivityIndicator
style={[styles.centering]}
/>
<ActivityIndicatorIOS
style={[styles.centering, {backgroundColor: '#eeeeee', height: 40}]}
<ActivityIndicator
style={[styles.centering, {backgroundColor: '#eeeeee'}]}
/>
</View>
);
@ -92,23 +89,23 @@ exports.examples = [
},
{
title: 'Custom colors',
render: function() {
render() {
return (
<View style={styles.horizontal}>
<ActivityIndicatorIOS color="#0000ff" />
<ActivityIndicatorIOS color="#aa00aa" />
<ActivityIndicatorIOS color="#aa3300" />
<ActivityIndicatorIOS color="#00aa00" />
<ActivityIndicator color="#0000ff" />
<ActivityIndicator color="#aa00aa" />
<ActivityIndicator color="#aa3300" />
<ActivityIndicator color="#00aa00" />
</View>
);
}
},
{
title: 'Large',
render: function() {
render() {
return (
<ActivityIndicatorIOS
style={[styles.centering, styles.gray, {height: 80}]}
<ActivityIndicator
style={[styles.centering, styles.gray]}
color="white"
size="large"
/>
@ -117,22 +114,22 @@ exports.examples = [
},
{
title: 'Large, custom colors',
render: function() {
render() {
return (
<View style={styles.horizontal}>
<ActivityIndicatorIOS
<ActivityIndicator
size="large"
color="#0000ff"
/>
<ActivityIndicatorIOS
<ActivityIndicator
size="large"
color="#aa00aa"
/>
<ActivityIndicatorIOS
<ActivityIndicator
size="large"
color="#aa3300"
/>
<ActivityIndicatorIOS
<ActivityIndicator
size="large"
color="#00aa00"
/>
@ -142,16 +139,28 @@ exports.examples = [
},
{
title: 'Start/stop',
render: function(): ReactElement<any> {
render() {
return <ToggleAnimatingActivityIndicator />;
}
},
{
title: 'Custom size',
render() {
return (
<ActivityIndicator
style={[styles.centering, {transform: [{scale: 1.5}]}]}
size="large"
/>
);
}
},
];
var styles = StyleSheet.create({
const styles = StyleSheet.create({
centering: {
alignItems: 'center',
justifyContent: 'center',
padding: 8,
},
gray: {
backgroundColor: '#cccccc',
@ -159,5 +168,6 @@ var styles = StyleSheet.create({
horizontal: {
flexDirection: 'row',
justifyContent: 'space-around',
padding: 8,
},
});

View File

@ -49,41 +49,12 @@ var ProgressBarAndroidExample = React.createClass({
statics: {
title: '<ProgressBarAndroid>',
description: 'Visual indicator of progress of some operation. ' +
'Shows either a cyclic animation or a horizontal bar.',
description: 'Horizontal bar to show the progress of some operation.',
},
render: function() {
return (
<UIExplorerPage title="ProgressBar Examples">
<UIExplorerBlock title="Default ProgressBar">
<ProgressBar />
</UIExplorerBlock>
<UIExplorerBlock title="Normal ProgressBar">
<ProgressBar styleAttr="Normal" />
</UIExplorerBlock>
<UIExplorerBlock title="Small ProgressBar">
<ProgressBar styleAttr="Small" />
</UIExplorerBlock>
<UIExplorerBlock title="Large ProgressBar">
<ProgressBar styleAttr="Large" />
</UIExplorerBlock>
<UIExplorerBlock title="Inverse ProgressBar">
<ProgressBar styleAttr="Inverse" />
</UIExplorerBlock>
<UIExplorerBlock title="Small Inverse ProgressBar">
<ProgressBar styleAttr="SmallInverse" />
</UIExplorerBlock>
<UIExplorerBlock title="Large Inverse ProgressBar">
<ProgressBar styleAttr="LargeInverse" />
</UIExplorerBlock>
<UIExplorerBlock title="Horizontal Indeterminate ProgressBar">
<ProgressBar styleAttr="Horizontal" />
</UIExplorerBlock>
@ -92,10 +63,6 @@ var ProgressBarAndroidExample = React.createClass({
<MovingBar styleAttr="Horizontal" indeterminate={false} />
</UIExplorerBlock>
<UIExplorerBlock title="Large Red ProgressBar">
<ProgressBar styleAttr="Large" color="red" />
</UIExplorerBlock>
<UIExplorerBlock title="Horizontal Black Indeterminate ProgressBar">
<ProgressBar styleAttr="Horizontal" color="black" />
</UIExplorerBlock>

View File

@ -23,6 +23,10 @@ export type UIExplorerExample = {
};
var ComponentExamples: Array<UIExplorerExample> = [
{
key: 'ActivityIndicatorExample',
module: require('./ActivityIndicatorExample'),
},
{
key: 'SliderExample',
module: require('./SliderExample'),

View File

@ -29,8 +29,8 @@ export type UIExplorerExample = {
const ComponentExamples: Array<UIExplorerExample> = [
{
key: 'ActivityIndicatorIOSExample',
module: require('./ActivityIndicatorIOSExample'),
key: 'ActivityIndicatorExample',
module: require('./ActivityIndicatorExample'),
},
{
key: 'DatePickerIOSExample',

View File

@ -0,0 +1,127 @@
/**
* 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 ActivityIndicator
* @flow
*/
'use strict';
const ColorPropType = require('ColorPropType');
const NativeMethodsMixin = require('NativeMethodsMixin');
const Platform = require('Platform');
const PropTypes = require('ReactPropTypes');
const React = require('React');
const StyleSheet = require('StyleSheet');
const View = require('View');
const requireNativeComponent = require('requireNativeComponent');
const GRAY = '#999999';
/**
* Displays a circular loading indicator.
*/
const ActivityIndicator = React.createClass({
mixins: [NativeMethodsMixin],
propTypes: {
...View.propTypes,
/**
* Whether to show the indicator (true, the default) or hide it (false).
*/
animating: PropTypes.bool,
/**
* The foreground color of the spinner (default is gray).
*/
color: ColorPropType,
/**
* Size of the indicator. Small has a height of 20, large has a height of 36.
* Other sizes can be obtained using a scale transform.
*/
size: PropTypes.oneOf([
'small',
'large',
]),
/**
* Whether the indicator should hide when not animating (true by default).
*
* @platform ios
*/
hidesWhenStopped: PropTypes.bool,
},
getDefaultProps() {
return {
animating: true,
color: GRAY,
hidesWhenStopped: true,
size: 'small',
};
},
render() {
const {onLayout, style, ...props} = this.props;
let sizeStyle;
switch (props.size) {
case 'small':
sizeStyle = styles.sizeSmall;
break;
case 'large':
sizeStyle = styles.sizeLarge;
break;
}
return (
<View
onLayout={onLayout}
style={[styles.container, style]}>
<RCTActivityIndicator
{...props}
style={sizeStyle}
styleAttr="Normal"
indeterminate
/>
</View>
);
}
});
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
},
sizeSmall: {
width: 20,
height: 20,
},
sizeLarge: {
width: 36,
height: 36,
},
});
if (Platform.OS === 'ios') {
var RCTActivityIndicator = requireNativeComponent(
'RCTActivityIndicatorView',
ActivityIndicator,
{nativeOnly: {activityIndicatorViewStyle: true}},
);
} else if (Platform.OS === 'android') {
var RCTActivityIndicator = requireNativeComponent(
'AndroidProgressBar',
ActivityIndicator,
// Ignore props that are specific to non inderterminate ProgressBar.
{nativeOnly: {
indeterminate: true,
progress: true,
styleAttr: true,
}},
);
}
module.exports = ActivityIndicator;

View File

@ -7,27 +7,18 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ActivityIndicatorIOS
* @flow
*/
'use strict';
var ActivityIndicator = require('ActivityIndicator');
var NativeMethodsMixin = require('NativeMethodsMixin');
var PropTypes = require('ReactPropTypes');
var React = require('React');
var StyleSheet = require('StyleSheet');
var View = require('View');
var requireNativeComponent = require('requireNativeComponent');
var GRAY = '#999999';
type DefaultProps = {
animating: boolean;
color: string;
hidesWhenStopped: boolean;
size: 'small' | 'large';
};
/**
* Deprecated, use ActivityIndicator instead.
*/
var ActivityIndicatorIOS = React.createClass({
mixins: [NativeMethodsMixin],
@ -60,47 +51,13 @@ var ActivityIndicatorIOS = React.createClass({
onLayout: PropTypes.func,
},
getDefaultProps: function(): DefaultProps {
return {
animating: true,
color: GRAY,
hidesWhenStopped: true,
size: 'small',
};
componentDidMount: function() {
console.warn('ActivityIndicatorIOS is deprecated. Use ActivityIndicator instead.');
},
render: function() {
var {onLayout, style, ...props} = this.props;
var sizeStyle = (this.props.size === 'large') ? styles.sizeLarge : styles.sizeSmall;
return (
<View
onLayout={onLayout}
style={[styles.container, style]}>
<RCTActivityIndicatorView {...props} style={sizeStyle} />
</View>
);
return <ActivityIndicator {...this.props} />;
}
});
var styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
},
sizeSmall: {
width: 20,
height: 20,
},
sizeLarge: {
width: 36,
height: 36,
}
});
var RCTActivityIndicatorView = requireNativeComponent(
'RCTActivityIndicatorView',
ActivityIndicatorIOS,
{nativeOnly: {activityIndicatorViewStyle: true}},
);
module.exports = ActivityIndicatorIOS;

View File

@ -13,7 +13,6 @@
var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React');
var ReactPropTypes = require('ReactPropTypes');
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
var View = require('View');
var ColorPropType = require('ColorPropType');
@ -107,11 +106,24 @@ var ProgressBarAndroid = React.createClass({
mixins: [NativeMethodsMixin],
componentDidMount: function() {
if (this.props.indeterminate && this.props.styleAttr !== 'Horizontal') {
console.warn(
'Circular indeterminate `ProgressBarAndroid`' +
'is deprecated. Use `ActivityIndicator` instead.'
);
}
},
render: function() {
return <AndroidProgressBar {...this.props} />;
},
});
var AndroidProgressBar = requireNativeComponent('AndroidProgressBar', ProgressBarAndroid);
var AndroidProgressBar = requireNativeComponent(
'AndroidProgressBar',
ProgressBarAndroid,
{nativeOnly: {animating: true}},
);
module.exports = ProgressBarAndroid;

View File

@ -27,6 +27,7 @@ if (__DEV__) {
// Export React, plus some native additions.
const ReactNative = {
// Components
get ActivityIndicator() { return require('ActivityIndicator'); },
get ActivityIndicatorIOS() { return require('ActivityIndicatorIOS'); },
get ART() { return require('ReactNativeART'); },
get DatePickerIOS() { return require('DatePickerIOS'); },

View File

@ -25,6 +25,7 @@
//
var ReactNative = Object.assign(Object.create(require('ReactNative')), {
// Components
ActivityIndicator: require('ActivityIndicator'),
ActivityIndicatorIOS: require('ActivityIndicatorIOS'),
ART: require('ReactNativeART'),
DatePickerIOS: require('DatePickerIOS'),

View File

@ -7,6 +7,7 @@ import javax.annotation.Nullable;
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
@ -22,6 +23,7 @@ import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
private @Nullable Integer mColor;
private boolean mIndeterminate = true;
private boolean mAnimating = true;
private double mProgress;
private @Nullable ProgressBar mProgressBar;
@ -53,6 +55,10 @@ import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
mProgress = progress;
}
public void setAnimating(boolean animating) {
mAnimating = animating;
}
public void apply() {
if (mProgressBar == null) {
throw new JSApplicationIllegalArgumentException("setStyle() not called");
@ -61,6 +67,11 @@ import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
mProgressBar.setIndeterminate(mIndeterminate);
setColor(mProgressBar);
mProgressBar.setProgress((int) (mProgress * MAX_PROGRESS));
if (mAnimating) {
mProgressBar.setVisibility(View.VISIBLE);
} else {
mProgressBar.setVisibility(View.GONE);
}
}
private void setColor(ProgressBar progressBar) {

View File

@ -32,6 +32,7 @@ public class ReactProgressBarViewManager extends
/* package */ static final String PROP_STYLE = "styleAttr";
/* package */ static final String PROP_INDETERMINATE = "indeterminate";
/* package */ static final String PROP_PROGRESS = "progress";
/* package */ static final String PROP_ANIMATING = "animating";
/* package */ static final String REACT_CLASS = "AndroidProgressBar";
/* package */ static final String DEFAULT_STYLE = "Normal";
@ -79,6 +80,11 @@ public class ReactProgressBarViewManager extends
view.setProgress(progress);
}
@ReactProp(name = PROP_ANIMATING)
public void setAnimating(ProgressBarContainerView view, boolean animating) {
view.setAnimating(animating);
}
@Override
public ProgressBarShadowNode createShadowNodeInstance() {
return new ProgressBarShadowNode();

View File

@ -4,7 +4,7 @@ title: Building React Native from source
layout: docs
category: Guides (Android)
permalink: docs/android-building-from-source.html
next: activityindicatorios
next: activityindicator
---
You will need to build React Native from source if you want to work on a new feature/bug fix, try out the latest features which are not released yet, or maintain your own fork with patches that cannot be merged to the core.

View File

@ -250,7 +250,7 @@ function renderStyle(filepath) {
}
const components = [
'../Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js',
'../Libraries/Components/ActivityIndicator/ActivityIndicator.js',
'../Libraries/Components/DatePicker/DatePickerIOS.ios.js',
'../Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js',
'../Libraries/Image/Image.ios.js',