Et si on faisait le Flutter Clock Contest en React Native

Horloge de Dominik Roszkowski

Création du cadre de l’horloge

Commençons par créer notre page clock.js dans lequel nous allons créer la cadre de l’horloge. Celui-ci sera composé de 3 cercles de tailles différentes ayant le même centre. Le plus grand aura une ombre extérieur, le petit contiendra l’horloge en tant que telle, et le cercle du centre servira à porter l’ombre intérieure permettant l’effet de relief du cadrant.

import React from ‘react’;import { StyleSheet, View, Text } from ‘react-native’;export default class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
componentDidMount() {
}
render() {
return (
<View style={styles.container}>
<Text>Et si on construisait une horloge ?</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: ‘center’,
justifyContent: ‘center’
}
});
import React from ‘react’;
import { StyleSheet, View, Dimensions } from ‘react-native’;
export default class Clock extends React.Component {
[…]
render() {
let frameSize = Dimensions.get('screen').width > Dimensions.get('screen').height ? Dimensions.get('screen').height : Dimensions.get('screen').width;
frameSize = frameSize - 40;
const frameWidth = 20;
const clockSize = frameSize-2*frameWidth-36;
return (
<View style={styles.container}>
<View style={[styles.shadow, styles.frame, { width: frameSize, height: frameSize, borderRadius: frameSize / 2 }]}>
<View style={[styles.innerShadow, styles.innerFrame, { width: frameSize, height: frameSize, borderRadius: frameSize / 2, borderWidth: frameWidth }]}>
<View style={[{ width: clockSize, height: clockSize, borderRadius: clockSize / 2, justifyContent: 'center', alignItems: 'center' }]}>
</View>
</View>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
[...]
shadow: {
shadowColor: “#000”,
shadowOffset: {
width: 0,
height: 5,
},
shadowOpacity: 0.27,
shadowRadius: 4.65,
elevation: 6
},
frame: {
backgroundColor: '#fff',
justifyContent: 'center',
alignItems: 'center'
},
innerShadow: {
shadowColor: “#000”,
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.23,
shadowRadius: 2.62,
elevation: 4
},
innerFrame: {
overflow: 'hidden',
borderColor: '#fff',
justifyContent: 'center',
alignItems: 'center'
}
});
Le cadre de notre horloge

Prise en charge du passage Light Mode / Dark Mode

Le concours demandait de prendre en charge le Light mode et le Dark mode. Il serait trop lourd de traiter ici de la prise en charge de la valeur système du mode en cours, ainsi que de la mise en place d’un thème pour l’app. Nous allons donc simplement simuler le changement de mode via un booléen dans le state, que nous nommerons isLightMode (que nous initierons à true pour être en Light Mode par défaut). Nous créerons 2 objets darkModeTheme et lightModeTheme pour stocker les valeurs des couleurs en fonction du mode choisis.

import React from ‘react’;
import { StyleSheet, View, Dimensions, TouchableOpacity, Text} from ‘react-native’;
const lightModeTheme = {
background:’#E0E0E0',
shadow:'#9E9E9E',
frame:’#E0E0E0',
text:'#123456'
}
const darkModeTheme = {
background:’#3C4043',
shadow:'#212121',
frame:’#3C4043',
text:'#fff'
}
export default class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
isLightMode:true
}
}
[...]
render() {
const colorTheme = this.state.isLightMode ? lightModeTheme : darkModeTheme;
const colorName = this.state.isLightMode ? 'Light Mode' : 'Dark Mode';
[...]
return (
<View style={[styles.container, {backgroundColor:colorTheme.background}]}>
<View style={[styles.shadow, styles.frame, { shadowColor: colorTheme.shadow, backgroundColor: colorTheme.frame, width: frameSize, height: frameSize, borderRadius: frameSize / 2 }]}>
<View style={[styles.innerShadow, styles.innerFrame, { shadowColor: colorTheme.shadow, width: frameSize, height: frameSize, borderRadius: frameSize / 2, borderWidth: frameWidth, borderColor: theme.frame }]}>
<View style={[{ width: clockSize, height: clockSize, borderRadius: clockSize / 2, justifyContent: ‘center’, alignItems: ‘center’ }]}>
</View>
</View>
</View>
<TouchableOpacity style={styles.modeButton} onPress={() => this.setState({isLightMode:!this.state.isLightMode})}>
<Text style={{color:colorTheme.text}}>{colorName}</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: ‘center’,
justifyContent: ‘center’
},
shadow: {
shadowOffset: {
width: 0,
height: 5,
},
shadowOpacity: 1,
shadowRadius: 4.65,
elevation: 6
},
frame: {
justifyContent: ‘center’,
alignItems: ‘center’
},
innerShadow: {
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 1,
shadowRadius: 2.62,
elevation: 4
},
innerFrame: {
overflow: ‘hidden’,
justifyContent: ‘center’,
alignItems: ‘center’
},
modeButton: {
position:'absolute',
top:30,
right:30
}
});
Mise en place d’un switch entre Light Mode et Dark Mode

Création de l’horloge : marquage des heures

Jusque là nous étions dans la partie facile. Le but est maintenant de créer les repères des heures. Nous avons donc 12 repères à placer sur le cercle de notre horloge. Pour trouver la position de ces 12 points nous allons faire appel à nos souvenirs de géométrie pour calculer la position d’un point sur un cercle dans une repère à deux dimensions.

import React from ‘react’;import { StyleSheet, View, Dimensions, Text, TouchableOpacity } from ‘react-native’;const lightModeTheme = {
[...]
tick:’#212121'
}
const darkModeTheme = {
[...]
tick:’#212121'
}
export default class Clock extends React.Component {
[...]
render() {
[...]
//Rayon de l'horloge
const clockRadius = (clockSize) / 2;
//Centre de l'horloge
const clockCenter = { x: clockRadius, y: clockRadius };
//Initialisation de la vue contenant les points représentant chaque heure
let hoursView = [];
for (let i = 0; i < 12; i++) {
const clockAngle = Math.PI + i * Math.PI / 6;
hoursView.push(<View
style={{
position: 'absolute',
top: this.getPointAbscissa(clockAngle, clockRadius, clockCenter),
left: this.getPointOrdinate(clockAngle, clockRadius, clockCenter)-2}}>
<View style={{ width: 4, height: 4, borderRadius:2, backgroundColor: colorTheme.tick }} />
</View>)
}

return (
[...]
<View style={[{ width: clockSize, height: clockSize, borderRadius: clockSize / 2, justifyContent: 'center', alignItems: 'center' }]}>
{hoursView}
</View>
[...]
);
}
getPointAbscissa = (angle, radius, center) => {
return center.x + radius * Math.cos(angle);
}
getPointOrdinate = (angle, radius, center) => {
return center.y + radius * Math.sin(angle);
}
}
Le marquage des heures de notre horloge
<View 
style={{
position: ‘absolute’,
top: this.getPointAbscissa(clockAngle, clockRadius, clockCenter),
left: this.getPointOrdinate(clockAngle, clockRadius, clockCenter)-2
}}>
<View style={{ width: 4, height: 30, backgroundColor: colorTheme.tick }} />
</View>
<View
style={{
transform: [
{ rotate: (-i * (Math.PI / 6)) + ‘rad’ }
],
position: ‘absolute’,
top: this.getPointAbscissa(clockAngle, clockRadius, clockCenter),
left: this.getPointOrdinate(clockAngle, clockRadius, clockCenter) — 2
}}>
<View style={{ width: 4, height: 30, backgroundColor: colorTheme.tick }} />
</View>
render() {
[…]
const clockSize = frameSize — 2 * frameWidth — 56;
[…]
//Initialisation de la vue contenant les points représentant chaque heure
let hoursView = [];
for (let i = 0; i < 12; i++) {
const clockAngle = Math.PI + i * Math.PI / 6;
//Largeur du trait. Si l’heure est un multiple de 3 on grossit le trait
const widthOfHours = (i % 3 === 0) ? 6 : 4;
hoursView.push(<View
style={{
transform: [
{ rotate: (-i * (Math.PI / 6)) + ‘rad’ }
],
position: ‘absolute’,
top: this.getPointAbscissa(clockAngle, clockRadius, clockCenter) — 15,
left: this.getPointOrdinate(clockAngle, clockRadius, clockCenter) — (widthOfHours / 2)
}}>
<View style={{ width: widthOfHours, height: 30, backgroundColor: colorTheme.tick }} />
</View>)
}
return (
[…]
);
}

Création de l’horloge : les aiguilles

Il nous faut maintenant créer les éléments essentiels d’une horloge, à savoir les 3 aiguilles.

<View style={[{ width: clockSize, height: clockSize, borderRadius: clockSize / 2, justifyContent: ‘center’, alignItems: ‘center’ }]}
{hoursView}
<View style={{
transform: [
{ rotate: 10 * Math.PI / 6 + ‘rad’ },
{ scaleY: 0.5 },
{ translateY: -clockSize / 4 }
],
position: ‘absolute’,
left: (clockSize / 2) — hourPadding,
top: clockSize / 4,
width: hourWidth,
height: clockSize / 2,
backgroundColor: colorTheme.hour
}} />
<View style={{
transform: [
{ rotate: 10 * Math.PI / 30 + ‘rad’ },
{ scaleY: 0.7 },
{ translateY: -clockSize / 4 }
],
position: ‘absolute’,
left: (clockSize / 2) — minutePadding,
top: clockSize / 4,
width: minuteWidth,
height: clockSize / 2,
backgroundColor: colorTheme.minute
}} />
<View style={{
transform: [
{ rotate: 30 * Math.PI / 30 + ‘rad’ },
{ scaleY: 1 },
{ translateY: -20 }
],
position: ‘absolute’,
left: (clockSize / 2) — secondePadding,
top: clockSize / 6,
width: secondeWidth,
height: clockSize * 2 / 3,
backgroundColor: colorTheme.second
}} />
</View>
</View>
export default class Clock extends React.Component{
constructor(props) {
super(props);
this.state = {
isLightMode: true,
hours: 10,
minutes: 10,
seconds: 30
}
}
componentDidMount() {
this.getTime();
}
render() {
[...]
//Valeurs des variables des heures, minutes et secondes
const { hours, minutes, seconds } = this.state;
return (
<View style={[styles.container, { backgroundColor: colorTheme.background }]}>
<View style={[styles.shadow, styles.frame, { shadowColor: colorTheme.shadow, backgroundColor: colorTheme.frame, width: frameSize, height: frameSize, borderRadius: frameSize / 2 }]}>
<View style={[styles.innerShadow, styles.innerFrame, { shadowColor: colorTheme.shadow, width: frameSize, height: frameSize, borderRadius: frameSize / 2, borderWidth: frameWidth, borderColor: colorTheme.frame }]}>
<View style={[{ width: clockSize, height: clockSize, borderRadius: clockSize / 2, justifyContent: ‘center’, alignItems: ‘center’ }]}>
{hoursView}
<View style={{
transform: [
{ rotate: hours * Math.PI / 6 + ‘rad’ },
{ scaleY: 0.5 },
{ translateY: -clockSize / 4 }
],
position: ‘absolute’,
left: (clockSize / 2) — hourPadding,
top: clockSize / 4,
width: hourWidth,
height: clockSize / 2,
backgroundColor: colorTheme.hour
}} />
<View style={{
transform: [
{ rotate: minutes * Math.PI / 30 + ‘rad’ },
{ scaleY: 0.7 },
{ translateY: -clockSize / 4 }
],
position: ‘absolute’,
left: (clockSize / 2) — minutePadding,
top: clockSize / 4,
width: minuteWidth,
height: clockSize / 2,
backgroundColor: colorTheme.minute
}} />
<View style={{
transform: [
{ rotate: seconds * Math.PI / 30 + ‘rad’ },
{ scaleY: 1 },
{ translateY: -20 }
],
position: ‘absolute’,
left: (clockSize / 2) — secondePadding,
top: clockSize / 6,
width: secondeWidth,
height: clockSize * 2 / 3,
backgroundColor: colorTheme.second,
alignItems: ‘center’,
justifyContent: ‘flex-end’
}}>
<View style={[styles.counterweight, {backgroundColor: colorTheme.second }]} />
</View>
<View style={[styles.pivot, { backgroundColor: colorTheme.second }]} />
</View>
</View>
</View>
<TouchableOpacity style={styles.modeButton} onPress={() => this.setState({ isLightMode: !this.state.isLightMode })}>
<Text style={{ color: colorTheme.text }}>{colorName}</Text>
</TouchableOpacity>
</View>
);
}

getPointAbscissa = (angle, radius, center) => {
return center.x + radius * Math.cos(angle);
}
getPointOrdinate = (angle, radius, center) => {
return center.y + radius * Math.sin(angle);
}
getTime = () => {
const now = new Date();
this.setState({ hours: now.getHours(), minutes: now.getMinutes(), seconds: now.getSeconds() });
setTimeout(() => {
this.getTime();
}, 1000);
}
}
const styles = StyleSheet.create({
[...]
pivot: {
width: 12,
height: 12,
borderRadius: 6
},
counterweight: {
marginBottom: 30,
width: 20,
height: 20,
borderRadius: 10
}
});
Mon horloge du Flutter Clock Contest en React Native

--

--

Show Runner de projets mobiles. Développeur React Native et passionné par les challenges du monde mobile.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Axel de Sainte Marie

Axel de Sainte Marie

18 Followers

Show Runner de projets mobiles. Développeur React Native et passionné par les challenges du monde mobile.