Delt elementovergang med React Native

I dette indlæg taler jeg om, hvordan man opnår deling af elementovergang med React Native til både iOS og Android.

Jeg har lagt koden på GitHub, og du kan se, hvis du vil hoppe lige ind i den.

Hensigt

Lad os se på, hvad vi skal bygge. Nedenfor er et eksempel på fotonet, hvor vi tilføjer en delt elementovergang. På denne måde kan vi overføre jævnt mellem et gitter og detaljesiden for et foto, vi vælger.

Dette er en meget glattere og en kontinuerlig oplevelse.

Nærme sig

Inden vi bygger dette, så lad mig fortælle dig, hvordan systemet fungerer under hætten. Da React Native ikke understøtter ægte delte elementer, når vi siger, at vi udfører en delt elementovergang mellem to skærme, deler vi ikke teknisk nogen elementer. I stedet har hver skærm sit eget individuelle element.

Hvad jeg laver, er at videregive informationen om det delte element - såsom dets placering og størrelse - mellem disse to elementer.

Når detaljeringsskærmen starter, er baggrunden indstillet til gennemsigtig, og den skjuler alle dens elementer. Det ændrer derefter attributterne for det delte element til det, der er bestået, og gør det derefter synligt. Derefter animerer det sig til sin naturlige position. Når overgangen skrider frem, falmer vinduesbaggrunden og resten af ​​ikke-delte elementer langsomt ind, indtil de er helt uigennemsigtige.

Så selvom elementet ikke er teknisk delt, får dette smarte trick til røg og spejl det ud til at være det.

Så nu, hvor vi forstår, hvordan denne proces fungerer, slipper vi trin for trin for at forstå, hvordan mockelementet er blevet delt, og hvordan vi kan styre animationer.

Trin 1: Indtastning og afslut animation

Jeg har to skærme her: Gitter og detaljer. Fra gitterskærmen kan vi starte detaljer-skærmen ved at klikke på et af billederne i gitteret. Så kan vi vende tilbage til gitterskærmen ved at trykke på tilbage-knappen.

Når vi går fra skærmbilledet Grid til detalje, har vi en mulighed for at køre to sæt overgangsanimationer - Exit-overgangen til skærmbilledet Grid, og skærmen og Entry-overgang for Detail.

Lad os se, hvordan vi implementerer dette.

Uden nogen overgang er det sådan, hvordan appen ser ud. Ved at klikke på det individuelle billede kommer du til en detaljeskærm.

Lad os tilføje en exit-overgang til det første gitterskærmbillede. Her bruger vi en simpel fade out-overgang ved hjælp af den animerede api, som interpolerer opacitetsattributten for gitterskærmbeholderen fra 1 til 0.

Nu hvor vi har gjort det, er det sådan, det ser ud:

Ikke dårligt. Vi ser, at gitteret er falmet ud, når vi bevæger os hen på detaljeskærm.

Lad os nu tilføje en ny overgang til indholdet af detaljeskærmen, som den kommer ind. Lad os skubbe teksten ind på stedet fra bunden.

Dette gøres ved at tildele en interpoleret animeret værdi til egenskaben translateY i tekstbeholderen.

Og sådan ser det ud:

Titlen og beskrivelsen glider meget pænt ind, men billedet vises pludseligt. Dette skyldes, at vores overgang ikke målretter specifikt mod billedet. Vi løser dette snart.

Trin 2: Overgangslag for det delte element

Vi tilføjer nu et overgangslag, der vises under overgangen, og som kun indeholder det delte element.

Dette lag udløses, når der klikkes på billedet i gitteret. Det modtager information om det delte element, såsom dets placering og størrelse fra både skærmbilledet Tavle og skærmbilledet Detaljer.

Trin 3: Animation i overgangslaget

Vi har informationerne i overgangslaget om kilden og destinationspositionen for det delte element. Vi er bare nødt til at animere dem.

Lad os først indstille elementet baseret på kildepositionen og -størrelsen og derefter animere det til destinationsplaceringen. Dette kan gøres på to måder. Lad os se på dem begge.

Ved at interpolere på bredden, højden, toppen og venstre

Dette er en ligetil tilgang. Hvis vi ønsker, at et element skal ændre sig fra en størrelse til en anden, og fra en position til en anden, ændrer vi elementets bredde, højde, top og venstre stil.

Og sådan ser det ud:

Ydelsesanalyse

Når vi bruger Animeret, erklærer vi en graf over noder, der repræsenterer de animationer, vi vil udføre, og bruger derefter en driver til at opdatere en Animeret værdi ved hjælp af en foruddefineret kurve.

Her er en oversigt over trinene til en animation, og hvor det sker:

https://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html
  • JavaScript: Animationsdriveren bruger requestAnimationFrame til at udføre på hver ramme og opdatere den værdi, den kører ved hjælp af den nye værdi, den beregner, baseret på animationskurven.
  • JavaScript: Mellemværdier beregnes og sendes til en props-knude, der er knyttet til en visning.
  • JavaScript: Visningen opdateres ved hjælp af setNativeProps.
  • JavaScript til Native bridge.
  • Indfødt: UIView eller android.View er opdateret.

Som du kan se, sker det meste af arbejdet på JavaScript-tråden. Hvis det er blokeret, springer animationen over rammer. Det skal også gå gennem JavaScript til Native bridge på hver ramme for at opdatere indbyggede visninger.

Dette problem kan løses ved hjælp af useNativeDriver. Dette flytter alle disse trin til native.

Da Animated producerer en graf over animerede noder, kan den serialiseres og sendes til native kun én gang, når animationen starter. Dette eliminerer behovet for tilbagekald i JavaScript-tråden. Den oprindelige kode kan sørge for at opdatere visningerne direkte på UI-tråden i hver ramme.

Den største begrænsning er, at vi kun kan animere ikke-layoutegenskaber. Ting som transformering og opacitet fungerer, men flexbox- og positionsegenskaber som dem, der er brugt ovenfor, vil ikke.

Interpolering om transformering og brug af useNativeDriver

Lad os nu animere ved hjælp af transform. Dette kræver en vis matematik for at beregne skalaen, x og y positionen.

Med denne implementering skal billedet pixelere, hvis vi skalerer fra et mindre billede til et større billede. Så vi vil gengive det større billede, derefter skalere det ned til dets startstørrelse og derefter animere det op til den naturlige størrelse.

Vi kan få værdien for startskala med en linje JavaScript som denne:

openingScale = sourceDimension.width / destinationDimension.width;

Du kan se, at det skalerede billede og det originale billede ikke ser det samme ud, fordi størrelsesforholdet mellem kildebillede og destinationsbillede er forskellige, så for at løse det vil vi gengive billedet med kildeformatforhold baseret på destinationsdimensionen.

const sourceAspectRatio = source.width / source.height;
const destAspectRatio = destination.width / destination.height;
if (aspectRatio - destAspectRatio> 0) {
  // Landskabsbillede
  const newWidth = aspectRatio * destination.højde;
  openingScale = source.width / newWidth;
} andet {
  // Portrætbillede
  const newHeight = destination.width / aspectRatio;
  openingScale = source.height / newHeight;
}

Nu hvor skalaen er korrekt, er vi nødt til at få den nye position baseret på destinationsbilledet. Dette kan beregnes ved hjælp af destinationspositionen minus halvdelen af ​​forskellen mellem den gamle dimension og den nye dimension. Hvilket ville svare til:

if (aspectRatio - destAspectRatio> 0) {
  // Landskabsbillede
  destination.side side - = (newWidth - destinationWidth) / 2;
} andet {
  // Portrætbillede
  destination.side side - = (newHeight - destinationHeight) / 2;
}

Det er perfekt! Vi har nu den rigtige dimension og position til det overgangsbillede.

Nu skal vi beregne den oversættelsesposition, som billedet skal animeres fra. Vi skalerer billedet fra midten, så vi er nødt til at anvende vores translate i betragtning af at vi bare flytter midten af ​​billedet. Så vi gør bare noget relativt let matematik ved at indtage kildepositionen plus halvdelen af ​​kildedimensionen. Dette ville svare til dette:

const translateInitX = source.pageX + source.width / 2;
const translateInitY = source.pageY + source.height / 2;
const translateDestX = destination.pageX + destination.width / 2;
const translateDestY = destination.pageY + destination.height / 2;

Vi kan nu beregne den oversatte position med forskellen mellem midten af ​​kildebilledet og destinationsbilledet

const openingInitTranslateX = translateInitX - translateDestX;
const openingInitTranslateY = translateInitY - translateDestY;

Med denne fundne startskala og oversætte værdier kan vi animere vha. Den animerede api.

Det er det. Vi har nu overgangsarbejde. Vi kan nu bruge useNativeDriver, da vi nu kun animerer egenskaber, der ikke er layout.

Trin 4: Skjul kilde- og destinationsbillede under overgangen

I den forrige gif så vi, at under overgangen var det klikkede billede stadig i samme position, og destinationsbilledet dukkede op før overgangen var afsluttet.

Lad os skjule kilden og destinationsbilledet under overgangen, så det ser ud som det klikkede billede er det, der animeres til detaljeskærm.

Lad nu se output.

Trin 5: Håndter tilbage-knappen

Under overgangen til detaljeskærm ved hjælp af Animated.timing () ændrer vi AnimatedValue fra 0 til 1. Så når der klikkes på tilbage-knappen, skal vi bare ændre AnimatedValue fra 1 til o.

Det er det. Du kan tjekke koden på Github og prøve demoen på Expo.

Tjek også Eric Vicenti's udsendelse ved overgang til delt element.

Tak for at du tog tid og læste dette indlæg. Hvis du fandt dette nyttigt, så klap og del det. Du kan oprette forbindelse til mig på Twitter @narendra_shetty.