react-native-material-kit
Version:
Bringing Material Design to React Native
361 lines (317 loc) • 9.79 kB
JavaScript
//
// MDL style progress bar component.
//
// - @see [MDL Progress Bar](http://www.getmdl.io/components/index.html#loading-section/progress)
//
// - [Default progress](#Progress)
// - [Props](#props)
// - [Defaults](#defaults)
//
// - [Indeterminate progress](#IndeterminateProgress)
// - [Props](#IndeterminateProgressProps)
// - [Defaults](#IndeterminateProgressDefaults)
//
// Created by ywu on 15/8/7.
//
const React = require('react-native');
const {
Component,
View,
PropTypes,
Animated,
Easing,
} = React;
const MKColor = require('../MKColor');
//
// ## <section id='Progress'>Progress</section>
// The default `Progress` component, with a simplified buffering effect.
//
class Progress extends Component {
constructor(props) {
super(props);
this._progress = 0; // initial progress
this._buffer = 0; // initial buffering
this._totalLength = 0; // line length when progress is 100%
this._height = new Animated.Value(0); // height of the progress or line width
this._animatedLength = new Animated.Value(0);
this._animatedBufferLength = new Animated.Value(0);
}
componentWillMount() {
this.progress = this.props.progress;
this.buffer = this.props.buffer;
}
componentWillReceiveProps(nextProps) {
this.progress = nextProps.progress;
this.buffer = nextProps.buffer;
}
componentDidMount() {
requestAnimationFrame(() => {
if (!this.refs.bg) {
return;
}
this.refs.bg.measure((left, top, width, height) => {
this._totalLength = width;
this._height.setValue(height);
this._aniUpdateProgress(this.progress);
this._aniUpdateBuffer(this.buffer);
});
});
}
_aniUpdateProgress(theProgress) {
if (this._totalLength <= 0) {
return;
}
Animated.timing(this._animatedLength, {
toValue: theProgress * this._totalLength,
duration: this.props.progressAniDuration || 300,
easing: Easing.quad,
}).start();
}
_aniUpdateBuffer(buffer) {
if (this._totalLength <= 0) {
return;
}
Animated.timing(this._animatedBufferLength, {
toValue: buffer * this._totalLength,
duration: this.props.bufferAniDuration || 200,
}).start();
}
render() {
return (
<View // the background layer
ref="bg"
style={[Progress.defaultProps.style, this.props.style]}
>
<Animated.View // the buffering layer
ref="bufferLayer"
style={{
position: 'absolute',
backgroundColor: this.props.bufferColor,
width: this._animatedBufferLength,
height: this._height,
}}
/>
<Animated.View // the forefront layer showing progress
ref="progressLayer"
style={{
position: 'absolute',
backgroundColor: this.props.progressColor,
width: this._animatedLength,
height: this._height,
}}
/>
</View>
);
}
}
Object.defineProperty(Progress.prototype, 'progress', {
// Update the current progress.
// {`Number`} `value` the current progress, 0 ~ 1
set: function (value) {
this._progress = value;
this._aniUpdateProgress(value);
},
// Retrieve the current progress.
get: function () {
return this._progress;
},
enumerable: true,
});
Object.defineProperty(Progress.prototype, 'buffer', {
// Update the current percent of buffering.
// {`Number`} `buffer` current percent of buffering, 0 ~ 1
set: function (buffer) {
this._buffer = buffer;
this._aniUpdateBuffer(buffer);
},
// Retrieve the current progress.
get: function () {
return this._buffer;
},
enumerable: true,
});
// ## <section id='props'>Props</section>
Progress.propTypes = {
// [View Props](https://facebook.github.io/react-native/docs/view.html#props)...
...View.propTypes,
// Initial value of progress, Number: [0, 1.0]
progress: PropTypes.number,
// Color of the progress layer
progressColor: PropTypes.string,
// Color of the buffering layer
bufferColor: PropTypes.string,
// Duration of the progress animation, in milliseconds
progressAniDuration: PropTypes.number,
// Duration of the buffering animation, in milliseconds
bufferAniDuration: PropTypes.number,
};
// ## <section id='defaults'>Defaults</section>
Progress.defaultProps = {
progressColor: MKColor.Indigo,
bufferColor: `rgba(${MKColor.RGBIndigo},0.3)`,
style: {
backgroundColor: `rgba(${MKColor.RGBIndigo},0.3)`,
height: 4,
},
};
//
// ## <section id='IndeterminateProgress'>IndeterminateProgress</section>
// Progress Bar with Indeterminate Progress.
//
class IndeterminateProgress extends Component {
constructor(props) {
super(props);
this._totalLength = 0; // line length when progress is 100%
this._height = new Animated.Value(0); // height of the progress or line width
this._animatedBlock1 = { // state of the 1st progress block
left: new Animated.Value(0),
right: new Animated.Value(0),
};
this._animatedBlock2 = { // state of the 2nd progress block
left: new Animated.Value(0),
right: new Animated.Value(0),
};
this._aniUpdateProgress = this._aniUpdateProgress.bind(this);
}
componentDidMount() {
requestAnimationFrame(() => {
if (!this.refs.bg) {
return;
}
this.refs.bg.measure((left, top, width, height) => {
this._totalLength = width;
this._height.setValue(height);
this._aniUpdateProgress();
});
});
}
_aniUpdateProgress() {
if (this._totalLength <= 0) {
return;
}
this._animatedBlock1.left.setValue(0);
this._animatedBlock1.right.setValue(this._totalLength);
Animated.sequence([
Animated.parallel([
Animated.timing(this._animatedBlock1.left, {
toValue: this._totalLength * 0.25,
duration: this.props.progressAniDuration || 1250,
}),
Animated.timing(this._animatedBlock1.right, {
toValue: 0,
duration: this.props.progressAniDuration || 1250,
}),
]),
Animated.parallel([
Animated.timing(this._animatedBlock1.left, {
toValue: this._totalLength,
duration: this.props.progressAniDuration || 500,
easing: Easing.quad,
}),
this._getBlock2Ani(),
]),
]).start(({finished}) => finished && setImmediate(this._aniUpdateProgress));
}
_getBlock2Ani() {
this._animatedBlock2.left.setValue(0);
this._animatedBlock2.right.setValue(this._totalLength);
return Animated.sequence([
Animated.timing(this._animatedBlock2.right, {
toValue: this._totalLength * 0.75,
duration: this.props.progressAniDuration || 500,
}),
Animated.parallel([
Animated.timing(this._animatedBlock2.left, {
toValue: this._totalLength,
duration: this.props.progressAniDuration || 705,
easing: Easing.quad,
}),
Animated.timing(this._animatedBlock2.right, {
toValue: 0,
duration: this.props.progressAniDuration || 700,
easing: Easing.quad,
}),
]),
]);
}
render() {
return (
<View // the background layer
ref="bg"
style={[Progress.defaultProps.style, this.props.style]}
>
<Animated.View // the 1st animated progress block
style={{
backgroundColor: this.props.progressColor,
position: 'absolute',
left: this._animatedBlock1.left,
right: this._animatedBlock1.right,
height: this._height,
}}
/>
<Animated.View // the 2nd animated progress block
style={{
backgroundColor: this.props.progressColor,
position: 'absolute',
left: this._animatedBlock2.left,
right: this._animatedBlock2.right,
height: this._height,
}}
/>
</View>
);
}
}
// ## <section id='IndeterminateProgressProps'>Props</section>
IndeterminateProgress.propTypes = {
// [View Props](https://facebook.github.io/react-native/docs/view.html#props)...
...View.propTypes,
// Color of the progress layer
progressColor: PropTypes.string,
};
// ## <section id='IndeterminateProgressDefaults'>Defaults</section>
IndeterminateProgress.defaultProps = {
progressColor: Progress.defaultProps.progressColor,
style: Progress.defaultProps.style,
};
// --------------------------
// Builder
//
const {
Builder,
} = require('../builder');
//
// ## Progress builder
//
class ProgressBuilder extends Builder {
withIndeterminate(isIndeterminate) {
this.indeterminate = isIndeterminate;
return this;
}
build() {
const BuiltProgress = this.indeterminate ?
class extends IndeterminateProgress {} : class extends Progress {};
const defaults = (this.indeterminate ? IndeterminateProgress : Progress).defaultProps;
BuiltProgress.defaultProps = Object.assign({}, defaults, this.toProps());
return BuiltProgress;
}
}
// define builder method for each prop
ProgressBuilder.defineProps(Progress.propTypes);
ProgressBuilder.defineProps(IndeterminateProgress.propTypes);
// ----------
// ## <section id="builders">Built-in builders</section>
//
function progress() {
return new ProgressBuilder()
.withBackgroundColor(Progress.defaultProps.style.backgroundColor);
}
function indeterminateProgress() {
return progress().withIndeterminate(true);
}
// ## Public interface
module.exports = Progress;
Progress.Indeterminate = IndeterminateProgress;
Progress.Builder = ProgressBuilder;
Progress.progress = progress;
Progress.indeterminateProgress = indeterminateProgress;