Man mano che i nuovi fattori di forma vanno oltre il touchscreen, il sistema di input di Compose si è evoluto per rispondere alla sfida. La sua architettura a livelli fornisce la base per supportare senza problemi qualsiasi cosa, dai gesti avanzati del trackpad ai nuovi input hardware.
Attraverso chiare analisi dell'architettura ed esempi di codice pratici, segui il ciclo di vita di un evento di input dal livello della piattaforma del sistema operativo fino ai rilevatori di gesti e ai componenti dell'interfaccia utente di Compose. Scoprirai come tracciare gli eventi nell'intero stack, applicare pattern architetturali chiave per i tipi di input emergenti ed eseguire il debug con sicurezza delle interazioni con gesti complessi nelle tue app.
performTrackpadInput. Queste API forniscono superfici di test familiari per verificare che le app gestiscano correttamente i dispositivi non touch.Trascrizione
Ciao,
Ci auguriamo che l'Android Developer Summit sia di tuo gradimento e ti ringraziamo per aver partecipato al nostro intervento.
Sono Alex Vanyo, un software engineer del team di Compose, e sono qui con Jeremy, anche lui software engineer del team di Compose.
Entrambi abbiamo lavorato per migliorare il sistema di input di Jetpack Compose e oggi parleremo di come lo abbiamo scalato oltre il touchscreen per semplificare il supporto dell'input non touch fin da subito.
In generale, l'input acquisisce qualsiasi modo in cui un utente interagisce con la tua app.
I touchscreen sono probabilmente la prima cosa che ti viene in mente per un'app mobile, ma non sono assolutamente l'unico tipo di input.
Altri tipi di input includono tastiere fisiche, mouse, trackpad, stilo, controller e dispositivi rotanti.
Stiamo anche assistendo all'introduzione di nuovi tipi di input, come i trackpad sugli occhiali intelligenti, il tracciamento oculare e delle mani in XR e i gesti delle dita e del polso su Wear.
E se vuoi andare ancora più sul generico, ci sono anche altri tipi di input: cose come gli IME software per l'editing di testo, microfoni, fotocamere e altri sensori del dispositivo.
Devi aspettarti che le tue app vengano utilizzate con un'ampia gamma di tipi di input.
Uno dei motivi è la varietà sempre crescente di dispositivi su cui vengono eseguite le applicazioni e la loro crescente adattabilità.
I fattori di forma dei dispositivi possono avere un metodo di immissione più comune, ma è importante non dare per scontato che un particolare fattore di forma del dispositivo implichi l'utilizzo di un particolare tipo di immissione.
Sebbene un Chromebook abbia un trackpad e una tastiera integrati, questi tipi di input non sono esclusivi di questo fattore di forma.
Dovresti aspettarti che tastiere fisiche, trackpad e mouse vengano utilizzati anche su smartphone, tablet e TV e alcune TV hanno persino touchscreen.
Un secondo motivo importante per prevedere e supportare più tipi di input è l'accessibilità.
Consentire agli utenti di utilizzare il dispositivo di immissione che preferiscono e di cambiarlo quando lo ritengono opportuno è la cosa giusta da fare per rendere le applicazioni più accessibili e utilizzabili.
Inoltre, a seconda della giurisdizione, potrebbe essere un requisito legale avere un comportamento specifico e compatibile nella tua applicazione per questi tipi di input, in particolare per quanto riguarda tastiere e mouse.
In breve, devi creare il supporto per diversi tipi di input, indipendentemente dal fattore di forma.
Vediamo quindi in che modo Jetpack Compose aiuta le tue app a soddisfare queste esigenze degli utenti.
La filosofia di Compose, in generale, è che questi diversi tipi di input dovrebbero comportarsi in modo appropriato fin da subito con i componenti standard, senza che tu debba fare nulla di speciale.
Inoltre, dovresti avere gli strumenti necessari per personalizzare e modificare il comportamento come ritieni opportuno.
Quindi, oltre a questi tipi di input iniziali, man mano che la piattaforma sottostante supporta altri tipi di input, puntiamo a creare il supporto per questi tipi di input nei componenti di Compose, migliorando il comportamento delle tue applicazioni semplicemente aggiornando la versione di Compose.
In questo intervento, esamineremo due di questi esempi, entrambi aggiunti di recente a Compose.
Per coincidenza, entrambi sono dispositivi simili a trackpad, ma, come vedrai, vengono utilizzati in modi molto diversi.
Iniziamo con alcuni lavori recenti sui trackpad dei laptop.
Un trackpad è un dispositivo di input basato su puntatore.
Più nello specifico, quando utilizzi un trackpad, ottieni coordinate bidimensionali.
Tuttavia, a differenza di un touchscreen, le coordinate delle dita sul trackpad non si trovano direttamente nello spazio delle coordinate della UI.
Invece, per un trackpad simile a quello di un laptop, c'è una traduzione per il controllo di un cursore.
All'interno di questa traduzione, puoi aspettarti una serie di conversioni di piattaforma complicate, come l'accelerazione del cursore e il palm rejection.
Quindi, ciò che ottieni sul lato Compose è un puntatore corrispondente alla posizione del cursore che agisce solitamente come un input del mouse.
Dalla piattaforma Android, riceviamo informazioni sul passaggio del mouse, sui clic, inclusi i clic secondari o terziari, e tutte queste informazioni vengono racchiuse in MotionEvent.
Per capire come abbiamo migliorato il comportamento nelle ultime versioni di Compose, diamo prima un'occhiata più da vicino a una panoramica generale di come Compose gestisce questi eventi con puntatore.
Suddividerò il processo in sei fasi diverse, che esamineremo una per una.
La prima fase prevede lo spostamento di MotionEvents in Compose.
Quindi, Compose è in realtà solo una vista: una vista molto complicata che fa molto, ma è solo una vista, il che significa che deve funzionare all'interno del sistema Android complessivo per l'input del puntatore.
Quindi, in Compose, ciò significa che Compose, poiché si trova all'interno di una finestra normale all'interno di un'attività normale, riceve i MotionEvent: i MotionEvent della piattaforma Android.
Compose deve quindi gestirli ed eseguire tutte le operazioni che vuoi eseguire in Compose in relazione all'input del puntatore.
La prima fase consiste nel tradurre questi MotionEvent in una rappresentazione interna indipendente dalla piattaforma in Compose, che chiamiamo PointerInputEvent.
Si tratta di un tipo interno, quindi non lo vedrai direttamente.
Se vuoi esaminare tutti i dettagli di ciò che sta accadendo qui, puoi consultare MotionEventAdapter, ma qui li salteremo.
Ora abbiamo una rappresentazione indipendente dalla piattaforma di un MotionEvent in Compose.
Inoltre, l'input di solito avviene nel tempo.
Ricevi una serie di eventi di input uno dopo l'altro, quindi è molto utile sapere cosa sta succedendo nel tempo.
Elementi come gesti, monitoraggio della velocità, rilevamento di slop, tutti questi elementi devono avere una comprensione di ciò che sta accadendo in ogni momento.
Quindi, la fase successiva di questa pipeline di elaborazione consiste nell'aggiungere queste informazioni temporali ed eseguire una pre-elaborazione basata su eventi puntatore successivi.
Nel codice, puoi vederlo come l'acquisizione del PointerInputEvent corrente, la sua combinazione con ciò che è accaduto con quello precedente e la creazione di un InternalPointerEvent sensibile al tempo.
Questo è un altro tipo interno, come suggerisce il nome, ma puoi considerarlo come una mappa di diversi ID puntatore per le modifiche dell'input del puntatore, mentre PointerInputChanges è un tipo pubblico.
Questo è il primo.
Un aspetto che potresti notare pensando ai diversi ID puntatore è che ora che siamo consapevoli del monitoraggio del tempo, possiamo anche tenere traccia di quando gli ID puntatore vanno e vengono.
Non ce n'è uno solo.
Se hai più dita su un touch screen, potresti avere più puntatori contemporaneamente che vanno e vengono e dobbiamo gestirli.
Anche in questo caso, se vuoi approfondire i dettagli, puoi dare un'occhiata a PointerInputChangeEventProducer, che fa tutto questo, ma noi lo salteremo.
Abbiamo ancora molto da fare.
Ora abbiamo una rappresentazione di input di Compose, che contiene informazioni sul tempo.
Il passaggio successivo consiste nel capire cosa fare con questo evento.
Dove dobbiamo inviarlo?
E questo ci porta ai test del percorso di hit.
Immaginiamo una situazione in cui viene inviato un MotionEvent e questo ComposeView riempie l'intera finestra, forse l'intero schermo.
Dobbiamo capire dove inviarlo.
E poiché Compose è solo una visualizzazione, ma al suo interno abbiamo un sacco di cose, un gruppo di nodi, pulsanti e cose diverse con cui interagisci, dobbiamo fare in Compose tutto il lavoro per capire dove inviare un evento che si verifica in una posizione specifica e capire quale nodo è effettivamente interessato.
Questi nodi di layout hanno coordinate, limiti e forme, quindi dobbiamo capire quale, se presente, viene toccato.
Ma non è nemmeno la visione più generale.
A quanto pare, ci piace inserire elementi interattivi all'interno di altri elementi interattivi, ad esempio un pulsante all'interno di un elenco.
A quanto pare, lo facciamo spesso.
Quindi, il caso generale completo è che un evento di input specifico crea un percorso di hit completo che attraversa ogni nodo interattivo colpito dalle coordinate dell'evento di input.
Quindi, in questo caso, il 3 si trova nella gerarchia di nidificazione.
Quindi, abbiamo un percorso di hit, ma non è ancora sufficiente.
Ricordi che ho detto che abbiamo più eventi di input del puntatore, più puntatori che potrebbero essere premuti?
In realtà, dobbiamo avere un albero dei percorsi di hit completo.
Non esiste un solo percorso di hit quando sono presenti più puntatori.
Questa è la fase successiva: la creazione e l'aggiornamento di questo albero dei percorsi dei risultati.
Ecco un'analisi più dettagliata di ciò che sta accadendo.
A sinistra, abbiamo i nodi disposti visivamente, mentre in basso a sinistra puoi vedere la rappresentazione gerarchica, quali nodi si trovano all'interno di altri nodi, quali sono predecessori o discendenti l'uno dell'altro.
Quando un puntatore si abbassa, teniamo traccia di questo percorso di hit, ovvero il percorso completo attraverso l'albero, e poi otteniamo un percorso di hit per quel puntatore specifico.
Questo percorso di hit diventa un albero quando otteniamo il secondo puntatore.
Potrebbe intersecarsi con alcuni degli stessi nodi dell'altro, ma potrebbe anche interagire con altri.
A destra, quindi, è presente l'albero dei percorsi degli hit che ora gestiamo.
Inoltre, i puntatori possono andare e venire, quindi dobbiamo mantenerli nel tempo per assicurarci di ottenere l'elenco completo dei nodi interessati a ogni evento in corso in questo momento.
Okay.
Ora abbiamo un albero dei percorsi di hit che gestiamo per questi eventi di input, il che significa che siamo passati alla fase successiva.
Ora sappiamo dove inviare questi eventi di input del puntatore, ovvero quali nodi sono interessati a ogni evento specifico.
Ora che lo sappiamo, possiamo eseguire una trasformazione delle coordinate.
Si scopre che è molto utile se il nodo riceve eventi di input all'interno del proprio spazio di coordinate locali, in modo da non dover eseguire una serie di calcoli matematici delle coordinate.
Quindi, una volta che lo sappiamo, possiamo prendere l'evento di input del puntatore, combinarlo con le coordinate del nodo interessato e infine creare il PointerEvent pubblico che verrà inviato a tutti voi e al modificatore di input del puntatore.
E questa è l'ultima cosa della nostra fase, la fase 6, ovvero l'invio effettivo dell'evento.
Questo è un evento di input del puntatore che si è verificato e che viene inviato al nodo per eseguire l'azione desiderata.
Lo facciamo in tre passaggi diversi.
Abbiamo un passaggio iniziale, un passaggio principale e un passaggio finale.
Il motivo per cui esistono tre passaggi diversi è che consentono ai nodi di comunicare tra loro e di comunicare l'interesse per parti specifiche di eventi di input del puntatore specifici, per poi consumare parti diverse di questi eventi nell'ambito dell'invio.
Quindi, il primo passaggio, quello iniziale, va dall'antenato al discendente.
Un esempio di dove questo è utile è se un nodo di layout esterno potrebbe essere un container di scorrimento e sa di aver iniziato a scorrere e vuole impedire che tutti gli eventi del puntatore raggiungano i suoi elementi secondari.
In questo modo, nella fase iniziale, l'evento viene consumato e gli elementi secondari non riceveranno altri eventi.
La seconda fase è quella principale.
Questo è l'unico che va dal discendente all'antenato.
Come suggerisce il nome, questo è il pass principale per eseguire le operazioni.
Ad esempio, è qui che un discendente utilizzerà un evento down, avviando la gestione di un clic, e avrà la possibilità di farlo prima di uno qualsiasi dei suoi elementi principali se hai elementi cliccabili nidificati.
Infine, l'ultimo passaggio, quello finale, è identico al passaggio iniziale, in cui si passa dall'antenato al discendente.
In questo modo, un elemento secondario può comunicare a un elemento principale che ha utilizzato parte di un evento.
Quindi, se un elemento secondario inizia a gestire un clic, ma poi un elemento principale inizia a scorrere, l'elemento secondario può vedere che l'elemento principale ha eseguito questa azione e può annullare il clic che aveva iniziato a gestire.
Questi sono i tre passaggi e le sei fasi.
Queste sono le basi di tutto ciò che si trova sopra il sistema di input del puntatore.
Tutta la gestione dei gesti, lo scorrimento, il trascinamento, i clic e qualsiasi input personalizzato del puntatore sono basati su questa base.
Ora possiamo parlare un po' dei dettagli di come abbiamo migliorato il supporto del comportamento del trackpad in Compose.
Quindi, prima di Compose 1.12, gli eventi di input del trackpad venivano segnalati come un tipo di puntatore di tocco, poiché è un dito che interagisce fisicamente con un trackpad per controllare il cursore.
Tuttavia, l'utente si aspetta che questo tipo di input si comporti in modo molto più simile a un mouse fisico.
Pertanto, abbiamo modificato il tipo di puntatore da PointerType.Touch a PointerType.Mouse.
Con questa modifica, gli utenti vedranno un'esperienza di mouse e trackpad molto più coerente per qualsiasi componente che esamina queste informazioni.
Un esempio è il comportamento dei tooltip: probabilmente vuoi che il comportamento dei tooltip sia lo stesso per un trackpad e un mouse.
La seconda grande modifica che abbiamo apportato al trackpad riguarda i gesti con più dita che si possono eseguire su un trackpad.
In precedenza, Compose utilizzava il concetto intelligente, ma un po' inquietante, di dita finte della piattaforma Android.
Vediamo cosa significa.
Quando utilizzi due dita su un trackpad per eseguire la panoramica, Android genera un singolo dito touchscreen fittizio che si posiziona nel punto in cui si trova il cursore.
Poi, mentre muovi le dita sul trackpad, il dito finto sul touchscreen si sposta della distanza e nella direzione corrette per simulare il comportamento desiderato, quindi si rilascia.
Quindi, due dita che si muovono verso l'alto sul trackpad vengono tradotte in un unico dito finto sul touchscreen, che si muove anch'esso verso l'alto, il che a sua volta provoca uno scorrimento verso il basso.
Un aspetto interessante è che se, come me, utilizzi la funzionalità di scorrimento inverso del trackpad, lo scorrimento deve andare nella direzione opposta.
Per farlo, basta invertire la direzione del dito finto.
Quindi, ora, due dita sul trackpad che si muovono verso l'alto vengono tradotte in un singolo dito finto sul touchscreen che si muove verso il basso, il che a sua volta causa uno scorrimento verso l'alto, l'opposto di quello che avevamo prima.
La scalabilità funziona in modo simile.
Quando utilizzi due dita su un trackpad per ridimensionare, Android genera due dita virtuali sul touchscreen che si abbassano e poi si avvicinano o si allontanano, a seconda del gesto, per simulare il comportamento desiderato di aumento o diminuzione dello zoom.
Quindi, a partire da Compose 1.12 per la panoramica e Compose 1.13 per la scalabilità, ora intercettiamo questi tocchi involontari.
Non li inviamo così come li riceviamo dalla piattaforma, ma li reinterpretiamo in nuovi tipi di eventi puntatore: PanStart, PanMove e PanEnd per la panoramica e ScaleStart, ScaleChange e ScaleEnd per la scalabilità.
E lo facciamo nella fase di conversione degli eventi da Android a Compose di cui ho parlato prima.
Quindi, convertiamo gli eventi Fake Finger nei tipi appropriati e poi inviamo questi gesti definiti dalla piattaforma al percorso di hit corrispondente al cursore.
Vediamo perché è importante.
Perché lo facciamo?
Supponiamo che questa casella sia una bella immagine che vogliamo poter scalare.
Quando utilizzi due dita su un trackpad per ridimensionare, è possibile che uno dei due tocchi simulati non colpisca l'elemento.
Il percorso di hit non intersecherebbe l'immagine e inizierebbe al di fuori dei limiti.
In questo modo, l'immagine non verrebbe ridimensionata, ma potrebbe scorrere.
Inoltre, se ci fosse un altro elemento scorrevole al di fuori dell'immagine, anche questo potrebbe iniziare a scorrere insieme.
Non è quello che voleva l'utente.
Voleva scalare.
Quindi, ora, invece di inviare i tocchi simulati, inviamo il gesto di ridimensionamento solo all'elemento sotto la posizione del cursore, il che porta al comportamento corretto.
Indipendentemente dalla posizione del cursore o dalla sua vicinanza al bordo dell'elemento che vuoi ridimensionare, ora il ridimensionamento verrà eseguito correttamente.
La buona notizia per tutti voi è che questi eventi di input reinterpretati vengono utilizzati per impostazione predefinita in componenti come scrollable e transformable per offrire un comportamento di fedeltà superiore per gli input del trackpad se utilizzate questi componenti di base.
E se utilizzi qualcosa basato su di essi, otterrai questo comportamento migliorato semplicemente aggiornando la tua versione di Compose all'ultima versione.
Se stai scrivendo modificatori di input del puntatore personalizzati, ti invito a dare un'occhiata a queste nuove API e ad assicurarti di supportarle per offrire ai tuoi utenti la migliore esperienza possibile su tutti questi diversi dispositivi di input.
E, naturalmente, abbiamo anche aggiunto delle API di test, in modo che tu possa verificare che la tua app gestisca correttamente tutti questi diversi tipi di input.
Pertanto, l'performTrackpadInput è molto simile alle API di test esistenti per mouse e tocco.
onNodeWithContentDescription("Photo")
.performTrackpadInput {
click(center)
pan(Offset(0f, 100f))
scale(0.9f)
}Inoltre, puoi utilizzare tutti questi elementi per verificare che l'app funzioni correttamente, indipendentemente dal dispositivo di input utilizzato dall'utente sul tuo dispositivo.
Spero che questo ti dia un'idea molto più chiara di come un evento puntatore passi da un dito su un touchpad alla tua app.
Ora passo la parola a Jeremy, che vi parlerà di un modo molto diverso di utilizzare i trackpad.
Grazie.
Okay.
Ciao, sono Jeremy Walker.
Lavoro molto sull'input e ho pensato che sarebbe stato interessante raccontarvi alcune delle novità che stiamo introducendo con l'input di Scrivi e l'impatto che hanno su di voi.
E in particolare con questo tipo di input indiretto, nato da un nuovo input basato su un nuovo fattore di forma: gli occhiali intelligenti.
Quindi, quando ricevi input dagli occhiali, puoi farlo in un paio di modi: puoi guardare qualcosa, puoi usare la voce, puoi usare i gesti, ma parlerò in particolare del touchpad.
Questi sono un po' diversi da quelli di cui ha parlato Alex, in quanto sono, ad esempio, sul lato della testa, sono verticali, sono molto larghi e sono estremamente corti.
Quindi, quando abbiamo pensato a come avrebbe funzionato in Compose, se immagini di guardare davanti a te un piccolo pulsante, se sposti un cursore per provare a toccarlo, non sarà un'esperienza fantastica, giusto?
Quindi, come faremo a supportarlo?
Ecco come appare la UI di Glasses.
È progettato in modo unico per sovrapporre i contenuti al tuo campo visivo senza interruzioni.
Ma è molto semplice, cerchiamo di mantenerlo semplice.
Se guardiamo un esempio qui, puoi vedere che si tratta di un elenco.
È incentrata su un determinato elemento al centro.
Cosa vogliamo fare davvero?
Beh, vogliamo selezionarlo o magari passare a quello precedente o successivo.
Non abbiamo necessariamente bisogno di una posizione sullo schermo.
Questa è un po' più complicata, ma puoi vedere che ci sono fondamentalmente tre opzioni: puoi riagganciare, riattivare l'audio o disattivare il video.
Ma in realtà, vuoi selezionarlo o passare a un altro e selezionarlo.
Quindi, pensiamo che non sia necessario mappare direttamente una posizione sullo schermo o, in questo caso, i tuoi occhi.
Quindi, sì, questo è l'aspetto effettivo.
Per fare un esempio, qui puoi vedere che mi sto spostando tra gli elementi e questo è sulla UI di Glasses, che si adatta a ciò che vogliamo.
Inoltre, puoi toccarlo.
Abbiamo esaminato Android per capire cosa esistesse già che potessimo utilizzare per supportare questa funzionalità.
E se vi è mai capitato di… Oh, scusa.
Qui stiamo saltando un passaggio.
Fammi tornare indietro.
Ci siamo.
Okay.
Quindi, se avete mai utilizzato uno di questi dispositivi e lo avete collegato a un dispositivo Android, sapete che potete spostarvi facilmente da un elemento UI all'altro.
Ad esempio, con il telecomando, puoi vedere che puoi Se immagini o se lo hai collegato a Google TV e hai un'intera griglia di film, puoi premere i pulsanti freccia per andare a quello che vuoi e puoi premere il pulsante per selezionarlo, il pulsante OK.
Se hai un menu di gioco e questo controller, puoi utilizzare il D-pad o il joystick per spostarti su un elemento UI, ad esempio in un menu, e selezionarlo.
Lo stesso vale per la tastiera.
Questo corrisponde a ciò che vogliamo in Compose.
E ancora una volta, ecco gli elementi, così puoi farti un'idea di come appare.
Se andiamo un po' più indietro nel tempo, c'era questo tipo di touchpad accoppiato a una TV che utilizzava una funzionalità chiamata navigazione touch.
L'idea è che le coordinate x e y non sono affatto legate allo schermo.
Vengono utilizzate solo per il rilevamento dei gesti.
Quindi, se scorri verso destra, è come se premessi la freccia per andare avanti.
Se scorri verso sinistra, ti sposti verso sinistra, verso l'alto e verso il basso.
In questo modo, otteniamo quasi tutto ciò che vogliamo.
Utilizzi x e y solo per il monitoraggio della velocità, per capire i trascinamenti, gli scorrimenti e così via. Questo ci dà la maggior parte di ciò che ci serve per iniziare con Compose.
Non è esatto perché è mappato alla TV, quindi dobbiamo assicurarci di non seguire il percorso della TV, di non rovinare le TV in un secondo momento, quindi dobbiamo assicurarci di dividerle, ma è qualcosa che faremo tra un po'.
Ma ci dà un punto di partenza.
In questo modo, possiamo utilizzare questo tipo di navigazione touch.
Quindi, ok.
Come sarà la progettazione dell'API?
In Compose, l'input più generale o più comune è il tocco, giusto?
Quindi, abbiamo molto lavoro da fare in questo senso.
Quindi, abbiamo dovuto capire come si relaziona con questo.
Ricordiamo che l'evento PointerEvent ha questo aspetto.
Qui puoi vedere che hai il tipo, che è il tipo di movimento verso il basso, e poi hai questo PointerInputChange, che è solo un elenco. Puoi pensarlo come le dita.
Se hai tre dita abbassate, avrai tre elementi nell'elenco.
Volevamo che avesse un aspetto simile a questo.
Questo non è un modulo UI, che è un modulo di livello molto più basso rispetto a quello utilizzato dalla maggior parte degli sviluppatori quando gestiscono l'input.
Saranno un livello superiore rispetto al livello di base.
È qui che si trovano tutti i nostri rilevatori di gesti.
Perché, in qualità di sviluppatori, non vi interessano tutti questi input dettagliati.
Vuoi solo sapere se è stato eseguito un trascinamento, un clic o uno scorrimento e poi eseguire l'azione in base a questo.
Abbiamo quindi un codice che lo fa per te, basato su questi elementi.
Quindi, abbiamo iniziato a pensare: "Ok, dobbiamo rendere il più semplice possibile per il codice esistente in Foundation il supporto di questi nuovi tipi".
Quindi, abbiamo creato un nuovo tipo che sembra praticamente lo stesso.
Viene semplicemente chiamato indiretto.
"Indiretta" significa che non si tratta di un tocco diretto sullo schermo.
Quindi, ti fa sapere che, hey, sta arrivando. Non pensare a questo da un punto di vista del tocco.
Pensa che qui stiamo solo gestendo i gesti e navigando tra gli elementi UI.
Il tipo è lo stesso.
C'è un elenco di dita.
In questo caso, si tratta di IndirectPointerInputChange.
Abbiamo fatto così e ha funzionato alla grande.
Ma poi abbiamo trovato un altro elemento chiamato Primary Directional Motion Axis (Asse di movimento direzionale principale).
È un po' complicato, ma avrà senso quando lo spiegherò, perché è interessante il modo in cui è nato.
Come è nata l'idea?
Anche in questo caso, hai questo touchpad sul lato della testa, che è largo e corto.
A questo punto, abbiamo pensato: "Ok, abbiamo la navigazione touch".
Quindi, possiamo scorrere verso sinistra e verso destra.
E funziona bene.
Ma se pensi a come sono fatti i trackpad oggi, giusto?
Quando ti sposti verso sinistra o verso destra, lo fai sul trackpad con i contenuti della UI.
Se scorri verso l'alto o verso il basso, scorrerai l'elenco verso l'alto o verso il basso.
Se provi a farlo con gli occhiali, non è così male, giusto?
Ma se lo fai, l'esperienza non è ottimale.
Abbiamo scoperto che il movimento più naturale è ovviamente il tocco, ma anche lo scorrimento lungo il lato degli occhiali.
Abbiamo quindi condotto una serie di studi con gli utenti, i quali si aspettano che, quando scorrono in orizzontale, i contenuti non si spostino in orizzontale, ma in verticale.
Puoi pensarci se provi a farlo nella tua testa, ad esempio quando scorri qui, se hai un elenco davanti a te, come andare avanti, puoi andare avanti nell'elenco o puoi tornare indietro nell'elenco.
Quindi, prendiamo un input bidimensionale e cerchiamo di pensarlo in modo unidimensionale.
Ed è da qui che è nata l'idea dell'asse di movimento principale: puoi muoverti in orizzontale e il movimento avverrà in verticale.
Quindi, puoi specificarlo sul dispositivo stesso.
Puoi dire: "Ehi, questo non è un normale trackpad".
Voglio che la direzione principale sia orizzontale, che è l'unico asse lungo il quale voglio muovermi.
Ed è da qui che è nata l'idea.
Ok.
Quindi, siamo abbastanza soddisfatti.
Compose gestisce tutto.
Inoltre, hai questa bella implementazione verticale.
Puoi pensarci.
Quindi, abbiamo mappato la definizione orizzontale con i pulsanti Avanti e Indietro.
Non abbiamo un sopra, un sotto, una sinistra o una destra.
Abbiamo solo avanti e indietro.
Poi, possiamo mappare questo elemento, se hai familiarità con lo stato attivo, con l'elemento successivo e quello precedente.
Ora passiamo al successivo e al precedente.
Questo approccio funziona benissimo per i video verticali.
Ed è anche più bello.
Funziona con l'orientamento orizzontale, giusto?
Perché hai solo un gruppo di elementi attivi.
Basta andare al brano successivo o precedente e funziona con entrambi.
Quindi questo è un caso con Compose in cui abbiamo definito e capito tutto in Compose, ma sarebbe davvero bello se il dispositivo stesso potesse dirlo, perché sa se ha una forma strana e sa se vuole solo questo asse principale, ma non ha modo di definirlo.
Quindi, abbiamo dovuto riportare questo concetto alla piattaforma e dire: "Ok, dobbiamo definirlo nella piattaforma in modo da poterlo standardizzare e questi dispositivi stessi possono specificarlo, così Compose può supportarlo completamente fino in fondo".
Quindi, in questo caso abbiamo lavorato in Compose, siamo andati avanti in Compose e abbiamo lavorato a ritroso, portando con noi il lavoro.
Ed è esattamente quello che abbiamo fatto.
Abbiamo creato questa nuova API in modo che tu o il dispositivo possiate specificarlo.
InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()E se ricordate, prima, quando ho parlato di input tocco, o di navigazione tramite tocco, ho detto che era pensata principalmente per le TV.
Beh, non volevamo interrompere queste implementazioni.
Per questo motivo, abbiamo aggiunto un altro campo per specificare questo nuovo tipo di input per il vetro.
Ok.
Quindi, in definitiva, cosa significa questo per te?
Si tratta di una versione più specifica di ciò che ottieni come rilevatore di gesti.
// Glimmer Library
Box(
modifier =
Modifier.fillMaxSize()
.onIndirectPointerGesture(
enabled = true,
onSwipeForward = { /* onSwipeForward */ },
onSwipeBackward = { /* onSwipeBackward */ },
onClick = { /* onClick */ },
)
.focusTarget()
) {
// App()Se utilizzi Foundation, funzionerà immediatamente con i rilevatori di gesti.
Abbiamo fatto il lavoro per te, quindi non devi fare nulla.
Ma se vuoi creare un'app per gli occhiali, puoi utilizzare la nostra libreria Glimmer e poi abbiamo un rilevatore di gesti speciale, che ti dà solo, sai, quindi non devi combinarli, puoi usare solo questo, ottenere il clic, il frame avanti e indietro.
Ok.
Abbiamo parlato del funzionamento dell'API, nella piattaforma e in Compose.
E i test?
Ecco come appare il sensore touch quando vuoi eseguire i test del sensore touch.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }
}Basta dire performTouchInput e poi fare un gesto.
Esistono molti gesti diversi e noi creiamo tutti i piccoli eventi per far sì che il gesto avvenga.
Come funziona a livello generale?
Beh, hai il modulo Compose UI, hai il modulo UI Testing e in un certo senso comunicano, almeno per quanto riguarda l'input, utilizzando MotionEvents.
Inoltre, il modulo UI Testing utilizza anche un meccanismo di accodamento, tutto basato su MotionEvent.
Perché ti sto dicendo questo?
Perché non è possibile creare in modo programmatico un MotionEvent simile a un evento di input indiretto, il che è stato fantastico per me perché significava che dovevo riscrivere tutta la comunicazione tra i moduli e il sistema di coda.
Ma l'ho fatto.
Cosa significa tutto questo per te?
Se hai dimestichezza con i test degli input tocco, questo è ciò che ottieni per l'input indiretto.
Basta aggiungere "indiretta" al nome.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}Ora, ho passato alcuni parametri aggiuntivi.
Probabilmente riconosci il primo, ovvero dove specifichi l'asse orizzontale.
Inoltre, le dimensioni dell'input corrisponderebbero ovviamente alle dimensioni del touchpad.
Questo non cambia molto.
Quindi, se guardi il codice sorgente, puoi vedere che ho appena scritto una funzione di estensione che ha questo aspetto.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }
}Ok.
Questo dovrebbe darvi un'idea di tutto il lavoro che abbiamo fatto solo per supportare i trackpad sulle montature o sugli occhiali intelligenti.
Ma stiamo facendo questi, sto facendo questi esercizi anche per il tracciamento degli occhi e delle mani, quindi c'è molto altro in arrivo.
In sintesi, cosa voglio che ricordiate della mia parte?
Quindi, che tu stia utilizzando un trackpad con uno smartphone, degli occhiali o qualsiasi altro input, stiamo pensando a questo e stiamo cercando di fare il lavoro in anticipo.
Quindi, se utilizzi questi rilevatori di gesti di Foundation, funzionano immediatamente.
Questo è il primo punto.
In secondo luogo, Compose è il toolkit UI principale e continueremo a esplorare e supportare nuovi tipi di input, sia nella piattaforma che nel toolkit, come ho fatto qui.
Inoltre, ovviamente, per le API di test, voglio mantenere una superficie e un paradigma familiari, in modo che sia il più semplice possibile per te eseguire la migrazione e testare questi nuovi tipi.
Grazie, spero che queste informazioni ti siano state utili.
onNodeWithContentDescription("Photo")
.performTrackpadInput {
click(center)
pan(Offset(0f, 100f))
scale(0.9f)
}InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()// Glimmer Library
Box(
modifier =
Modifier.fillMaxSize()
.onIndirectPointerGesture(
enabled = true,
onSwipeForward = { /* onSwipeForward */ },
onSwipeBackward = { /* onSwipeBackward */ },
onClick = { /* onClick */ },
)
.focusTarget()
) {
// App()@Test
clickTest {
rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }
}@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }
}