Core Concepts
Kuikly provides a completecomponent visibility event system, used to listen for changes in a component's visibility state within a scrolling container or page. Four key elements:
- Visible window: the nearest scrolling container (Scroller/List/WaterfallList), ModalView, or Pager
- Visibility state: four states + percentage (WILL_APPEAR → DID_APPEAR → WILL_DISAPPEAR → DID_DISAPPEAR)
- Trigger timing: scroll offset changes, child view layout completion, visible area margin changes, component removal
- Application scenarios: exposure reporting, lazy loading, playback control, visible percentage monitoring
Visible Window Lookup Rules
The system starts from the component's parentand traverses upward, finding the first matching ancestor as the visible window:
组件 → parent → ... → ScrollerView(Scroller/List/WaterfallList/PageList)→ 命中
→ ModalView → 命中
→ Pager → 命中(兜底)
- For ScrollerView(Scroller/List/WaterfallList): visibility is based on thelist visible areacalculation, subtracting the scroll offset
- For ModalView: visibility is calculated based on the Modal's layout frame
- For Pager: visibility is calculated based on the page's layout frame
Important: visibility is relative to thevisible area of the nearest scrolling container, not the screen position. If screen-position visibility is needed, in nested scrolling container scenarios, you can use the scroll event callback of the outer scrolling container, combined with the list offset, to calculate the node's actual position.
State Machine
┌──────────────────────────────────────────────┐
│ │
▼ │
DID_DISAPPEAR ──→ WILL_APPEAR ──→ DID_APPEAR ──→ WILL_DISAPPEAR ─┘
(完全不可见) (部分可见) (完全可见) (部分可见)
Three spatial relationships and state transitions:
| Spatial relationship | Determination condition | State transition |
|---|---|---|
| Separate(the component is completely outside the window) | The component frame has no intersection with the window | → WILL_DISAPPEAR → DID_DISAPPEAR |
| Contained(the component is completely inside the window) | The component frame is completely inside the window | → WILL_APPEAR → DID_APPEAR |
| Intersecting(the component is partially inside the window) | The component frame partially overlaps with the window | From DID_DISAPPEAR → WILL_APPEAR; from DID_APPEAR → WILL_DISAPPEAR |
Special behavior:
- When the component isremoved(v-if removal, page destruction, etc.), WILL_DISAPPEAR → DID_DISAPPEAR is automatically triggered, and percentage is set to 0
- State changes areasynchronous(scheduled via
addNextTickTask), except when the page is destroyed (executed synchronously)
API Quick Reference
Visibility events
| Event | Description | Callback parameters | import |
|---|---|---|---|
willAppear { } |
The component is about to become visible (partially entering the window) | None | com.tencent.kuikly.core.base.event.willAppear |
didAppear { } |
The component is fully visible (completely inside the window) | None | com.tencent.kuikly.core.base.event.didAppear |
willDisappear { } |
Component is about to become invisible (partially leaving the window) | None | com.tencent.kuikly.core.base.event.willDisappear |
didDisappear { } |
Component is completely invisible (entirely outside the window) | None | com.tencent.kuikly.core.base.event.didDisappear |
appearPercentage { } |
Change in component visible percentage | percentage01: Float([0,1]) |
com.tencent.kuikly.core.base.event.appearPercentage |
appearPercentage percentage calculation logic
| Spatial relationship | percentage value |
|---|---|
| Completely invisible(disjoint) | 0 |
| Completely visible(contained) | 1 |
| Partially visible(intersecting) | Vertical list:可见高度 / 组件高度; Horizontal list (FlexDirection.ROW / ROW_REVERSE):可见宽度 / 组件宽度 |
The percentage value is always clamped to the range [0, 1]. The callback is triggered only when the percentage changes.
Scroller/List/WaterfallList auxiliary properties
| Property | Description | Parameter type | Supported containers |
|---|---|---|---|
visibleAreaIgnoreTopMargin(margin) |
Crop the specified height from the top of the visible window | Float | Scroller、List、WaterfallList |
visibleAreaIgnoreBottomMargin(margin) |
Crop the specified height from the bottom of the visible window | Float | Scroller、List、WaterfallList |
How it works: Shrinks the list's "visibility determination window". After setting, visible window height = list height - marginTop - marginBottom, and child component Y coordinates are also reduced by marginTop. For example, with a list height of 500dp, after setting
visibleAreaIgnoreTopMargin(60f)andvisibleAreaIgnoreBottomMargin(80f), only child components within the middle 360dp area of the list will be determined as visible.Typical scenario: The top/bottom of the list has fixed occluding UI (such as a floating navigation bar, semi-transparent overlay, or bottom toolbar). Child components within the occluded area are "visible" in the list coordinate system, but the user cannot actually see them, so they need to be excluded from exposure determination.
Quick start
didAppear exposure reporting
import com.tencent.kuikly.core.base.event.didAppear
View {
attr {
size(100f, 100f)
backgroundColor(Color.GREEN)
}
event {
didAppear {
// 组件完全可见,执行曝光上报
}
}
}
appearPercentage visible percentage listening
import com.tencent.kuikly.core.base.event.appearPercentage
View {
attr {
size(100f, 100f)
backgroundColor(Color.GREEN)
}
event {
appearPercentage { percentage01 ->
// percentage01 为 [0,1] 的露出百分比
// 1 表示 100% 可见,0 表示 0% 可见
if (percentage01 >= 0.5f) {
// 超过 50% 可见时执行逻辑
}
}
}
}
Full lifecycle listening
import com.tencent.kuikly.core.base.event.willAppear
import com.tencent.kuikly.core.base.event.didAppear
import com.tencent.kuikly.core.base.event.willDisappear
import com.tencent.kuikly.core.base.event.didDisappear
import com.tencent.kuikly.core.base.event.appearPercentage
View {
event {
willAppear {
// 组件将要可见(部分进入窗口)
}
didAppear {
// 组件完全可见 → 曝光上报
}
willDisappear {
// 组件将要不可见(部分离开窗口)
}
didDisappear {
// 组件完全不可见
}
appearPercentage { percentage ->
// 可见百分比变化
}
}
}
visibleAreaIgnoreMargin excludes occluded areas
import com.tencent.kuikly.core.base.event.didAppear
Scroller {
attr {
flex(1f)
// 顶部 60dp 区域内的子组件不参与曝光判定
visibleAreaIgnoreTopMargin(60f)
// 底部 80dp 区域内的子组件不参与曝光判定
visibleAreaIgnoreBottomMargin(80f)
}
View {
attr {
height(120f)
backgroundColor(Color(0xFFE3F2FD))
}
event {
didAppear {
// 只有完全进入中间有效区域(500 - 60 - 80 = 360dp)时才触发
}
}
}
}
Common mistakes and pitfalls
| Incorrect approach | Correct approach | Reason |
|---|---|---|
❌ The root node uses didAppear to listen for page exposure |
✅ Use the page lifecycle pageDidAppear() |
The root node has no scrolling container as a visible window, so behavior is unpredictable |
| ❌ Assuming visibility is relative to screen position | ✅ Visibility is relative to thevisible area of the nearest scrolling container | If screen position is needed, calculate it in combination with the scroll event offset |
❌ Directly perform exposure reporting in appearPercentage directly execute exposure reporting in |
✅ For exposure reporting use didAppear, or add a threshold + deduplication flag |
appearPercentage triggers on every scroll, at an extremely high frequency |
| ❌ Forgetting to import the visibility event extension function | ✅ Add the corresponding import statement | These are Event extension functions, which require an explicit import |
Complete usage example
See EXAMPLES.md