Documentation

animate:flip

Smooth animations when list items are reordered, added, or removed.

Overview

The animate:flip directive automatically animates position changes in lists. When items move, they smoothly transition from their old position to their new position.

Basic Usage

Add animate:flip to items in a LazyColumn, LazyRow, or Column:

<LazyColumn>
  @for (item in items) {
    <Row key={item.id} animate:flip>
      <Text>{item.name}</Text>
    </Row>
  }
</LazyColumn>

Always provide a key attribute when using animate:flip. This helps the framework track which item is which.

Configuration

Customize animation timing:

<Row key={item.id} animate:flip={{ duration: 200 }}>
  <Text>{item.name}</Text>
</Row>

Options

OptionTypeDefaultDescription
durationNumber300Animation duration in milliseconds
easingString"ease"Easing function

Examples

Sortable List

var items = ["Apple", "Banana", "Cherry"]

fun shuffle() {
  items = items.shuffled()
}

<Column>
  <Button onClick={shuffle} text="Shuffle" />

  <LazyColumn gap={8}>
    @for (item in items) {
      <Card key={item} animate:flip={{ duration: 300 }}>
        <Text>{item}</Text>
      </Card>
    }
  </LazyColumn>
</Column>

Drag to Reorder

var todos = [...]

<LazyColumn>
  @for (todo in todos) {
    <Row
      key={todo.id}
      animate:flip={{ duration: 250 }}
      draggable
      onReorder={(from, to) => reorder(from, to)}
    >
      <Text>{todo.text}</Text>
    </Row>
  }
</LazyColumn>

Add/Remove with Animation

var messages = []

fun addMessage(text: String) {
  messages = messages + Message(id = UUID(), text = text)
}

fun removeMessage(id: String) {
  messages = messages.filter { it.id != id }
}

<LazyColumn>
  @for (msg in messages) {
    <Card
      key={msg.id}
      animate:flip
      in:slide={{ from: "bottom" }}
      out:slide={{ to: "right" }}
    >
      <Text>{msg.text}</Text>
      <Button onClick={() => removeMessage(msg.id)} text="Delete" />
    </Card>
  }
</LazyColumn>

Combine animate:flip with in: and out: directives for complete control over enter, exit, and reorder animations.

Filter/Sort Animation

var products = [...]
var filter = "all"

val filtered = when (filter) {
  "featured" -> products.filter { it.featured }
  "sale" -> products.filter { it.onSale }
  else -> products
}

<Column>
  <TabRow>
    <Tab onClick={() => filter = "all"}>All</Tab>
    <Tab onClick={() => filter = "featured"}>Featured</Tab>
    <Tab onClick={() => filter = "sale"}>Sale</Tab>
  </TabRow>

  <LazyVerticalGrid columns={2} gap={12}>
    @for (product in filtered) {
      <Card
        key={product.id}
        animate:flip={{ duration: 200, easing: "ease-out" }}
      >
        <Image src={product.image} />
        <Text>{product.name}</Text>
      </Card>
    }
  </LazyVerticalGrid>
</Column>

How It Works

Under the hood, animate:flip transpiles to Compose's Modifier.animateItem(). The framework tracks each item's position and smoothly animates changes using layout animations.

Performance

  • Animations run on the composition thread for smoothness
  • Only visible items are animated in lazy lists
  • Items outside the viewport skip animation for better performance

See Also