or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

animation.mdcore-components.mdindex.mdnative-bridge.mdplatform-apis.mdreact-hooks.mdstyling.mduser-interaction.md

animation.mddocs/

0

# React Native Animation & Interaction

1

2

React Native provides powerful animation and interaction APIs for creating smooth, engaging user experiences with declarative animations and gesture handling.

3

4

## Installation

5

6

```bash

7

npm install react-native

8

```

9

10

## Core Animation API

11

12

### Animated

13

14

The primary animation library providing declarative, composable animations with performance optimization.

15

16

```javascript { .api }

17

// ESM

18

import {Animated} from 'react-native';

19

20

// CommonJS

21

const {Animated} = require('react-native');

22

23

// Basic animated value

24

const fadeAnim = new Animated.Value(0);

25

26

// Animate to value

27

Animated.timing(fadeAnim, {

28

toValue: 1,

29

duration: 1000,

30

useNativeDriver: true, // Use native driver for performance

31

}).start();

32

33

// Basic fade in animation

34

function FadeInView({children, style, ...props}) {

35

const fadeAnim = useRef(new Animated.Value(0)).current;

36

37

useEffect(() => {

38

Animated.timing(fadeAnim, {

39

toValue: 1,

40

duration: 1000,

41

useNativeDriver: true,

42

}).start();

43

}, [fadeAnim]);

44

45

return (

46

<Animated.View

47

style={[style, {opacity: fadeAnim}]}

48

{...props}

49

>

50

{children}

51

</Animated.View>

52

);

53

}

54

55

// Multiple animated values

56

function SlideUpView({children}) {

57

const slideAnim = useRef(new Animated.Value(50)).current;

58

const fadeAnim = useRef(new Animated.Value(0)).current;

59

60

useEffect(() => {

61

Animated.parallel([

62

Animated.timing(slideAnim, {

63

toValue: 0,

64

duration: 500,

65

useNativeDriver: true,

66

}),

67

Animated.timing(fadeAnim, {

68

toValue: 1,

69

duration: 500,

70

useNativeDriver: true,

71

}),

72

]).start();

73

}, []);

74

75

return (

76

<Animated.View

77

style={{

78

opacity: fadeAnim,

79

transform: [{translateY: slideAnim}],

80

}}

81

>

82

{children}

83

</Animated.View>

84

);

85

}

86

87

// Animated components

88

const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity);

89

const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);

90

91

// Scale animation on press

92

function ScaleButton({children, onPress}) {

93

const scaleAnim = useRef(new Animated.Value(1)).current;

94

95

const handlePressIn = () => {

96

Animated.spring(scaleAnim, {

97

toValue: 0.95,

98

useNativeDriver: true,

99

}).start();

100

};

101

102

const handlePressOut = () => {

103

Animated.spring(scaleAnim, {

104

toValue: 1,

105

useNativeDriver: true,

106

}).start();

107

};

108

109

return (

110

<AnimatedTouchable

111

onPressIn={handlePressIn}

112

onPressOut={handlePressOut}

113

onPress={onPress}

114

style={{

115

transform: [{scale: scaleAnim}],

116

}}

117

>

118

{children}

119

</AnimatedTouchable>

120

);

121

}

122

123

// Interpolation for complex animations

124

function RotatingView({children}) {

125

const rotateAnim = useRef(new Animated.Value(0)).current;

126

127

useEffect(() => {

128

Animated.loop(

129

Animated.timing(rotateAnim, {

130

toValue: 1,

131

duration: 2000,

132

useNativeDriver: true,

133

})

134

).start();

135

}, []);

136

137

const rotate = rotateAnim.interpolate({

138

inputRange: [0, 1],

139

outputRange: ['0deg', '360deg'],

140

});

141

142

return (

143

<Animated.View style={{transform: [{rotate}]}}>

144

{children}

145

</Animated.View>

146

);

147

}

148

149

// Color interpolation

150

function ColorChangingView() {

151

const colorAnim = useRef(new Animated.Value(0)).current;

152

153

useEffect(() => {

154

Animated.loop(

155

Animated.sequence([

156

Animated.timing(colorAnim, {

157

toValue: 1,

158

duration: 1000,

159

useNativeDriver: false, // Color animations need JS driver

160

}),

161

Animated.timing(colorAnim, {

162

toValue: 0,

163

duration: 1000,

164

useNativeDriver: false,

165

}),

166

])

167

).start();

168

}, []);

169

170

const backgroundColor = colorAnim.interpolate({

171

inputRange: [0, 1],

172

outputRange: ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'],

173

});

174

175

return (

176

<Animated.View style={{backgroundColor, width: 100, height: 100}} />

177

);

178

}

179

180

// Chained animations

181

function ChainedAnimation() {

182

const translateX = useRef(new Animated.Value(0)).current;

183

const scale = useRef(new Animated.Value(1)).current;

184

185

const startAnimation = () => {

186

Animated.sequence([

187

Animated.timing(translateX, {

188

toValue: 100,

189

duration: 500,

190

useNativeDriver: true,

191

}),

192

Animated.spring(scale, {

193

toValue: 1.5,

194

useNativeDriver: true,

195

}),

196

Animated.delay(500),

197

Animated.parallel([

198

Animated.timing(translateX, {

199

toValue: 0,

200

duration: 500,

201

useNativeDriver: true,

202

}),

203

Animated.spring(scale, {

204

toValue: 1,

205

useNativeDriver: true,

206

}),

207

]),

208

]).start();

209

};

210

211

return (

212

<View>

213

<Animated.View

214

style={{

215

transform: [

216

{translateX},

217

{scale},

218

],

219

}}

220

>

221

<Text>Animated Box</Text>

222

</Animated.View>

223

<Button title="Start Animation" onPress={startAnimation} />

224

</View>

225

);

226

}

227

228

// Value listeners

229

function AnimationWithCallback() {

230

const slideAnim = useRef(new Animated.Value(0)).current;

231

232

const animate = () => {

233

slideAnim.setValue(0);

234

235

Animated.timing(slideAnim, {

236

toValue: 100,

237

duration: 1000,

238

useNativeDriver: true,

239

}).start((finished) => {

240

if (finished) {

241

console.log('Animation completed');

242

} else {

243

console.log('Animation was interrupted');

244

}

245

});

246

};

247

248

// Listen to value changes

249

useEffect(() => {

250

const listener = slideAnim.addListener(({value}) => {

251

console.log('Current value:', value);

252

});

253

254

return () => slideAnim.removeListener(listener);

255

}, []);

256

257

return (

258

<View>

259

<Animated.View

260

style={{

261

transform: [{translateX: slideAnim}],

262

}}

263

>

264

<Text>Moving Box</Text>

265

</Animated.View>

266

<Button title="Animate" onPress={animate} />

267

</View>

268

);

269

}

270

```

271

272

```typescript { .api }

273

// Animated Value types

274

interface AnimatedValue {

275

constructor(value: number): AnimatedValue;

276

setValue(value: number): void;

277

setOffset(offset: number): void;

278

flattenOffset(): void;

279

extractOffset(): void;

280

addListener(callback: (value: {value: number}) => void): string;

281

removeListener(id: string): void;

282

removeAllListeners(): void;

283

stopAnimation(callback?: (value: number) => void): void;

284

resetAnimation(callback?: (value: number) => void): void;

285

interpolate(config: InterpolationConfig): AnimatedValue;

286

}

287

288

interface InterpolationConfig {

289

inputRange: number[];

290

outputRange: number[] | string[];

291

easing?: (input: number) => number;

292

extrapolate?: 'extend' | 'identity' | 'clamp';

293

extrapolateLeft?: 'extend' | 'identity' | 'clamp';

294

extrapolateRight?: 'extend' | 'identity' | 'clamp';

295

}

296

297

// Animation configuration types

298

interface TimingAnimationConfig {

299

toValue: number;

300

easing?: (input: number) => number;

301

duration?: number;

302

delay?: number;

303

useNativeDriver?: boolean;

304

isInteraction?: boolean;

305

}

306

307

interface SpringAnimationConfig {

308

toValue: number;

309

restDisplacementThreshold?: number;

310

overshootClamping?: boolean;

311

restSpeedThreshold?: number;

312

velocity?: number;

313

bounciness?: number;

314

speed?: number;

315

tension?: number;

316

friction?: number;

317

stiffness?: number;

318

damping?: number;

319

mass?: number;

320

useNativeDriver?: boolean;

321

isInteraction?: boolean;

322

}

323

324

interface DecayAnimationConfig {

325

velocity: number;

326

deceleration?: number;

327

isInteraction?: boolean;

328

useNativeDriver?: boolean;

329

}

330

331

// Animation composition types

332

interface CompositeAnimation {

333

start(callback?: (finished: boolean) => void): void;

334

stop(): void;

335

reset(): void;

336

}

337

338

// Animated component types

339

interface AnimatedStatic {

340

// Core values

341

Value: typeof AnimatedValue;

342

ValueXY: typeof AnimatedValueXY;

343

344

// Animated components

345

View: React.ComponentType<Animated.AnimatedProps<ViewProps>>;

346

Text: React.ComponentType<Animated.AnimatedProps<TextProps>>;

347

Image: React.ComponentType<Animated.AnimatedProps<ImageProps>>;

348

ScrollView: React.ComponentType<Animated.AnimatedProps<ScrollViewProps>>;

349

FlatList: React.ComponentType<Animated.AnimatedProps<FlatListProps<any>>>;

350

SectionList: React.ComponentType<Animated.AnimatedProps<SectionListProps<any>>>;

351

352

// Animation methods

353

timing(value: AnimatedValue, config: TimingAnimationConfig): CompositeAnimation;

354

spring(value: AnimatedValue, config: SpringAnimationConfig): CompositeAnimation;

355

decay(value: AnimatedValue, config: DecayAnimationConfig): CompositeAnimation;

356

357

// Composition methods

358

parallel(animations: CompositeAnimation[], config?: {stopTogether?: boolean}): CompositeAnimation;

359

sequence(animations: CompositeAnimation[]): CompositeAnimation;

360

stagger(time: number, animations: CompositeAnimation[]): CompositeAnimation;

361

delay(time: number): CompositeAnimation;

362

loop(animation: CompositeAnimation, config?: {iterations?: number}): CompositeAnimation;

363

364

// Utility methods

365

add(a: AnimatedValue, b: AnimatedValue): AnimatedValue;

366

subtract(a: AnimatedValue, b: AnimatedValue): AnimatedValue;

367

divide(a: AnimatedValue, b: AnimatedValue): AnimatedValue;

368

multiply(a: AnimatedValue, b: AnimatedValue): AnimatedValue;

369

modulo(a: AnimatedValue, modulus: number): AnimatedValue;

370

diffClamp(a: AnimatedValue, min: number, max: number): AnimatedValue;

371

372

// Component creation

373

createAnimatedComponent<T extends React.ComponentType<any>>(component: T): React.ComponentType<Animated.AnimatedProps<React.ComponentProps<T>>>;

374

375

// Events

376

event(argMapping: any[], config?: {useNativeDriver?: boolean; listener?: Function}): Function;

377

}

378

379

namespace Animated {

380

interface AnimatedProps<T> {

381

[K in keyof T]: K extends 'style'

382

? StyleProp<T[K]> | AnimatedValue | {[P in keyof T[K]]: T[K][P] | AnimatedValue}

383

: T[K];

384

}

385

}

386

```

387

388

### Easing

389

390

Easing functions for smooth animation transitions and natural motion curves.

391

392

```javascript { .api }

393

import {Easing} from 'react-native';

394

395

// Basic easing functions

396

const fadeIn = () => {

397

Animated.timing(fadeAnim, {

398

toValue: 1,

399

duration: 1000,

400

easing: Easing.ease, // Default easing

401

useNativeDriver: true,

402

}).start();

403

};

404

405

// Different easing curves

406

const easingExamples = {

407

// Basic curves

408

linear: Easing.linear,

409

ease: Easing.ease,

410

quad: Easing.quad,

411

cubic: Easing.cubic,

412

413

// Directional easing

414

easeIn: Easing.in(Easing.quad),

415

easeOut: Easing.out(Easing.quad),

416

easeInOut: Easing.inOut(Easing.quad),

417

418

// Bezier curves

419

customBezier: Easing.bezier(0.25, 0.1, 0.25, 1),

420

421

// Bounce and elastic

422

bounce: Easing.bounce,

423

elastic: Easing.elastic(1),

424

425

// Stepped animation

426

steps: Easing.step0,

427

428

// Circle easing

429

circle: Easing.circle,

430

431

// Sine easing

432

sine: Easing.sin,

433

434

// Exponential easing

435

expo: Easing.exp,

436

437

// Back easing (overshoot)

438

back: Easing.back(1.5),

439

440

// Polynomial easing

441

poly: Easing.poly(4),

442

};

443

444

// Custom easing function

445

const customEasing = (t) => {

446

return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;

447

};

448

449

// Easing comparison component

450

function EasingComparison() {

451

const animations = useRef(

452

Object.keys(easingExamples).reduce((acc, key) => {

453

acc[key] = new Animated.Value(0);

454

return acc;

455

}, {})

456

).current;

457

458

const startAnimations = () => {

459

Object.keys(easingExamples).forEach((key) => {

460

animations[key].setValue(0);

461

462

Animated.timing(animations[key], {

463

toValue: 200,

464

duration: 2000,

465

easing: easingExamples[key],

466

useNativeDriver: true,

467

}).start();

468

});

469

};

470

471

return (

472

<View>

473

<Button title="Start Easing Comparison" onPress={startAnimations} />

474

{Object.keys(easingExamples).map((key) => (

475

<View key={key} style={styles.easingRow}>

476

<Text style={styles.easingLabel}>{key}</Text>

477

<Animated.View

478

style={[

479

styles.easingBox,

480

{transform: [{translateX: animations[key]}]},

481

]}

482

/>

483

</View>

484

))}

485

</View>

486

);

487

}

488

489

// Bouncy button with back easing

490

function BouncyButton({children, onPress}) {

491

const scaleAnim = useRef(new Animated.Value(1)).current;

492

493

const handlePress = () => {

494

Animated.sequence([

495

Animated.timing(scaleAnim, {

496

toValue: 0.8,

497

duration: 100,

498

easing: Easing.in(Easing.back(2)),

499

useNativeDriver: true,

500

}),

501

Animated.timing(scaleAnim, {

502

toValue: 1,

503

duration: 300,

504

easing: Easing.out(Easing.back(2)),

505

useNativeDriver: true,

506

}),

507

]).start();

508

509

onPress?.();

510

};

511

512

return (

513

<AnimatedTouchable

514

onPress={handlePress}

515

style={{

516

transform: [{scale: scaleAnim}],

517

}}

518

>

519

{children}

520

</AnimatedTouchable>

521

);

522

}

523

524

// Progress indicator with stepped animation

525

function SteppedProgress({progress}) {

526

const stepAnim = useRef(new Animated.Value(0)).current;

527

528

useEffect(() => {

529

Animated.timing(stepAnim, {

530

toValue: progress,

531

duration: 1000,

532

easing: Easing.step0, // Stepped animation

533

useNativeDriver: false,

534

}).start();

535

}, [progress]);

536

537

const width = stepAnim.interpolate({

538

inputRange: [0, 1],

539

outputRange: ['0%', '100%'],

540

});

541

542

return (

543

<View style={styles.progressContainer}>

544

<Animated.View style={[styles.progressBar, {width}]} />

545

</View>

546

);

547

}

548

```

549

550

```typescript { .api }

551

interface EasingStatic {

552

// Basic easing functions

553

linear: (t: number) => number;

554

ease: (t: number) => number;

555

quad: (t: number) => number;

556

cubic: (t: number) => number;

557

poly(n: number): (t: number) => number;

558

sin: (t: number) => number;

559

circle: (t: number) => number;

560

exp: (t: number) => number;

561

562

// Directional modifiers

563

in(easing: (t: number) => number): (t: number) => number;

564

out(easing: (t: number) => number): (t: number) => number;

565

inOut(easing: (t: number) => number): (t: number) => number;

566

567

// Special easing functions

568

elastic(bounciness?: number): (t: number) => number;

569

back(s?: number): (t: number) => number;

570

bounce: (t: number) => number;

571

572

// Bezier curve

573

bezier(x1: number, y1: number, x2: number, y2: number): (t: number) => number;

574

575

// Stepped animation

576

step0: (t: number) => number;

577

step1: (t: number) => number;

578

}

579

```

580

581

### LayoutAnimation

582

583

Automatically animate layout changes without explicit animation code.

584

585

```javascript { .api }

586

import {LayoutAnimation} from 'react-native';

587

588

// Basic layout animation

589

function AnimatedList() {

590

const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);

591

592

const addItem = () => {

593

LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);

594

setItems([...items, `Item ${items.length + 1}`]);

595

};

596

597

const removeItem = (index) => {

598

LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);

599

setItems(items.filter((_, i) => i !== index));

600

};

601

602

return (

603

<View>

604

<Button title="Add Item" onPress={addItem} />

605

{items.map((item, index) => (

606

<TouchableOpacity

607

key={index}

608

onPress={() => removeItem(index)}

609

style={styles.listItem}

610

>

611

<Text>{item}</Text>

612

</TouchableOpacity>

613

))}

614

</View>

615

);

616

}

617

618

// Custom layout animation config

619

const customLayoutAnimation = {

620

duration: 300,

621

create: {

622

type: LayoutAnimation.Types.easeInEaseOut,

623

property: LayoutAnimation.Properties.opacity,

624

},

625

update: {

626

type: LayoutAnimation.Types.easeInEaseOut,

627

},

628

delete: {

629

type: LayoutAnimation.Types.easeInEaseOut,

630

property: LayoutAnimation.Properties.opacity,

631

},

632

};

633

634

function CustomAnimatedComponent() {

635

const [isExpanded, setIsExpanded] = useState(false);

636

637

const toggleExpanded = () => {

638

LayoutAnimation.configureNext(customLayoutAnimation);

639

setIsExpanded(!isExpanded);

640

};

641

642

return (

643

<View>

644

<TouchableOpacity onPress={toggleExpanded} style={styles.header}>

645

<Text>Tap to expand</Text>

646

</TouchableOpacity>

647

648

{isExpanded && (

649

<View style={styles.content}>

650

<Text>This content animates in and out</Text>

651

<Text>Using LayoutAnimation</Text>

652

</View>

653

)}

654

</View>

655

);

656

}

657

658

// Grid layout animation

659

function AnimatedGrid() {

660

const [numColumns, setNumColumns] = useState(2);

661

662

const toggleColumns = () => {

663

LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);

664

setNumColumns(numColumns === 2 ? 3 : 2);

665

};

666

667

return (

668

<View>

669

<Button

670

title={`Switch to ${numColumns === 2 ? 3 : 2} columns`}

671

onPress={toggleColumns}

672

/>

673

674

<FlatList

675

data={Array.from({length: 20}, (_, i) => ({id: i, title: `Item ${i}`}))}

676

numColumns={numColumns}

677

key={numColumns} // Force re-render for column change

678

renderItem={({item}) => (

679

<View style={[styles.gridItem, {flex: 1/numColumns}]}>

680

<Text>{item.title}</Text>

681

</View>

682

)}

683

/>

684

</View>

685

);

686

}

687

688

// Layout animation with callbacks

689

function CallbackLayoutAnimation() {

690

const [items, setItems] = useState(['A', 'B', 'C']);

691

692

const shuffleItems = () => {

693

LayoutAnimation.configureNext(

694

LayoutAnimation.Presets.easeInEaseOut,

695

() => {

696

console.log('Layout animation finished');

697

},

698

(error) => {

699

console.error('Layout animation failed:', error);

700

}

701

);

702

703

const shuffled = [...items].sort(() => Math.random() - 0.5);

704

setItems(shuffled);

705

};

706

707

return (

708

<View>

709

<Button title="Shuffle Items" onPress={shuffleItems} />

710

{items.map((item, index) => (

711

<View key={item} style={styles.shuffleItem}>

712

<Text>{item}</Text>

713

</View>

714

))}

715

</View>

716

);

717

}

718

719

// Enable layout animations on Android

720

if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {

721

UIManager.setLayoutAnimationEnabledExperimental(true);

722

}

723

```

724

725

```typescript { .api }

726

interface LayoutAnimationStatic {

727

// Configure next layout change

728

configureNext(

729

config: LayoutAnimationConfig,

730

onAnimationDidEnd?: () => void,

731

onAnimationDidFail?: (error: any) => void

732

): void;

733

734

// Preset configurations

735

Presets: {

736

easeInEaseOut: LayoutAnimationConfig;

737

linear: LayoutAnimationConfig;

738

spring: LayoutAnimationConfig;

739

};

740

741

// Animation types

742

Types: {

743

spring: string;

744

linear: string;

745

easeInEaseOut: string;

746

easeIn: string;

747

easeOut: string;

748

keyboard: string;

749

};

750

751

// Animation properties

752

Properties: {

753

opacity: string;

754

scaleX: string;

755

scaleY: string;

756

scaleXY: string;

757

};

758

}

759

760

interface LayoutAnimationConfig {

761

duration?: number;

762

create?: LayoutAnimationAnim;

763

update?: LayoutAnimationAnim;

764

delete?: LayoutAnimationAnim;

765

}

766

767

interface LayoutAnimationAnim {

768

duration?: number;

769

delay?: number;

770

springDamping?: number;

771

initialVelocity?: number;

772

type?: string;

773

property?: string;

774

}

775

```

776

777

## Gesture and Interaction APIs

778

779

### PanResponder

780

781

Handle complex gesture recognition and touch events for custom interactive components.

782

783

```javascript { .api }

784

import {PanResponder} from 'react-native';

785

786

// Basic draggable component

787

function DraggableView({children}) {

788

const pan = useRef(new Animated.ValueXY()).current;

789

790

const panResponder = useRef(

791

PanResponder.create({

792

onMoveShouldSetPanResponder: () => true,

793

onPanResponderGrant: () => {

794

pan.setOffset({

795

x: pan.x._value,

796

y: pan.y._value,

797

});

798

},

799

onPanResponderMove: Animated.event([

800

null,

801

{dx: pan.x, dy: pan.y},

802

], {useNativeDriver: false}),

803

onPanResponderRelease: () => {

804

pan.flattenOffset();

805

},

806

})

807

).current;

808

809

return (

810

<Animated.View

811

style={{

812

transform: [{translateX: pan.x}, {translateY: pan.y}],

813

}}

814

{...panResponder.panHandlers}

815

>

816

{children}

817

</Animated.View>

818

);

819

}

820

821

// Swipe-to-dismiss component

822

function SwipeToDismiss({children, onDismiss}) {

823

const translateX = useRef(new Animated.Value(0)).current;

824

const [dismissed, setDismissed] = useState(false);

825

826

const panResponder = useRef(

827

PanResponder.create({

828

onMoveShouldSetPanResponder: (_, gestureState) => {

829

return Math.abs(gestureState.dx) > Math.abs(gestureState.dy);

830

},

831

832

onPanResponderMove: (_, gestureState) => {

833

translateX.setValue(gestureState.dx);

834

},

835

836

onPanResponderRelease: (_, gestureState) => {

837

const threshold = 120;

838

839

if (Math.abs(gestureState.dx) > threshold) {

840

// Dismiss

841

const toValue = gestureState.dx > 0 ? 300 : -300;

842

843

Animated.timing(translateX, {

844

toValue,

845

duration: 200,

846

useNativeDriver: true,

847

}).start(() => {

848

setDismissed(true);

849

onDismiss?.();

850

});

851

} else {

852

// Return to original position

853

Animated.spring(translateX, {

854

toValue: 0,

855

useNativeDriver: true,

856

}).start();

857

}

858

},

859

})

860

).current;

861

862

if (dismissed) return null;

863

864

return (

865

<Animated.View

866

style={{

867

transform: [{translateX}],

868

}}

869

{...panResponder.panHandlers}

870

>

871

{children}

872

</Animated.View>

873

);

874

}

875

876

// Scalable and rotatable view

877

function ScalableRotatableView({children}) {

878

const scale = useRef(new Animated.Value(1)).current;

879

const rotate = useRef(new Animated.Value(0)).current;

880

881

const lastScale = useRef(1);

882

const lastRotate = useRef(0);

883

884

const panResponder = useRef(

885

PanResponder.create({

886

onMoveShouldSetPanResponder: () => true,

887

onMoveShouldSetPanResponderCapture: () => true,

888

889

onPanResponderGrant: () => {

890

lastScale.current = scale._value;

891

lastRotate.current = rotate._value;

892

},

893

894

onPanResponderMove: (evt, gestureState) => {

895

// Multi-touch gestures would require additional logic

896

// This is a simplified version for single touch

897

898

// Scale based on vertical movement

899

const newScale = lastScale.current + gestureState.dy / 200;

900

scale.setValue(Math.max(0.5, Math.min(2, newScale)));

901

902

// Rotate based on horizontal movement

903

const newRotate = lastRotate.current + gestureState.dx / 100;

904

rotate.setValue(newRotate);

905

},

906

907

onPanResponderRelease: () => {

908

// Optionally snap back to default values

909

Animated.parallel([

910

Animated.spring(scale, {

911

toValue: 1,

912

useNativeDriver: true,

913

}),

914

Animated.spring(rotate, {

915

toValue: 0,

916

useNativeDriver: true,

917

}),

918

]).start();

919

},

920

})

921

).current;

922

923

const rotateStr = rotate.interpolate({

924

inputRange: [-1, 1],

925

outputRange: ['-45deg', '45deg'],

926

});

927

928

return (

929

<Animated.View

930

style={{

931

transform: [

932

{scale},

933

{rotate: rotateStr},

934

],

935

}}

936

{...panResponder.panHandlers}

937

>

938

{children}

939

</Animated.View>

940

);

941

}

942

943

// Pull-to-refresh with PanResponder

944

function CustomPullToRefresh({children, onRefresh}) {

945

const translateY = useRef(new Animated.Value(0)).current;

946

const [isRefreshing, setIsRefreshing] = useState(false);

947

948

const panResponder = useRef(

949

PanResponder.create({

950

onMoveShouldSetPanResponder: (_, gestureState) => {

951

return gestureState.dy > 0 && gestureState.vy > 0;

952

},

953

954

onPanResponderMove: (_, gestureState) => {

955

if (gestureState.dy > 0 && !isRefreshing) {

956

translateY.setValue(Math.min(gestureState.dy, 100));

957

}

958

},

959

960

onPanResponderRelease: (_, gestureState) => {

961

if (gestureState.dy > 60 && !isRefreshing) {

962

setIsRefreshing(true);

963

964

Animated.timing(translateY, {

965

toValue: 60,

966

duration: 200,

967

useNativeDriver: true,

968

}).start();

969

970

// Simulate refresh

971

onRefresh?.().finally(() => {

972

setIsRefreshing(false);

973

974

Animated.timing(translateY, {

975

toValue: 0,

976

duration: 300,

977

useNativeDriver: true,

978

}).start();

979

});

980

} else {

981

Animated.timing(translateY, {

982

toValue: 0,

983

duration: 200,

984

useNativeDriver: true,

985

}).start();

986

}

987

},

988

})

989

).current;

990

991

return (

992

<View style={{flex: 1}}>

993

<Animated.View

994

style={{

995

position: 'absolute',

996

top: -50,

997

left: 0,

998

right: 0,

999

height: 50,

1000

justifyContent: 'center',

1001

alignItems: 'center',

1002

transform: [{translateY}],

1003

}}

1004

>

1005

{isRefreshing ? (

1006

<ActivityIndicator size="small" />

1007

) : (

1008

<Text>Pull to refresh</Text>

1009

)}

1010

</Animated.View>

1011

1012

<Animated.View

1013

style={{

1014

flex: 1,

1015

transform: [{translateY}],

1016

}}

1017

{...panResponder.panHandlers}

1018

>

1019

{children}

1020

</Animated.View>

1021

</View>

1022

);

1023

}

1024

```

1025

1026

```typescript { .api }

1027

interface PanResponderStatic {

1028

create(config: PanResponderConfig): PanResponderInstance;

1029

}

1030

1031

interface PanResponderInstance {

1032

panHandlers: {

1033

onStartShouldSetResponder: (evt: GestureResponderEvent) => boolean;

1034

onMoveShouldSetResponder: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;

1035

onResponderGrant: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;

1036

onResponderMove: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;

1037

onResponderRelease: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;

1038

onResponderTerminate: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;

1039

onStartShouldSetResponderCapture: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;

1040

onMoveShouldSetResponderCapture: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;

1041

};

1042

}

1043

1044

interface PanResponderConfig {

1045

onStartShouldSetPanResponder?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;

1046

onMoveShouldSetPanResponder?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;

1047

onPanResponderGrant?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;

1048

onPanResponderMove?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;

1049

onPanResponderRelease?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;

1050

onPanResponderTerminate?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;

1051

onPanResponderReject?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;

1052

onStartShouldSetPanResponderCapture?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;

1053

onMoveShouldSetPanResponderCapture?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;

1054

onPanResponderStart?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;

1055

onPanResponderEnd?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;

1056

onShouldBlockNativeResponder?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;

1057

}

1058

1059

interface PanResponderGestureState {

1060

stateID: number;

1061

moveX: number;

1062

moveY: number;

1063

x0: number;

1064

y0: number;

1065

dx: number;

1066

dy: number;

1067

vx: number;

1068

vy: number;

1069

numberActiveTouches: number;

1070

}

1071

```

1072

1073

### InteractionManager

1074

1075

Schedule work after interactions have completed to maintain smooth animations and user experience.

1076

1077

```javascript { .api }

1078

import {InteractionManager} from 'react-native';

1079

1080

// Defer expensive operations after interactions

1081

function ExpensiveComponent() {

1082

const [data, setData] = useState(null);

1083

const [isLoading, setIsLoading] = useState(true);

1084

1085

useEffect(() => {

1086

// Show loading state immediately

1087

setIsLoading(true);

1088

1089

// Wait for interactions to complete, then load data

1090

const interactionPromise = InteractionManager.runAfterInteractions(() => {

1091

// Expensive operation

1092

return fetchLargeDataset().then(result => {

1093

setData(result);

1094

setIsLoading(false);

1095

});

1096

});

1097

1098

return () => {

1099

interactionPromise.cancel();

1100

};

1101

}, []);

1102

1103

if (isLoading) {

1104

return <ActivityIndicator size="large" />;

1105

}

1106

1107

return (

1108

<FlatList

1109

data={data}

1110

renderItem={({item}) => <ExpensiveListItem item={item} />}

1111

/>

1112

);

1113

}

1114

1115

// Navigation transition with deferred loading

1116

function NavigationScreen({route}) {

1117

const [content, setContent] = useState(null);

1118

1119

useEffect(() => {

1120

// Run after navigation animation completes

1121

InteractionManager.runAfterInteractions(() => {

1122

loadScreenContent(route.params.id).then(setContent);

1123

});

1124

}, [route.params.id]);

1125

1126

return (

1127

<View style={styles.container}>

1128

<Header title="Screen Title" />

1129

{content ? (

1130

<Content data={content} />

1131

) : (

1132

<LoadingSkeleton />

1133

)}

1134

</View>

1135

);

1136

}

1137

1138

// Custom hook for deferred execution

1139

function useAfterInteractions(callback, deps = []) {

1140

const [isReady, setIsReady] = useState(false);

1141

1142

useEffect(() => {

1143

let cancelled = false;

1144

1145

const handle = InteractionManager.runAfterInteractions(() => {

1146

if (!cancelled) {

1147

callback();

1148

setIsReady(true);

1149

}

1150

});

1151

1152

return () => {

1153

cancelled = true;

1154

handle.cancel();

1155

};

1156

}, deps);

1157

1158

return isReady;

1159

}

1160

1161

// Usage with custom hook

1162

function DeferredComponent() {

1163

const [heavyData, setHeavyData] = useState(null);

1164

1165

const isReady = useAfterInteractions(() => {

1166

processHeavyData().then(setHeavyData);

1167

}, []);

1168

1169

return (

1170

<View>

1171

{isReady && heavyData ? (

1172

<HeavyDataView data={heavyData} />

1173

) : (

1174

<PlaceholderView />

1175

)}

1176

</View>

1177

);

1178

}

1179

1180

// Creating interaction handles manually

1181

function InteractiveAnimation() {

1182

const scaleAnim = useRef(new Animated.Value(1)).current;

1183

const [isInteracting, setIsInteracting] = useState(false);

1184

1185

const startAnimation = () => {

1186

// Create interaction handle

1187

const handle = InteractionManager.createInteractionHandle();

1188

setIsInteracting(true);

1189

1190

Animated.sequence([

1191

Animated.timing(scaleAnim, {

1192

toValue: 1.2,

1193

duration: 500,

1194

useNativeDriver: true,

1195

}),

1196

Animated.timing(scaleAnim, {

1197

toValue: 1,

1198

duration: 500,

1199

useNativeDriver: true,

1200

}),

1201

]).start(() => {

1202

// Clear interaction handle when animation completes

1203

InteractionManager.clearInteractionHandle(handle);

1204

setIsInteracting(false);

1205

});

1206

};

1207

1208

return (

1209

<View>

1210

<Animated.View style={{transform: [{scale: scaleAnim}]}}>

1211

<TouchableOpacity onPress={startAnimation}>

1212

<Text>Animate</Text>

1213

</TouchableOpacity>

1214

</Animated.View>

1215

1216

{isInteracting && (

1217

<Text>Animation in progress...</Text>

1218

)}

1219

</View>

1220

);

1221

}

1222

1223

// Batch processing with interaction management

1224

function BatchProcessor({items}) {

1225

const [processedItems, setProcessedItems] = useState([]);

1226

const [isProcessing, setIsProcessing] = useState(false);

1227

1228

const processBatch = async () => {

1229

setIsProcessing(true);

1230

1231

// Process items in batches after interactions

1232

const batchSize = 10;

1233

const batches = [];

1234

1235

for (let i = 0; i < items.length; i += batchSize) {

1236

batches.push(items.slice(i, i + batchSize));

1237

}

1238

1239

for (const batch of batches) {

1240

await new Promise(resolve => {

1241

InteractionManager.runAfterInteractions(() => {

1242

const processed = batch.map(processItem);

1243

setProcessedItems(prev => [...prev, ...processed]);

1244

resolve();

1245

});

1246

});

1247

}

1248

1249

setIsProcessing(false);

1250

};

1251

1252

return (

1253

<View>

1254

<Button

1255

title="Process Items"

1256

onPress={processBatch}

1257

disabled={isProcessing}

1258

/>

1259

1260

<FlatList

1261

data={processedItems}

1262

renderItem={({item}) => <ProcessedItem item={item} />}

1263

/>

1264

1265

{isProcessing && (

1266

<ActivityIndicator style={styles.processingIndicator} />

1267

)}

1268

</View>

1269

);

1270

}

1271

```

1272

1273

```typescript { .api }

1274

interface InteractionManagerStatic {

1275

// Run after interactions

1276

runAfterInteractions(callback: () => void | Promise<any>): {

1277

then: (callback: () => void) => {cancel: () => void};

1278

done: (...args: any[]) => any;

1279

cancel: () => void;

1280

};

1281

1282

// Manual interaction handles

1283

createInteractionHandle(): number;

1284

clearInteractionHandle(handle: number): void;

1285

1286

// Event listeners

1287

addListener?(callback: () => void): void;

1288

1289

// Promise-based API

1290

setDeadline?(deadline: number): void;

1291

}

1292

1293

interface InteractionHandle {

1294

then(callback: () => void): {cancel: () => void};

1295

done(...args: any[]): any;

1296

cancel(): void;

1297

}

1298

```

1299

1300

This comprehensive animation and interaction documentation provides developers with all the tools needed to create smooth, engaging user experiences with React Native's powerful animation APIs and gesture handling capabilities.