Reagisci Replica nativa di SoundCloud

Animazione + Navigazione

Foto di Rachit Tank su Unsplash

Ogni giorno codice, c'è un'app che mi accompagna sempre. Il mio complice. Il mio amante. Ultimamente, mi ritrovo ad analizzare ogni dettaglio di ogni app che incontro. Più di ogni altra cosa, la navigazione e l'animazione hanno suscitato il mio interesse. Quindi non dovrebbe essere una grande sorpresa che il mio sguardo sia caduto sul mio più vicino e caro alleato delle trincee - SoundCloud.

Mi piace molto SoundCloud. Una caratteristica specifica che adoro è lo scorrimento dell'immagine sul cellulare. Trovo che ciò fornisca all'artista un sostegno aggiuntivo per la storia raccontata. Questo è un tale vantaggio e, se fatto bene, puoi dirlo.

Con gli occhi chiari e la coda folta mi misi al lavoro. Ho deciso di creare una replica SoundCloud ed estrarre alcuni dati dalla loro API.

'Oh, mia dolce bambina estiva.'

Sembra che alcuni mesi fa SoundCloud abbia smesso di regalare chiavi API. All'inizio ero sicuro di averne solo "preso in prestito".

Frustrantemente, ho capito che non sarebbe stato di grande aiuto neanche per motivi di autenticazione. Imperterrito, ho spinto in avanti. Ho ricordato a me stesso cosa mi ha portato su questo percorso in primo luogo.

Animazione + Navigazione

Andiamo a lavorare!

Innanzitutto impostiamo il nostro ambiente. Se stai utilizzando un Mac, assicurati di aver installato Homebrew, Yarn e Xcode o Android Studio. Se sei su Windows, assicurati di aver installato Yarn e l'installazione di Android Studio. Ho esaminato come farlo nel mio ultimo articolo.

Facciamo installare Rea-native.

npm install -g reazioni-nativo-cli

Ora per creare il nostro progetto. Chiamiamolo SoundCloud.

init SoundCloud di reazione nativo

I prossimi comandi saranno il cd nel nostro progetto, l'installazione di alcune dipendenze e l'avvio del simulatore iOS o dell'emulatore Android.

cd SoundCloud
il filato aggiunge reattanza alla navigazione
il filato aggiunge reagire icone native native
collegamento di reazione nativo

Userò l'iPhone X come mio simulatore:

reazioni-nativo run-ios --simulator = 'iPhone X'

Per Android:

reatt-native run-android

NAVIGAZIONE

Apri il tuo editor preferito. Prima di apportare modifiche a App.js, creiamo una nuova cartella denominata "schermate". All'interno delle schermate creiamo due file:

(1) Login.js

(2) Home.js

All'interno di Login.js inserisci il seguente codice:

Per Home.js:

Ora regola App.js in modo che assomigli al codice seguente:

In Login.js e Home.js importiamo semplicemente alcuni componenti reagenti nativi, creiamo il componente LoginScreen, il componente HomeScreen e inseriamo un pulsante. Diamo un nome al pulsante e implementiamo la sintassi di reazione-navigazione per "passare" a un'altra schermata.

Di nuovo in App.js importiamo i nostri componenti e reagiamo alla navigazione. Quindi creiamo un SwitchNavigator in cui denominiamo Switch. All'interno di Switch assegniamo il nome "Login" al nostro componente LoginScreen in Login.js e facciamo lo stesso per il nostro componente HomeScreen in Home.js. Dal momento che abbiamo chiamato prima Login, diventerà la "rotta iniziale" per la nostra app. Un modo migliore per farlo in modo più dichiarativo è aggiungere "initialRouteName" al nostro SwitchNavigator.

Dopo aver salvato il progetto, sarai in grado di navigare tra la schermata di accesso e la schermata principale. Freddo! Ora finiamo Login.js in modo da non doverci più preoccupare.

Per prima cosa importiamo icone reagenti native-vector. Nel nostro metodo di rendering inseriamo la nostra icona sopra il nostro pulsante. Diamo uno stile sia all'icona che al pulsante, li rendiamo entrambi reattivi e cambiamo il colore di sfondo.

Dolce! Abbiamo imparato lo SwitchNavigator! Onward!

Torna nella cartella delle schermate per creare altri tre file:

(1) Stream.js

(2) Search.js

(3) Profile.js

Ci occuperemo prima di Stream.js:

Ora Search.js:

Infine Profile.js:

Sono anche andato su Home.js e ho modificato il titolo del pulsante per dire "Sono la schermata principale" e il metodo onPress per passare allo StreamScreen che presto chiameremo "Stream". Ora che abbiamo le nostre schermate, noi " dovrò importarli in App.js. Importeremo anche alcune dipendenze. All'interno di App.js adegua le tue importazioni alle seguenti:

Qui importiamo createBottomTabNavigator dalla reazione di navigazione. Importiamo anche icone dalla cartella MaterialIcons e MaterialCommunityIcons di reattivo-vettore-icone. Infine, importiamo il resto dei nostri schermi.

Ora che abbiamo importato il nostro tabNavigator, potremmo anche usarlo. Il nostro obiettivo è passare dalla schermata di accesso alla schermata principale, che sarà la rotta iniziale del nostro tabNavigator. Ciò richiede le seguenti modifiche ad App.js:

Innanzitutto, creiamo le schede. All'interno configuriamo il nostro routeName per i loro componenti. Successivamente, utilizziamo le opzioni di navigazione di response-navigation. All'interno di navigationOptions utilizziamo istruzioni destrutturanti e condizionali per assegnare le nostre icone alle schermate appropriate. Dopo aver assegnato a ciascuna schermata la propria icona, utilizziamo tabBarOptions per modellarle. Infine, regoliamo la seconda schermata del nostro SwitchNavigator su Schede. In questo modo passiamo dall'accesso, direttamente al nostro tabNavigator.

Per sbarazzarsi di questo fastidioso avviso, regolare index.js in questo modo:

ECCO! Abbiamo concordato TabNavigator! Il nostro prossimo campione - StackNavigator. Vorremmo usare StackNavigator per navigare verso nuove schermate all'interno di ogni scheda. Ma prima, dovremmo creare almeno un'altra schermata in cui navigheremo. Andiamo a questo!

In App.js dobbiamo importare createStackNavigator da "reagisci alla navigazione".

importare {createSwitchNavigator, createBottomTabNavigator, createStackNavigator} da "reply-navigation"

Dobbiamo anche importare il componente "Pulsante" da "Reattivo".

Dopo aver apportato le modifiche necessarie, appena sotto le nostre importazioni e sopra TabNavigator, aggiungi il seguente codice:

Per prima cosa creiamo il nostro componente SongScreen. Successivamente, aggiungiamo un pulsante come al solito e lo modelliamo. Infine, creiamo quattro StackNavigator e assegniamo a ciascuno i loro schermi individuali. Perché questo funzioni, deve comunicare in qualche modo con TabNavigator. Possiamo raggiungere questo obiettivo modificando le rotte del nostro TabNavigator in modo che siano i nomi dei nostri StackNavigator.

Vai su Home.js e regola il percorso di navigazione di Button su "Song" e il titolo su "Riproduci brano". Quindi nella parte superiore del componente HomeScreen, sopra il metodo di rendering, aggiungi il seguente codice:

Dopo aver salvato il progetto, vedrai quanto segue:

Come puoi vedere, StackNavigator ci fornisce metodi interessanti per dare uno stile alle nostre intestazioni e una freccia indietro per navigare avanti e indietro tra le schermate. Ora il nostro navigatore HomeStack funziona bene. Il processo per StreamStack, SearchStack e ProfileStack è lo stesso.

Vorremmo andare su Stream.js, Search.js e Profile.js per creare lo stesso metodo statico che abbiamo fatto in Home.js. Puoi regolare il metodo Button onPress per ogni schermata in modo tale che ogni stack passi alla SongScreen o alle schermate aggiuntive che desideri.

ANIMAZIONE

In App.js avremo bisogno di regolare le nostre importazioni un'ultima volta. Avremo bisogno di altri componenti da Reattivo-nativo e di alcune icone dalla cartella Feather e Ionicon di reattivo-vettore-icone. Apporta le modifiche indicate di seguito:

Crea una nuova cartella denominata "immagini". L'immagine che inseriamo qui sarà l'immagine SongScreen che scorre in senso orizzontale, imitando la funzionalità SoundCloud. Se desideri utilizzare la stessa immagine che sono, puoi trovarla qui. Scarica questa immagine o una di tua scelta e trascinala nella cartella delle immagini.

In App.js, appena sotto le nostre importazioni e sopra il componente SongScreen, aggiungi queste due variabili globali:

const SCREEN_WIDTH = Dimensions.get ("finestra"). larghezza
const SCREEN_HEIGHT = Dimensions.get ('window'). height

Queste due variabili abbastanza autoesplicative ottengono l'altezza e la larghezza di qualunque dispositivo utilizziamo.

Quando ascolti un brano sull'app mobile di SoundCloud, l'immagine occupa l'intera altezza dello schermo. Non ci sono intestazioni e tabbar. Liberarsi dell'intestazione è facile come implementare un metodo statico come quello che abbiamo usato nelle nostre altre schermate. Nella parte superiore del componente SongScreen inserire il seguente codice:

navigazione staticaOpzioni = {
intestazione: null
}

Con la recente versione di reagire-navigazione 2.0.1 ci sono stati alcuni cambiamenti significativi. Nelle versioni precedenti avremmo semplicemente potuto usare tabBarVisible: false nel metodo statico sopra per sbarazzarci della tabBar. Non è così facile ora ma siamo all'altezza del compito! Appena sotto StackNavigators inserisci il seguente codice:

Una volta salvato il tuo progetto, noterai che l'intestazione e la tabbar sono sparite. Andiamo oltre questo codice per cementare la nostra comprensione di ciò che sta succedendo qui. Usiamo la destrutturazione per posizionare navigation.state.routes [navigation.state.index] nella variabile routeName. Questo posiziona il nostro percorso (o schermo) corrente nella variabile routeName. Quindi implementiamo un'istruzione condizionale che dice: "Se questa è la SongScreen, sbarazzati di quella fastidiosa tabBar". Tutto questo è collegato al nostro StackNavigator HomeStack. Potremmo fare lo stesso anche per le altre tre pile, ma te lo lascio.

All'interno della nostra funzione di rendering SongScreen avvolgiamo il nostro intero componente all'interno di un componente SafeAreaView che abbiamo importato per gentile concessione di reattivo-nativo. Questo componente assicura semplicemente che la nostra app appaia presentabile su tutti i dispositivi (in particolare creata per gestire lo schermo più grande dell'iPhone X). Da tutto quello che ho letto è solo la migliore pratica per farlo.

All'interno di questo componente SafeAreaView manterremo il nostro componente Visualizza corrente, che contiene il nostro pulsante. In questa vista implementeremo un Animated.View. Questo ci viene fornito per gentile concessione dell'API animata che abbiamo importato tramite reagente nativo. È lo stesso di una visualizzazione normale, tranne per il fatto che possiamo usare animazioni al suo interno. All'interno di questo Animated.View implementeremo un componente Animated.Image che ci consente di animare la nostra immagine.

Affinché la nostra immagine scorra deve essere più grande della larghezza dello schermo. Questo è il motivo per cui la larghezza è impostata su SCREEN_WIDTH * 3. Questo rende la nostra immagine larga tre schermate. Quindi, quando animiamo il nostro scroll, dovrà scorrere la lunghezza di due schermate. Non preoccuparti del margine commentato Sinistra, torneremo su questo. Inoltre, non preoccuparti dello stile di Animated.View etichettato "imageContainer" ancora. A questo punto è vuoto.

Noterai che l'immagine che abbiamo importato che ho chiamato addio.jpg si trova in cima alla nostra vista contenente il pulsante che attualmente è il nostro unico mezzo per uscire dal SongScreen. Ci occuperemo di questo in seguito, ma per ora puoi semplicemente ricaricare il simulatore (comando + R su Mac) per tornare alla schermata di accesso. Ora per gestire la nostra animazione di scorrimento dell'asse x.

Dopo il metodo statico che abbiamo utilizzato per liberarci dell'intestazione, inserisci il codice seguente:

All'interno della nostra funzione di costruzione creiamo un nuovo Animated.Value. Successivamente, utilizziamo componentDidMount per inizializzare una funzione che chiamiamo animare. All'interno di questa funzione animata impostiamo animatedValue su 0. Pensa a questo come stato di inizializzazione e successivamente usando setState per regolare il valore.

L'API animata ha più "tipi" e Animated.timing è uno di questi. Diamo un'occhiata ai documenti.

Impostiamo su Valore su 1. In questo modo il nostro valore iniziale sarà compreso tra 0 e 1. Puoi considerare 0 come 0% e 1 come 100%. Quindi utilizziamo easing: Easing.linear per scorrere a una velocità costante.

Impostiamo la durata dello scorrimento su 90000ms o 90 secondi. Questo sarebbe normalmente impostato sulla durata di qualunque canzone fosse riprodotta, ma non abbiamo il lusso dell'API SoundCloud, quindi 90 secondi. Quindi utilizziamo .start () per avviare l'animazione. Se lo lasciassimo, l'animazione inizierebbe e si fermerebbe una volta che il minuto e mezzo è scaduto. Per realizzare il ciclo di animazione, utilizziamo il codice seguente per ripetere il processo.

.start (() => this.animate ())

Un'ultima cosa è necessaria per far funzionare il nostro scroll. Sotto la funzione di rendering ma sopra l'istruzione return, aggiungi il codice seguente:

Qui creiamo quella curiosa variabile marginLeft che è stata commentata dal nostro componente Animated.Image all'interno della nostra dichiarazione di ritorno. L'interpolazione mappa gli intervalli di input con gli intervalli di output. Quindi se 0 = 0% e 1 = 100%, allora vogliamo iniziare il nostro margine sinistro all'inizio (o 0%) e terminare una volta che l'immagine scorre altre due larghezze dello schermo (100%) perché la larghezza dell'immagine è SCREEN_WIDTH * 3.

Margine di decompressione Sinistra in Animated.Image e ricaricare il simulatore o l'emulatore e si dovrebbe vedere la seguente animazione. Dovrò velocizzarlo un po 'per convertirlo in una gif, ma avrai l'idea.

Sono davvero terribile nel fare gif. Rendo giustizia a questa immagine zero! Comunque, AVANTI!

Questa è una replica di SoundCloud, quindi facciamo sembrare la nostra immagine SongScreen la parte. In Animated.Image ma all'interno di Animated.View e SafeAreaView più esterni, inserisci il seguente codice:

Qui implementiamo alcune icone e testo. Le separiamo in tre categorie: Icone più in alto, Icone superiori e Icone inferiori. Ciò sarà più chiaro quando li modelleremo e daremo un'occhiata al simulatore, quindi facciamolo. Aggiorna di conseguenza il foglio di stile:

Salva App.js e dai un'occhiata al simulatore.

Ora sappiamo cosa sono le icone in alto, in alto e in basso. Ma perché abbiamo lasciato le icone (e il testo) più in alto nella nostra vista? Se hai familiarità con SoundCloud su dispositivo mobile, sai che sullo scorrimento dell'asse y (o panoramica), puoi animare l'immagine di SongScreen nella parte inferiore della vista. L'unica parte della vista visibile quando questo è fatto sono le icone in alto. Questo espone anche la vista dietro l'Animated.Image, dandoci di nuovo accesso al nostro pulsante di navigazione.

Detto questo, le icone in alto non restano lì solo per tutto il tempo. Svaniscono una volta che l'Animated.Image ha raggiunto il fondo della Vista. Allo stesso modo, Animated.Image e le icone associate si sbiadiscono da Visualizza. Dovremo far scorrere la nostra immagine verso il basso sulla panoramica e creare un'animazione di opacità.

Sotto il nostro metodo di navigazione staticaOpzioni e sopra la nostra funzione di costruzione, aggiungi il seguente codice:

Innanzitutto utilizziamo il metodo WillMount del ciclo di vita del componente di reattivo-nativo. All'interno, inizializziamo "animazione" e impostiamo il valore xey su 0. Quindi creiamo un PanResponder nativo che abbiamo importato in precedenza. Diamo un'occhiata ai documenti.

Chiediamoci quale funzionalità stiamo cercando. Esaminare questo codice passo dopo passo darà chiarezza.

onMoveShouldSetPanResponder: () => true,

Qui chiediamo il permesso di essere il rispondente in modo che ogni volta che gestiamo (o eseguiamo la panoramica) dello schermo, il PanResponder diventa reattivo.

onPanResponderMove: (evt, gestureState) => {
  this.animation.setValue ({x: 0, y: gestureState.dy})
}

onPanResponderMove tiene traccia del movimento del gesto sullo schermo. Non siamo preoccupati per l'asse x, quindi lo lasciamo a 0. Usiamo il parametro gestureState per tenere traccia della posizione dell'asse y e impostarne il valore su qualsiasi punto dello schermo dell'utente.

onPanResponderRelease viene chiamato quando l'utente rimuove il dito dallo schermo. All'interno di questo metodo abbiamo tre dichiarazioni condizionali. Cominciamo con il secondo.

else if (gestureState.dy <0) {
  Animated.spring (this.animation.y, {
    toValue: 0,
    tensione: 1
  }).inizio()
}

La prima cosa da capire è quando eseguiamo una panoramica dello schermo, che questo viene interpretato come un numero negativo. Al contrario, quando spostiamo lo schermo verso il basso, questo viene interpretato come un numero positivo. Quindi gestureState.dy <0 sta dicendo: "Se eseguiamo una panoramica dello schermo, procedi come segue".

Quindi quando scorriamo verso l'alto implementiamo Animated.spring, passiamo lo stato dell'animazione e diciamo all'immagine di tornare in cima allo schermo. Dato che stiamo usando Animated.spring per "far scattare" l'immagine in posizione, utilizziamo la tensione: 1 che impedirà all'immagine di rimbalzare in posizione. Infine, iniziamo l'animazione.

else if (gestureState.dy> 0) {
  Animated.spring (this.animation.y, {
    toValue: SCREEN_HEIGHT - 60,
    tensione: 1
  }).inizio()
}

Qui facciamo esattamente la stessa cosa, tranne per la panoramica dello schermo. Ricorda che i numeri positivi scendono e quelli negativi salgono. Quindi, se stiamo superando SCREEN_HEIGHT come numero, questo ci avvierà nella parte inferiore dello schermo. Quindi sottraggiamo 60 per posizionare l'immagine di 60 unità sopra la parte inferiore dello schermo.

Torneremo alla prima dichiarazione condizionale in seguito una volta che avremo una migliore comprensione del perché è necessario.

Nel nostro metodo di rendering e sopra l'istruzione return, aggiungi il codice seguente:

All'interno di animatedHeight memorizziamo il metodo che interpola i nostri gesti sullo schermo in numeri positivi e negativi. Questo metodo è getTranslateTransform ().

animatedIconOpacity cambia la nostra opacità upperMostIcons su scroll. animatedScreenOpacity fa lo stesso per il testo e le icone del nostro Animated.Image. Usiamo "clamp" per assicurarci che i valori di opacità rimangano nell'intervallo desiderato.

Gli intervalli di input su animatedScreenOpacity e animatedIconOpacity sono quasi identici, solo invertiti. L'idea qui è quella di far sbiadire gli UpperMostIcons contemporaneamente allo sbiadimento di animatoScreenOpacity. animatedIconOpacity inizia nella parte superiore dello schermo, procede verso il fondo-200 e termina sul fondo-60. Vedremo come appare nel simulatore tra un momento.

Nella nostra dichiarazione di reso avremo bisogno di passare il nostro PanResponder e aggiungere i nostri valori di AnimatedHeight e animatedOpacity. Ci occuperemo per primo del nostro Animated.View più esterno. Apporta le seguenti modifiche:

  {...} this.PanResponder.panHandlers
  style = {[animatedHeight, styles.imageContainer]}>

Quindi regola il file UpperMostIcons Animated.View in questo modo:

Ora per le nostre icone superiori:

Infine, le icone inferiori:

Dopo aver salvato App.js, dovresti vedere la seguente animazione:

Noterai come l'immagine "balza" in basso e in alto ma non rimbalza. Noterai anche che eseguiamo una panoramica dello schermo per portare l'immagine in fondo. Tuttavia, onPress l'immagine viene visualizzata nella parte superiore dello schermo, imitando la funzionalità di SoundCloud.

Tuttavia, se volessimo alterare in modo disonesto questo comportamento (cosa che non voglio), aggiungeremmo il seguente codice all'interno del nostro PanResponder:

Alla fine della suddetta gif, ho dimostrato un caso limite. Consentire all'utente di spostare l'immagine dallo schermo sarebbe un po 'sciocco. Cosa abbiamo fatto per impedire che ciò accadesse? Proprio quale stregoneria è al lavoro qui? Ricordi quella prima frase condizionale all'interno di onPanResponderRelease? Diamo un'altra occhiata:

if (gestureState.moveY 
  Animated.spring (this.animation.y, {
    toValue: 0,
    tensione: 1
  }).inizio()
}

if (gesto dell'asse y è in qualsiasi punto dello schermo && scorrendo verso l'alto) {Riporta l'immagine al valore iniziale e non lasciarlo rimbalzare} Penso che sia più facile da capire dopo aver visto la dimostrazione nella gif (almeno spero che è).

Un'ultima cosa da fare. Che cosa è successo al nostro pulsante nella prima vista che ci ha portato a casa? Se ti sbarazzi del flex: 1 in styles.container e fornisci un margine Top of say 55, lo vedrai. Detto questo, mi sono stancato di questo pulsante. Ha superato di gran lunga il suo benvenuto e preferirei che fosse sostituito. Regola la vista appena dentro SafeAreaView e sopra il nostro primo Animated.View di conseguenza:

Noterai che abbiamo aggiunto una vista proprio sotto la nostra vecchia. Creando questa vista e passandogli un flex: 1, dice semplicemente a reagire di riempire lo schermo con questa vista ad eccezione delle parti a cui teniamo - la vista sopra di essa. Ora regola il contenitore di StyleSheet e aggiungi i seguenti due elementi a StyleSheet:

Una volta salvato App.js, il nostro progetto sarà completo. Ho apportato una piccola modifica nel video qui sotto. Ho cambiato le dimensioni dell'icona di SoundCloud in Login.js da un ridicolo 50 a un 100 più ragionevole. Ho anche fatto del mio meglio per replicare ciò che amo di SoundCloud aggiungendo un pezzo di una delle mie canzoni di codifica preferite che sembra avere il mio essere umano preferito di tutti i tempi: Carl Sagan.

Vediamo come appare.

Huzzah !! È stata una battaglia, ma speriamo di non aver lasciato nessuno alle spalle mentre conquistavamo alcune delle funzionalità di SoundCloud!

Fino alla prossima volta!

Puoi trovare questo progetto sul mio Github. Raggiungi: Twitter | DEV