Skip to content

Linear progress bar. #88

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
2 changes: 2 additions & 0 deletions example/src/ExampleList.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import CheckboxExample from './CheckboxExample';
import DividerExample from './DividerExample';
import GridViewExample from './GridViewExample';
import PaperExample from './PaperExample';
import ProgressExample from './ProgressExample';
import RippleExample from './RippleExample';
import RadioButtonExample from './RadioButtonExample';
import TextExample from './TextExample';
Expand All @@ -31,6 +32,7 @@ export const examples = {
divider: DividerExample,
grid: GridViewExample,
paper: PaperExample,
progress: ProgressExample,
ripple: RippleExample,
radio: RadioButtonExample,
text: TextExample,
Expand Down
82 changes: 82 additions & 0 deletions example/src/ProgressExample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from 'react';
import {
View,
StyleSheet,
} from 'react-native';
import {
Progress,
Headline,
Subheading,
} from 'react-native-paper';

class ProgressExample extends React.Component {
state = {
progress: 0,
visible: true,
}

componentDidMount() {
this._interval = setInterval(() => {
this.setState(state => {
if (state.progress >= 1) {
if (state.visible) {
return { visible: false };
} else {
return { progress: 0 };
}
} else {
if (state.visible) {
return { progress: state.progress + 0.3 };
} else {
if (state.progress === 0) {
return { visible: true };
} else {
return { progress: 0 };
}
}
}
});
}, 1000);
}

componentWillUnmount() {
clearInterval(this._interval);
}

render() {
return (
<View style={styles.container}>
<Headline>Linear</Headline>

<Subheading>Determinate</Subheading>
<Progress.Linear
progress={this.state.progress}
visible={this.state.visible}
style={styles.bar}
/>

<Subheading>Indeterminate</Subheading>
<Progress.Linear
indeterminate
visible={this.state.visible}
style={styles.bar}
/>

</View>
);
}
}

ProgressExample.title = 'Progress';

const styles = StyleSheet.create({
container: {
padding: 16,
},

bar: {
marginVertical: 8,
},
});

export default ProgressExample;
221 changes: 221 additions & 0 deletions src/components/Progress/ProgressLinear.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/* @flow */

import color from 'color';
import React, {
PureComponent,
PropTypes,
} from 'react';
import {
StyleSheet,
Animated,
View,
} from 'react-native';
import withTheme from '../../core/withTheme';
import type { Theme } from '../../types/Theme';

type Props = {
color: string;
indeterminate: boolean;
progress: number;
visible: boolean;
style?: any;
theme: Theme;
}

type State = {
progress: Animated.Value;
visibility: Animated.Value;
indeterminate: Animated.Value;
width: number;
}

/**
* Progress is visual indication of loading content.
*
* **Usage:**
* ```
* const MyComponent = () => (
* <Progress.Linear progress={0.2} />
* );
* ```
*/
class Linear extends PureComponent<any, Props, State> {
static propTypes = {
/**
* Color of complete bar.
*/
color: PropTypes.string,
/**
* Used for visualizing an unspecified wait time.
*/
indeterminate: PropTypes.bool,
/**
* Progress of loaded content. Rounded to match 0-1 range.
*/
progress: PropTypes.number,
/**
* Determines if component is visible.
*/
visible: PropTypes.bool,
style: View.propTypes.style,
theme: PropTypes.object.isRequired,
};

static defaultProps = {
indeterminate: false,
progress: 0,
visible: true,
};

state: State = {
progress: new Animated.Value(0),
visibility: new Animated.Value(0),
indeterminate: new Animated.Value(0),
width: 0,
};

componentDidMount() {
if (this.props.visible) {
this._updateVisibility(true);
}
if (this.props.indeterminate) {
this._updateIndeterminate(true);
}
}

componentDidUpdate(prevProps: Props) {
const hasIndeterminateChanged = prevProps.indeterminate !== this.props.indeterminate;
const hasProgressChanged = prevProps.progress !== this.props.progress;
const hasVisiblityChanged = prevProps.visible !== this.props.visible;

if (hasVisiblityChanged) {
this._updateVisibility(this.props.visible);
}

if (hasIndeterminateChanged) {
this._updateIndeterminate(this.props.indeterminate);
}

if (hasProgressChanged) {
this._updateProgress(this.props.progress);
}
}

_updateIndeterminate = (indeterminate: boolean) => {
if (indeterminate) {
Animated.parallel([
Animated.sequence([
Animated.timing(this.state.progress, {
toValue: 0.8,
duration: 600,
isInteraction: false,
}),
Animated.timing(this.state.progress, {
toValue: 0.2,
duration: 600,
isInteraction: false,
}),
]),
Animated.timing(this.state.indeterminate, {
toValue: 1,
duration: 1200,
isInteraction: false,
}),
]).start(animationState => {
if (animationState.finished) {
this.state.indeterminate.setValue(-1);
this._updateIndeterminate(true);
}
});
} else {
Animated.timing(this.state.indeterminate, {
toValue: 0,
duration: 600,
isInteraction: false,
}).start();
}
};

_updateProgress = (progress: number) => {
Animated.spring(this.state.progress, {
toValue: progress,
bounciness: 0,
isInteraction: false,
}).start();
};

_updateVisibility = (visible: boolean) => {
Animated.timing(this.state.visibility, {
toValue: visible ? 1 : 0,
isInteraction: false,
}).start();
};

_handleLayout = (event) => {
const { width } = event.nativeEvent.layout;
this.setState({ width });
}

render() {
const {
style,
theme,
...restProps
} = this.props;
const { width } = this.state;

const progressColor = this.props.color || theme.colors.accent;
const backgroundColor = color(progressColor).alpha(0.32).rgbaString();
const scaleX = Animated.diffClamp(this.state.progress, 0, 1);
const opacity = this.state.visibility.interpolate({
inputRange: [ 0, 0.1, 1 ],
outputRange: [ 0, 1, 1 ],
});
const translateX = Animated.add(
Animated.multiply(this.state.indeterminate, width),
scaleX.interpolate({
inputRange: [ 0, 1 ],
outputRange: [ -width / 2, 0 ],
})
);
const barStyle = {
backgroundColor,
opacity,
transform: [ { scaleY: this.state.visibility } ],
};
const filleStyle = {
backgroundColor: progressColor,
transform: [
{ translateX },
{ scaleX },
],
};

return (
<Animated.View
{...restProps}
onLayout={this._handleLayout}
style={[
styles.container,
barStyle,
style,
]}
>
<Animated.View style={[ styles.fill, filleStyle ]} />
</Animated.View>
);
}
}

const styles = StyleSheet.create({
container: {
overflow: 'hidden',
height: 4,
},

fill: {
flex: 1,
},
});

export default withTheme(Linear);
5 changes: 5 additions & 0 deletions src/components/Progress/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Linear from './ProgressLinear';

export default {
Linear,
};
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export { default as Button } from './components/Button';
export { default as Card } from './components/Card';
export { default as Checkbox } from './components/Checkbox';
export { default as Paper } from './components/Paper';
export { default as Progress } from './components/Progress';
export { default as RadioButton } from './components/RadioButton';
export { default as TouchableRipple } from './components/TouchableRipple';

Expand All @@ -25,4 +26,3 @@ export { default as Text } from './components/Typography/Text';
export { default as Divider } from './components/Divider';
export { default as Drawer } from './components/Drawer';
export { default as GridView } from './components/GridView';