# 滚动与列表组件

## `<scroll-view>`

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| scrollOrientation | `'vertical' \| 'horizontal'` | `'vertical'` | 滚动方向 |
| bounces | boolean | true | iOS 弹性回弹 |
| enableScroll | boolean | true | 是否允许滚动 |
| scrollBarEnable | boolean | true | 是否显示滚动条 |
| upperThreshold | number | 0 | 触发 onScrollToUpper 的距离阈值 (px) |
| lowerThreshold | number | 0 | 触发 onScrollToLower 的距离阈值 (px) |
| initialScrollOffset | number | 0 | 初始滚动偏移 (px)，仅首屏有效 |
| onScrollToUpper | (e: ScrollToUpperEvent) => void | - | 滚动到顶/左 |
| onScrollToLower | (e: ScrollToLowerEvent) => void | - | 滚动到底/右 |
| onScroll | (e: ScrollEvent) => void | - | 滚动中 |
| onScrollEnd | (e: ScrollEndEvent) => void | - | 滚动结束 |
| onContentSizeChanged | (e: ContentSizeChangedEvent) => void | - | 内容尺寸变化 |

```tsx
// 垂直滚动 + 上拉加载
<scroll-view
  style={{ height: '100vh' }}
  lowerThreshold={50}
  onScrollToLower={() => loadMore()}
>
  {items.map(item => <view key={item.id}>{/* ... */}</view>)}
</scroll-view>

// 水平滚动
<scroll-view scrollOrientation="horizontal" style={{ width: '100%' }}>
  {images.map(img => <image key={img.id} src={img.url} style={{ width: '100px', height: '100px' }} />)}
</scroll-view>
```

---

## `<list>` / `list-item`

高性能列表，支持多列/瀑布流。

**`<list>` 必须显式指定 height**，不支持自适应高度。

`list-item` 是原生 Lynx 元素，属性名使用连字符（`item-key`、`sticky-top`），不是驼峰。

### list 属性

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| style | CSSProperties | - | **必须包含 height** |
| scrollOrientation | `'vertical' \| 'horizontal'` | `'vertical'` | 方向 |
| listType | `'single' \| 'flow' \| 'waterfall'` | `'single'` | 布局类型 |
| spanCount | number | 1 | 列数，flow/waterfall 时有效 |
| enableScroll | boolean | true | 是否允许滚动 |
| bounces | boolean | true | iOS 弹性回弹 |
| initialScrollIndex | number | 0 | 渲染后自动滚动到的节点位置 |
| lowerThresholdItemCount | number | 0 | 触发底部事件的剩余子节点数阈值 |
| upperThresholdItemCount | number | 0 | 触发顶部事件的剩余子节点数阈值 |
| scrollEventThrottle | number | 200 | 滚动事件节流间隔 (ms) |
| onScroll | (e: ListScrollEvent) => void | - | 滚动 |
| onScrollToUpper | (e: ListScrollToUpperEvent) => void | - | 滚动到顶部 |
| onScrollToLower | (e: ListScrollToLowerEvent) => void | - | 滚动到底部 |
| onLayoutComplete | (e: ListLayoutCompleteEvent) => void | - | 首屏渲染完成 |

### list-item 属性

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| item-key | string | - | **必填**，唯一标识 |
| sticky-top | boolean | false | 吸顶 |
| sticky-bottom | boolean | false | 吸底 |
| full-span | boolean | false | 多列时占满一行 |
| estimated-main-axis-size-px | number | -1 | 预估主轴尺寸 (px)，瀑布流时建议填 |
| recyclable | boolean | true | 是否可回收 |

```tsx
// 基础列表
<list style={{ height: '400px' }}>
  {data.map(item => (
    <list-item key={item.id} item-key={item.id}>
      <view style={{ padding: '16px' }}><text>{item.title}</text></view>
    </list-item>
  ))}
</list>

// 吸顶 header
<list style={{ height: '400px' }}>
  <list-item item-key="header" sticky-top>
    <view style={{ padding: '12px', background: '#f5f5f5' }}><text>吸顶标题</text></view>
  </list-item>
  {data.map(item => (
    <list-item key={item.id} item-key={item.id}>
      <view style={{ padding: '16px' }}><text>{item.title}</text></view>
    </list-item>
  ))}
</list>

// 瀑布流
<list style={{ height: '100vh' }} listType="waterfall" spanCount={2}>
  {items.map(item => (
    <list-item key={item.id} item-key={item.id} estimated-main-axis-size-px={item.height}>
      <view style={{ height: `${item.height}px` }}><text>{item.title}</text></view>
    </list-item>
  ))}
</list>
```

---

## `<swiper>` / `<swiper-item>`

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| data | T[] | - | 轮播数据 |
| itemWidth | number | - | 每项宽度 |
| itemHeight | number \| `'auto'` | - | 每项高度 |
| children | (prop: RenderFunctionProps\<T\>) => ReactElement | - | 渲染函数，返回 swiper-item |
| containerWidth | number | screenWidth | 容器宽度 |
| initialIndex | number | 0 | 初始索引，仅首屏有效 |
| loop | boolean | false | 循环 |
| duration | number | 500 | 动画时长 (ms) |
| onChange | (current: number) => void | - | 索引变化 |
| onSwipeStart | (current: number) => void | - | 开始滑动 |
| onSwipeStop | (current: number) => void | - | 停止滑动 |

### swiper-item 属性

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| index | number | - | 当前索引 |
| real-index | number | index | 真实索引，循环模式下与 index 不同 |
| style | CSSProperties | - | 样式 |
| overlap | boolean | false | 透明度有问题时使用 |

```tsx
<swiper
  data={images}
  itemWidth={375}
  itemHeight={200}
  loop
  onChange={(current) => console.log('current:', current)}
>
  {({ item, index }) => (
    <swiper-item index={index}>
      <image src={item.url} style={{ width: '100%', height: '100%' }} mode="aspectFill" />
    </swiper-item>
  )}
</swiper>
```
