[已发布][组件] ScrollAdapter 虚拟列表升级版

0.1. 开发环境

  • 引擎版本:Cocos Creator 2x & 3x
  • 编程语言:TypeScript

0.2. 在线演示

0.3. 商店地址

0.4. 资源介绍

  • 这个插件是对原有的super-scrollview升级重构,在支持原有功能以外还增加了更多实用功能,重点是更加灵活,理论上可以通过这个插件实现的功能远远高于上一个版本。

0.6. 简单介绍

1. ScrollManager 参数

1.1. ViewManager 参数

1.2. LayoutManager 参数

1.3. PullReleaseManager 参数

1.4. AutoCenterManager 参数

1.5. PageViewManager 参数

1.6. NestedManager 参数

2. 入门 - 布局

  • 新建一个脚本 MyAdapter 继承ScrollAdapter

    export class MyAdapter extends ScrollAdapter<ITutorialModel> {
        @property(Node) prefab1: Node = null
        @property(Node) prefab2: Node = null
        @property(Node) prefab3: Node = null
        @property(Node) prefab4: Node = null
        /**
        * 根据数据类型返回相应的预制体
        * @param data 用户数据
        * @returns 预制体
        */
        getPrefab(data: ITutorialModel): Node | Prefab {
            switch (data.prefabType) {
                case 1: return this.prefab1
                case 2: return this.prefab2
                case 3: return this.prefab3
                case 4: return this.prefab4
                // ... 更多Prefab
            }
        }
        /**
        * 子类实现 创建View实例
        */
        getView(): View<ITutorialModel> {
            return new MyView(this)
        }
        /**
        * 子类实现 创建Holder实例
        */
        getHolder(node: Node, code: string): Holder<ITutorialModel> {
            return new MyHolder(node, code, this)
        }
    }
    
  • 首先介绍下上面出现的 ITutorialModel View Holder 分别是什么
    ITutorialModel

    • 用户自定义数据类型
  • Holder

    • 每个Holder绑定一个固定的被实例化的预制体
    • Holder一旦被创建,预制体类型就确定了,永远不会改变
    • 变化的只有数据
  • View

    • View可以理解为单行容器
    • 每个View管理多个Holder
    • 例如,如果当前主轴方向(就是设置的滚动方向)为垂直时,每个View代表一个填满整个可视区域宽度的一行
    • 容器里面的每个Holder就是单独的列
    • 所谓的布局就是单独布局整个View里面所有Holder的交叉轴方向(和滚动相反的方向)
  • 首先定义自定义数据类型

    export interface ITutorialModel {
        name: string        //名称
        fixed: boolean      //是否固定     
        prefabType: number  //预制体类型
        wrapBeforeMode: WrapMode    //前换行模式
        wrapAfterMode: WrapMode    //后换行模式
        flexible?: number    // 弹性倍数
    }
    
  • 在 MyAdapter 的 start 方法里创建自定义数据

    start() {
        var models: ITutorialModel[] = []
        for循环 {
            // 数据结构是自定义的,可以自由发挥,这里举例
            // 第一行显示两个不同的预制体
            var model1: ITutorialModel = {
                name: "固定的标题"+ i,
                prefabType: 1,
                fixed: true, //标记它为固定标题
                /** 相对于前一个元素换行,永远置于新一行起始位置 */
                wrapBeforeMode: WrapMode.Wrap,
                /** 允许下一个元素可以排在自己后面 */
                wrapAfterMode: WrapMode.Nowrap,
                flexible: 0,
            }
            var model2: ITutorialModel = {
                name: "内容" +i,
                prefabType: 2,
                fixed: false,
                /**
                 * 相对于前一个元素不换行,排在它后面
                 * 能否真正排在前一个元素的后面还要看前一个元素的wrapAfterMode设置
                 */
                wrapBeforeMode: WrapMode.Nowrap,
                /** 要求下一个元素必须换行,无论它的wrapBeforeMode设置的是什么 */
                wrapAfterMode: WrapMode.Wrap,
                flexible: 1
            }
            models.push(model1, model2)
    
            // 第二行只显示一个预制体
            var model3: ITutorialModel = {
                name: "独占一行的内容" + i,
                prefabType: 3,
                fixed: false,
                /** 这个元素永远会独占一行 */
                wrapBeforeMode: WrapMode.Wrap,
                wrapAfterMode: WrapMode.Wrap,
                flexible: 1
            }
            models.push(model3)
    
            // 第三行包括之后的所有行都显示统一的内容,并且自动填满
            for循环 {
                models.push({
                    name: "#"+ j,
                    fixed: false,
                    prefabType: 4,
                    /** 自动换行 */
                    wrapBeforeMode: WrapMode.Auto,
                    wrapAfterMode: WrapMode.Nowrap,
                    /** 希望每组数据的第一个尺寸大一些 */
                    flexible: j == 0 ? 2 : 1
                })
            }
        }
        // 数据准备好了 向modelMananger中插入数据
        this.modelManager.insert(models)
    }
    
  • 以上代码看起来很多,这里只是为了演示方便,写死一些数据,如果没有特殊布局需求的话 wrapBeforeMode,wrapAfterMode,fixed ,flexible 都可以不设置,使用默认值即可

  • 接下来看下 View 部分

    class MyView extends View<ITutorialModel>{
        /**
        * 设置元素布局规则 每一个IViewElement都是一个被实例化的节点
        * @param element 元素布局规则
        * @param data 用户数据
        */
        protected setViewElement(element: IViewElement, data: ITutorialModel): void {
            // 设置换行模式
            element.wrapBeforeMode = data.wrapBeforeMode
            element.wrapAfterMode = data.wrapAfterMode
    
            // 由于我们在创建数据的时候指定了一个名为fixed的标志,代表它是一个固定标题
            // 所以这里根据数据来设置是否固定
            element.fixed = data.fixed
            // 不要忘了,固定标题一般都显示在最上层,除非你想要其他效果
            if (element.fixed) { //将非固定的提升到最上层,
                // 层级一共有3中 Lowest Medium Highest,默认所有节点都在 Lowest
                element.layer = Layer.Highest //设置到最上层
                // element.fixedOffset = 30 //固定标题和可视区域顶部的距离
                // element.fixedSpacing = 0 //当前标题和下一个固定标题的间距
            }
            /** 第一个元素的值在创建时设置为 2  */
            element.flexibleSize.width = data.flexible
    
        }
        protected onVisible(): void {
        }
        protected onDisable(): void {
        }
    }
    
  • 接下来看下 Holder 部分

    class MyHolder extends Holder<ITutorialModel>{
        private _label: Label = null
        /**
        * 当被创建时,只会执行一次,可以在这里初始化
        */
        protected onCreated(): void {
            this._label = this.node.getComponentInChildren(Label)
        }
        /**
        * 每次数据发生变化或从不可见到可见时,不只是用户数据变化,可能当前Holder所属的View变化也会执行
        */
        protected onVisible(): void {
            this._label.string = this.data.name
        }
        /**
        * 当被回收为不可见状态时
        */
        protected onDisable(): void {
        }
    }
    
  • 运行结果

  • 1.gif

  • 除了这些以外,布局还有其他属性,一下是完整的属性字段

    export interface IViewElement {
        /**
        * 前换行,当前元素相对于前一个元素的换行模式,默认值 Wrap
        */
        wrapBeforeMode: WrapMode
        /**
        * 后换行,下一个元素相对于自己的换行模式,默认值 Nowrap
        */
        wrapAfterMode: WrapMode
        /**
        * 忽略布局,使其不受Layout控制
        */
        ignoreLayout: boolean
        /**
        * 最小尺寸,如果同时设置了preferredSize则取最大值(只会影响滑动反方向)
        */
        minSize: ISizeLike
        /**
        * 首选尺寸,如果同时设置了minSize则取最大值(只会影响滑动反方向)
        */
        preferredSize: ISizeLike
        /**
        * flex,和CSS flex一样的作用(只会影响滑动反方向)
        */
        flexibleSize: ISizeLike
        /** 
        * 指定当前数据的层级,如果未设置,使用最低层 Lowest
        */
        layer: Layer
        /**
        * 固定到顶部
        */
        fixed: boolean
        /**
        * 开启fixed时有效,固定时顶部的偏移量
        */
        fixedOffset: number
        /**
        * 开启fixed时有效,与下一个设置了fixed的item的间距
        */
        fixedSpacing: number
    }
    

暂时先介绍到这里,内容比较多,日后持续更新教程…
所有演示场景都在3.x中,2.x版本没有演示场景

23赞

mark,牛逼

請問有買過super-scrollview的老客戶有優惠價麻 :rofl:

199是认真的吗~? :sweat_smile:

1赞

奈何老夫没文化,一句卧槽行天下。
打折的时候记得通知我。

牛b…

大佬, 有点没太看明白, 不知道这个的使用场景主要是用于 ? ? 用这个是会提高性能吗 ?

先mark,坐等降价

mark1111

白嫖的时候告诉我

真是好东西啊 只能说我穷,打折时候告诉我

已经打折了:smile:

发现一个小BUG,每次打开第一次点Scroll To跳转到某一页,并不会准确跳转到,后面就正常了

最新版本 已修复

大佬,我想问下demo里面那个简单演示也用了虚拟列表吗

这个就是我一直想要的,牛逼,

演示场景已更新

支不支持聊天功能
反向渲染。从最后一个开始渲染