# list-view

列表组件

在App中,基于recycle-view的list,才能实现长列表的资源自动回收,以保障列表加载很多项目时,屏幕外的资源被有效回收。list-view就是基于recycle-view的list组件。

每个list由1个父组件list-view及若干子组件list-item构成。

在list-item上使用v-for指令循环list-item,自动会回收屏幕外的列表项资源。

list-view和scroll-view都是滚动组件,list适用于长列表场景,其他场景适用于scroll-view。

注意: list-view仅识别list-item、sticky-header组件其他组件无法识别不能正常显示

# # 属性

名称 类型 默认值 描述
scroll-x boolean false 允许横向滚动,不支持同时设置scroll-y属性为true,同时设置true时scroll-y生效
scroll-y boolean true 允许纵向滚动,不支持同时设置scroll-x属性为true,同时设置true时scroll-y生效
rebound boolean true 控制是否回弹效果
upper-threshold number 50 距顶部/左边多远时(单位px),触发 scrolltoupper 事件
lower-threshold number 50 距底部/右边多远时(单位px),触发 scrolltolower 事件
scroll-top number 0 设置竖向滚动条位置
scroll-left number 0 设置横向滚动条位置
show-scrollbar boolean true 控制是否出现滚动条
scroll-into-view string - 值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素
scroll-with-animation boolean false 是否在设置滚动条位置时使用滚动动画,设置false没有滚动动画
refresher-enabled boolean false 开启下拉刷新,暂时不支持scroll-x = true横向刷新
refresher-threshold number 45 设置下拉刷新阈值, 仅 refresher-default-style = 'none' 自定义样式下生效
refresher-max-drag-distance number - 设置下拉最大拖拽距离(单位px),默认是下拉刷新控件高度的2.5倍
refresher-default-style string black 设置下拉刷新默认样式,支持设置 black | white | none, none 表示不使用默认样式 refresher-default-style
refresher-background string #FFF 设置下拉刷新区域背景颜色
refresher-triggered boolean false 设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发
custom-nested-scroll boolean - 子元素是否开启嵌套滚动 将滚动事件与父元素协商处理
@refresherpulling (event: RefresherEvent) => void - 下拉刷新控件被下拉
@refresherrefresh (event: RefresherEvent) => void - 下拉刷新被触发
@refresherrestore (event: RefresherEvent) => void - 下拉刷新被复位
@refresherabort (event: RefresherEvent) => void - 下拉刷新被中止
@scrolltoupper (event: ScrollToUpperEvent) => void - 滚动到顶部/左边,会触发 scrolltoupper 事件
@scrolltolower (event: ScrollToLowerEvent) => void - 滚动到底部/右边,会触发 scrolltolower 事件
@scroll (event: ScrollEvent) => void - 滚动时触发,event.detail = {scrollLeft, scrollTop, scrollHeight, scrollWidth, deltaX, deltaY}

# # refresher-default-style

值名称 描述
black 深颜色雪花样式
white 浅白色雪花样式
none 不使用默认样式
# # refresher-default-style 兼容性
安卓系统版本 安卓 uni-app 安卓 uni-app-x iOS 系统版本 iOS uni-app iOS uni-app-x
black 5.0 x 3.9+ - - -
white 5.0 x 3.9+ - - -
none 5.0 x 3.93 - - -

# # 事件

# # RefresherEvent

# # RefresherEvent 属性值
名称 类型 必填 默认值 描述
detail RefresherEventDetail - -
type string - 事件类型
target Element - 触发事件的组件
currentTarget Element - 当前组件
timeStamp number - 事件发生时的时间戳
# # RefresherEventDetail 属性值
名称 类型 必备 默认值 描述
dy number - -
# # RefresherEvent 方法
名称 类型 必填 默认值 描述
stopPropagation () => void - 阻止当前事件的进一步传播
preventDefault () => void - 阻止当前事件的默认行为

# # ScrollToUpperEvent

# # ScrollToUpperEvent 属性值
名称 类型 必填 默认值 描述
detail ScrollToUpperEventDetail - -
type string - 事件类型
target Element - 触发事件的组件
currentTarget Element - 当前组件
timeStamp number - 事件发生时的时间戳
# # ScrollToUpperEventDetail 属性值
名称 类型 必备 默认值 描述
direction string - 滚动方向 top 或 left
# # ScrollToUpperEvent 方法
名称 类型 必填 默认值 描述
stopPropagation () => void - 阻止当前事件的进一步传播
preventDefault () => void - 阻止当前事件的默认行为

# # ScrollToLowerEvent

# # ScrollToLowerEvent 属性值
名称 类型 必填 默认值 描述
detail ScrollToLowerEventDetail - -
type string - 事件类型
target Element - 触发事件的组件
currentTarget Element - 当前组件
timeStamp number - 事件发生时的时间戳
# # ScrollToLowerEventDetail 属性值
名称 类型 必备 默认值 描述
direction string - 滚动方向 bottom 或 right
# # ScrollToLowerEvent 方法
名称 类型 必填 默认值 描述
stopPropagation () => void - 阻止当前事件的进一步传播
preventDefault () => void - 阻止当前事件的默认行为

# # ScrollEvent

# # ScrollEvent 属性值
名称 类型 必填 默认值 描述
detail ScrollEventDetail - -
type string - 事件类型
target Element - 触发事件的组件
currentTarget Element - 当前组件
timeStamp number - 事件发生时的时间戳
# # ScrollEventDetail 属性值
名称 类型 必备 默认值 描述
scrollTop number - 竖向滚动的距离
scrollLeft number - 横向滚动的距离
scrollHeight number - 滚动区域的高度
scrollWidth number - 滚动区域的宽度
deltaY number - 当次滚动事件竖向滚动量
deltaX number - 当次滚动事件横向滚动量
# # ScrollEvent 方法
名称 类型 必填 默认值 描述
stopPropagation () => void - 阻止当前事件的进一步传播
preventDefault () => void - 阻止当前事件的默认行为

# # 示例

hello uni-app x

<script>
 import { type ItemType } from '@/components/enum-data/enum-data.vue'
 export default {
   data() {
     return {
       refresher_triggered_boolean: false,
       refresher_enabled_boolean: false,
       scroll_with_animation_boolean: false,
       show_scrollbar_boolean: true,
       rebound_boolean: true,
       scroll_y_boolean: true,
       scroll_x_boolean: false,
       upper_threshold_input: 50,
       lower_threshold_input: 50,
       scroll_top_input: 0,
       scroll_left_input: 0,
       refresher_background_input: "#FFF",
       scrollData: [] as Array<string>,
       size_enum: [{ "value": 0, "name": "item---0" }, { "value": 3, "name": "item---3" }] as ItemType[],
       scrollIntoView: "",
       refresherrefresh: false,
       refresher_default_style_input: "black",
       text: ['继续下拉执行刷新', '释放立即刷新', '刷新中', ""],
       state: 3,
       reset: true
     }
   },
   onLoad() {
     let lists : Array<string> = []
     for (let i = 0; i < 10; i++) {
       lists.push("item---" + i)
     }
     this.scrollData = lists
   },
   methods: {
     list_view_click() { console.log("组件被点击时触发") },
     list_view_touchstart() { console.log("手指触摸动作开始") },
     list_view_touchmove() { console.log("手指触摸后移动") },
     list_view_touchcancel() { console.log("手指触摸动作被打断,如来电提醒,弹窗") },
     list_view_touchend() { console.log("手指触摸动作结束") },
     list_view_tap() { console.log("手指触摸后马上离开") },
     list_view_longpress() { console.log("如果一个组件被绑定了 longpress 事件,那么当用户长按这个组件时,该事件将会被触发。") },
     list_view_refresherpulling(e : RefresherEvent) {
       console.log("下拉刷新控件被下拉")
       if(this.reset) {
       	if(e.detail.dy > 45) {
       		this.state = 1
       	} else {
       		this.state = 0
       	}
       }
     },
     list_view_refresherrefresh() {
       console.log("下拉刷新被触发 ")
       this.refresherrefresh = true
       this.refresher_triggered_boolean = true
       this.state = 2
       this.reset = false;
       setTimeout(function(){
       	this.refresher_triggered_boolean = false
       }, 1500)
     },
     list_view_refresherrestore() {
       this.refresherrefresh = false
       this.state = 3
       this.reset = true
       console.log("下拉刷新被复位")
     },
     list_view_refresherabort() { console.log("下拉刷新被中止") },
     list_view_scrolltoupper(e:ScrollToUpperEvent) { console.log("滚动到顶部/左边,会触发 scrolltoupper 事件  direction="+e.detail.direction) },
     list_view_scrolltolower(e:ScrollToLowerEvent) { console.log("滚动到底部/右边,会触发 scrolltolower 事件  direction="+e.detail.direction) },
     list_view_scroll() { console.log("滚动时触发,event.detail = {scrollLeft, scrollTop, scrollHeight, scrollWidth, deltaX, deltaY}") },
     list_item_click() { console.log("list-item组件被点击时触发") },
     list_item_touchstart() { console.log("手指触摸list-item组件动作开始") },
     list_item_touchmove() { console.log("手指触摸list-item组件后移动") },
     list_item_touchcancel() { console.log("手指触摸list-item组件动作被打断,如来电提醒,弹窗") },
     list_item_touchend() { console.log("手指触摸list-item组件动作结束") },
     list_item_tap() { console.log("手指触摸list-item组件后马上离开") },
     list_item_longpress() { console.log("list-item组件被绑定了 longpress 事件,那么当用户长按这个组件时,该事件将会被触发。") },
     change_refresher_triggered_boolean(checked : boolean) { this.refresher_triggered_boolean = checked },
     change_refresher_enabled_boolean(checked : boolean) { this.refresher_enabled_boolean = checked },
     change_scroll_with_animation_boolean(checked : boolean) { this.scroll_with_animation_boolean = checked },
     change_show_scrollbar_boolean(checked : boolean) { this.show_scrollbar_boolean = checked },
     change_rebound_boolean(checked : boolean) { this.rebound_boolean = checked },
     change_scroll_y_boolean(checked : boolean) { this.scroll_y_boolean = checked },
     change_scroll_x_boolean(checked : boolean) { this.scroll_x_boolean = checked },
     confirm_upper_threshold_input(value : number) { this.upper_threshold_input = value },
     confirm_lower_threshold_input(value : number) { this.lower_threshold_input = value },
     confirm_scroll_top_input(value : number) { this.scroll_top_input = value },
     confirm_scroll_left_input(value : number) { this.scroll_left_input = value },
     confirm_refresher_background_input(value : string) { this.refresher_background_input = value },
     item_change_size_enum(index : number) { this.scrollIntoView = "item---"+index },
     //自动化测试例专用
     check_scroll_height(): Boolean {
       var listElement = this.$refs["listview"] as Element
       console.log("check_scroll_height--"+listElement.scrollHeight)
       if(listElement.scrollHeight > 2000) {
         return true
       }
       return false
     },
     //自动化测试例专用
     check_scroll_width(): Boolean {
       var listElement = this.$refs["listview"] as Element
       console.log("check_scroll_width"+listElement.scrollWidth)
       if(listElement.scrollWidth > 2000) {
         return true
       }
       return false
     },
     change_refresher_style_boolean(checked : boolean) {
       if(checked) {
         this.refresher_default_style_input = "none"
       } else {
         this.refresher_default_style_input = "black"
       }
     }
   }
 }
</script>

<template>
 <view class="main">
   <list-view :scroll-x="scroll_x_boolean" :scroll-y="scroll_y_boolean" :rebound="rebound_boolean"
     :upper-threshold="upper_threshold_input" :lower-threshold="lower_threshold_input" :scroll-top="scroll_top_input"
     :scroll-left="scroll_left_input" :show-scrollbar="show_scrollbar_boolean" :scroll-into-view="scrollIntoView"
     :scroll-with-animation="scroll_with_animation_boolean" :refresher-enabled="refresher_enabled_boolean"
     :refresher-background="refresher_background_input" :refresher-triggered="refresher_triggered_boolean"
     :refresher-default-style="refresher_default_style_input"
     @click="list_view_click" @touchstart="list_view_touchstart" @touchmove="list_view_touchmove"
     @touchcancel="list_view_touchcancel" @touchend="list_view_touchend" @tap="list_view_tap"
     @longpress="list_view_longpress" @refresherpulling="list_view_refresherpulling"
     @refresherrefresh="list_view_refresherrefresh" @refresherrestore="list_view_refresherrestore"
     @refresherabort="list_view_refresherabort" @scrolltoupper="list_view_scrolltoupper" ref="listview" id="listview"
     @scrolltolower="list_view_scrolltolower" @scroll="list_view_scroll" style="width:100%;">
     <list-item
       v-for="key in scrollData" :key="key" :id="key" @click="list_item_click" @touchstart="list_item_touchstart"
       @touchmove="list_item_touchmove" @touchcancel="list_item_touchcancel" @touchend="list_item_touchend"
       @tap="list_item_tap" @longpress="list_item_longpress"
       class="list-item">
       <text>{{key}}</text>
     </list-item>
     <list-item slot="refresher" class="refresh-box">
       <text class="tip-text">{{text[state]}}</text>
     </list-item>
   </list-view>
 </view>

 <!-- #ifdef APP -->
 <scroll-view style="flex:1">
   <!-- #endif -->
   <view class="content nvue">
     <boolean-data :defaultValue="false" title="设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发"
       @change="change_refresher_triggered_boolean"></boolean-data>
     <boolean-data :defaultValue="false" title="开启下拉刷新" @change="change_refresher_enabled_boolean"></boolean-data>
     <boolean-data :defaultValue="false" title="开启自定义样式" @change="change_refresher_style_boolean"></boolean-data>
     <boolean-data :defaultValue="false" title="是否在设置滚动条位置时使用滚动动画,设置false没有滚动动画"
       @change="change_scroll_with_animation_boolean"></boolean-data>
     <boolean-data :defaultValue="true" title="控制是否出现滚动条" @change="change_show_scrollbar_boolean"></boolean-data>
     <boolean-data :defaultValue="true" title="控制是否回弹效果" @change="change_rebound_boolean"></boolean-data>
     <boolean-data :defaultValue="true" title="允许纵向滚动" @change="change_scroll_y_boolean"></boolean-data>
     <boolean-data :defaultValue="false" title="允许横向滚动" @change="change_scroll_x_boolean"></boolean-data>
     <input-data defaultValue="50" title="距顶部/左边多远时(单位px),触发 scrolltoupper 事件" type="number"
       @confirm="confirm_upper_threshold_input"></input-data>
     <input-data defaultValue="50" title="距底部/右边多远时(单位px),触发 scrolltolower 事件" type="number"
       @confirm="confirm_lower_threshold_input"></input-data>
     <input-data defaultValue="0" title="设置竖向滚动条位置" type="number" @confirm="confirm_scroll_top_input"></input-data>
     <input-data defaultValue="0" title="设置横向滚动条位置" type="number" @confirm="confirm_scroll_left_input"></input-data>
     <input-data defaultValue="#FFF" title="设置下拉刷新区域背景颜色" type="text"
       @confirm="confirm_refresher_background_input"></input-data>
     <enum-data :items="size_enum" title="通过id位置跳转" @change="item_change_size_enum"></enum-data>
   </view>
   <!-- #ifdef APP -->
 </scroll-view>
 <!-- #endif -->
</template>

<style>
 .main {
   max-height: 500rpx;
   padding: 10rpx 0;
   border-bottom: 1px solid rgba(0, 0, 0, .06);
   flex-direction: row;
   justify-content: center;
 }

 .main .list-item {
   width: 750rpx;
   height: 480rpx;
   border: 1px solid #666;
   background-color: #66ccff;
   align-items: center;
   justify-content: center;
 }
 
 .tip-text {
 	color: #888;
 	font-size: 12px;
 }
 
 .refresh-box {
   justify-content: center;
   align-items: center;
   flex-direction: row;
   height: 45px;
   width: 100%;
 }
</style>

# 自定义下拉刷新样式

  1. 设置refresher-default-style属性为 none 不使用默认样式
  2. 设置 list-item 定义自定义下拉刷新元素并声明为 slot="refresher",需要设置刷新元素宽高信息否则可能无法正常显示!
    <template>
    	<list-view refresher-default-style="none" :refresher-enabled="true" :refresher-triggered="refresherTriggered"
    			 @refresherpulling="onRefresherpulling" @refresherrefresh="onRefresherrefresh" 
    			 @refresherrestore="onRefresherrestore" style="flex:1" >
    
    		<list-item v-for="i in 10" class="content-item">
    			<text class="text">item-{{i}}</text>
    		</list-item>
    		
    		<!-- 自定义下拉刷新元素 -->
    		<list-item slot="refresher" class="refresh-box">
    			<text class="tip-text">{{text[state]}}</text>
    		</list-item>
    	</list-view>
    </template>
    
  3. 通过组件提供的refresherpulling、refresherrefresh、refresherrestore、refresherabort下拉刷新事件调整自定义下拉刷新元素!实现预期效果

注意:

  • 3.93版本开始支持
  • 目前自定义下拉刷新元素不支持放在list-view的首个子元素位置上。可能无法正常显示

# # 兼容性

安卓系统版本 安卓 uni-app 安卓 uni-app-x iOS 系统版本 iOS uni-app iOS uni-app-x
list-view 5.0 x 3.9+ 9.0 x -
scroll-x 5.0 x 3.9+ - - -
scroll-y 5.0 x 3.9+ - - -
rebound 5.0 x 3.9+ - - -
upper-threshold 5.0 x 3.9+ - - -
lower-threshold 5.0 x 3.9+ - - -
scroll-top 5.0 x 3.9+ - - -
scroll-left 5.0 x 3.9+ - - -
show-scrollbar 5.0 x 3.9+ - - -
scroll-into-view 5.0 x 3.9+ - - -
scroll-with-animation 5.0 x 3.9+ - - -
refresher-enabled 5.0 x 3.9+ - - -
refresher-threshold 5.0 x 3.9+ - - -
refresher-max-drag-distance 5.0 x 3.9+ - - -
refresher-default-style 5.0 x 3.9+ - - -
refresher-background 5.0 x 3.9+ - - -
refresher-triggered 5.0 x 3.9+ - - -
custom-nested-scroll 5.0 x 3.9+ 9.0 x -
@refresherpulling 5.0 x 3.9+ 9.0 x -
@refresherrefresh 5.0 x 3.9+ 9.0 x -
@refresherrestore 5.0 x 3.9+ 9.0 x -
@refresherabort 5.0 x 3.9+ 9.0 x -
@scrolltoupper 5.0 x 3.9+ 9.0 x -
@scrolltolower 5.0 x 3.9+ 9.0 x -
@scroll 5.0 x 3.9+ 9.0 x -

# # 子组件

吸顶布局容器

注意:暂时仅支持作为list-view的子节点, sticky-header不支持css样式!当一个容器视图设置多个sticky-header时,后一个sticky-header会停靠在前一个sticky-header的末尾处。

# # 示例

hello uni-app x

<template>
   <list-view :scroll-y="true" class="page" rebound="false" :scroll-top="scroll_top_input" :refresher-enabled="refresher_enabled_boolean"
   :refresher-triggered="refresher_triggered_boolean" @refresherrefresh="list_view_refresherrefresh">
   <list-item type = 1>
     <swiper indicator-dots="true" circular="true">
       <swiper-item  v-for="i in 3" :item-id="i">
         <image src="/static/shuijiao.jpg" style="height: 240px;"></image>
         <text style="position: absolute;">{{i}}</text>
       </swiper-item>
     </swiper>
   </list-item>
   <list-item class="content-item" type = 2>
   		<text class="text">向上滑动页面,体验sticky-header吸顶效果。</text>
   	</list-item>
   	<sticky-header>
     <scroll-view style="background-color: #f5f5f5; flex-direction: row;" :scroll-x="true" :scroll-y="false" :show-scrollbar="false">
       <view class="flex-row" style="align-self: flex-start; flex-direction: row;">
         <text ref="swipertab" class="sift-item"
           v-for="(name,index) in sift_item" @click="clickTH(index)">
           {{name}}
         </text>
       </view>
     </scroll-view>
   	</sticky-header>

   	<list-item v-for="(item,index) in list_item" :key="index" class="content-item" type = 3>
   		<text class="text">{{item}}</text>
   	</list-item>
   </list-view>
</template>

<script>
   export default {
   	data() {
   		return {
   			sift_item: ["排序", "筛选"],
   			list_item: [] as Array<string>,
   			refresher_enabled_boolean: true,
   			refresher_triggered_boolean: false,
   			scroll_top_input: 0
   		}
   	},
   onLoad() {
     let lists : Array<string> = []
     for (let i = 0; i < 40; i++) {
       lists.push("item---" + i)
     }
     this.list_item = lists
   },
   	methods: {
   		list_view_refresherrefresh() {
   		  console.log("下拉刷新被触发 ")
   		  this.refresher_triggered_boolean = true
   		  setTimeout(function(){
   		  	this.refresher_triggered_boolean = false
   		  }, 1500)
   		},
   		confirm_scroll_top_input(value : number) {
   		  this.scroll_top_input = value
   		},
     clickTH(index:number){
       console.log("点击表头:" + index);
     }
   	}
   }
</script>

<style>
   .page {
   	flex: 1;
   	background-color: #f5f5f5;
   }

   .content-item {
   	padding: 15px;
   	margin: 5px 0;
   	background-color: #fff;
   }

   .text {
   	font-size: 14px;
   	color: #666;
   	line-height: 20px;
   }

 .sift-item {
   color: #555;
   font-size: 16px;
   padding: 12px 15px;
 }

</style>

# # 兼容性

安卓系统版本 安卓 uni-app 安卓 uni-app-x iOS 系统版本 iOS uni-app iOS uni-app-x
sticky-header 5.0 x 3.93 9.0 x -

# list-item

list-view组件的唯一合法子组件。每个item是一行

# # 属性

名称 类型 默认值 描述
type number 0 对应list-item的类型 list-view 将对同类型条目进行复用,所以合理的类型拆分,可以很好地提升 list-view 性能

# list-item复用机制

  • type属性定义list-item组件类型。不赋值type属性默认值为0,每一个type类型都会有对应的list-item组件缓存池。

  • list-view组件加载list-item组件时,会优先查询对应type缓存池是否存在可复用的list-item组件。有则复用没有则创建新的list-item组件。

  • list-item组件被滑动出屏幕则会优先添加到对应类型的list-item缓存池,每个类型缓存最大5个(不同平台缓存最大值不固定),如果缓存池已满则进行组件销毁!

  • 部分list-item组件存在子元素个数差异或排版差异时。请尽可能的配置不同的type,这样可以规避获取相同type类型的list-item组件后。由于子元素差异导致list-item无法正常复用问题。具体可参考示例:

    <template>
      <view class="content">
    	<list-view ref="listView" class="list" :scroll-y="true">
    	  <list-item v-for="(item,index) in list" :key="index" class="content-item1" type=1>
    		<text class="text">title-{{item}}</text>
    		<text class="text">content-{{item}}</text>
    	  </list-item>
    	  <list-item v-for="(item,index) in list" :key="index" class="content-item2" type=2>
    	  	<image class="image" src ="/static/test-image/logo.png"></image>
    	  </list-item>
    	  <list-item type=3>
    		<text class="loading">{{text}}</text>
    	  </list-item>
    	</list-view>
      </view>
    </template>
    

    示例中有三种类型的list-item组件。如果都不赋值type,list-item组件滑动出屏幕后都归类到type=0的缓存池。当触发list-item组件重新加载时,获取type=0的缓存池的组件,获取到的list-item组件可能是两个text子组件也可能是一个image子组件或一个text子组件,底层复用判断时则认为该情况异常不复用,重新创建新的list-item组件!复用失败未能优化性能。正确的方式则是不同的类型设置不同的type。加载时则会获取对应type类型缓存池中的list-item组件实现复用。

# # 兼容性

安卓系统版本 安卓 uni-app 安卓 uni-app-x iOS 系统版本 iOS uni-app iOS uni-app-x
list-item 5.0 x 3.9+ 9.0 x -
type 5.0 x 3.9+ 9.0 x -

# 示例代码

# Bug & Tips

  • 暂不支持reverse,目前还不能开发im那样的倒序列表
  • 多列瀑布流是另外的组件,后续会提供