cocos creator ListView_Zack-zzh的博客-程序员宅基地

技术标签: cocos  

 

ListView.ts

const {ccclass, property} = cc._decorator;
/**
 * 通用 ListView 组件.
 * 能够显示垂直/横向ListView. 具体用法见Demo
 */
@ccclass
export default class ListView extends cc.Component {

    @property(cc.Prefab)
    private itemTemplate: cc.Prefab = null;

    @property(cc.Vec2)
    private readonly spacing: cc.Vec2 = cc.v2(0, 0);

    // 四周距离.
    @property(cc.Rect)
    private readonly margin: cc.Rect = cc.rect(0, 0, 0, 0);

    // 比可见元素多缓存2个, 缓存越多,快速滑动越流畅,但同时初始化越慢.
    @property
    private spawnCount: number = 2;

    // 横向布局的item 数量. 默认为1,即每行一个元素.
    @property
    private column: number = 1;

    @property(cc.ScrollView)
    private scrollView: cc.ScrollView = null;

    private content: cc.Node = null;

    private adapter: AbsAdapter = null;

    private readonly _items: cc.NodePool = new cc.NodePool();

    // 记录当前填充在树上的索引. 用来快速查找哪些位置缺少item了.
    private _filledIds: { [key: number]: cc.Node } = {};

    private horizontal: boolean = false;

    // 初始时即计算item的高度.因为布局时要用到.
    private _itemHeight: number = 1;

    private _itemWidth: number = 1;

    private _itemsVisible: number = 1;

    private dataChanged: boolean = false;
    // 当前屏幕可见元素索引值.
    private readonly visibleRange: number[] = [-1, -1];

    public readonly pager: Pager;

    public onLoad() {
   // public onLoad() {
        this.init();
        // @ts-ignore
        this.pager = new Pager(this);
        /**
         *  如果出现列表显示异常,如边界留白,item 错位等问题,可能是所在节点树 存在缩放行为.
         *  具体bug参考: https://forum.cocos.com/t/v2-1-0-scrollview/71260/5
         *  打开以下代码即可解决布局异常问题.
         */
        if (this.scrollView) {
             this.scheduleOnce(() => {
                 // @ts-ignore
                 this.scrollView._calculateBoundary();
             }, 0.1);
        }
    }

    public async setAdapter(adapter: AbsAdapter) {
        this.adapter = adapter;
        if (this.adapter == null) {
            console.warn("adapter 为空.");
            return
        }
        if (this.itemTemplate == null) {
            console.error("Listview 未设置待显示的Item模板.");
            return;
        }
        this.visibleRange[0] = this.visibleRange[1] = -1;
        this.recycleAll();
        this.notifyUpdate();
    }

    public getAdapter(): AbsAdapter {
        return this.adapter;
    }

    public getScrollView(): cc.ScrollView {
        return this.scrollView;
    }

    /**
     * 滚动API
     * @param pageIndex 滚动到哪一页.
     * @param pageCount 如果>0 则以count数量的item 为一页.否则以当前可见数量为一页.
     * @param timeSecond
     * @return true = 滚动到最后一页了.
     */
    public scrollToPage(pageIndex: number, pageCount?: number, timeSecond?: number): boolean {
        if (!this.adapter || !this.scrollView) {
            return false;
        }
        const count = this.adapter.getCount() || 1;
        this.column = this.column || 1;
        if (this.horizontal) {
            let pageWidth = 0;
            const maxWidth = this.content.width;
            const columnWidth = maxWidth / (count / this.column);
            if (!pageCount) {
                // 可见区域的总宽度. 还需要进一步缩减为整数个item的区域.
                let pW = this.content.parent.width;
                pageWidth = Math.floor(pW / columnWidth) * columnWidth;
            } else {
                pageWidth = columnWidth * pageCount;
            }
            this.scrollView.scrollToOffset(cc.v2(pageWidth * pageIndex, 0), timeSecond);
            return pageWidth * (pageIndex + 1) >= maxWidth;
        } else {
            const maxHeight = this.content.height;
            const rowHeight = maxHeight / (count / this.column);
            let pageHeight = 0;
            if (!pageCount) {
                // maskView 的高度.
                let pH = this.content.parent.height;
                pageHeight = Math.floor(pH / rowHeight) * rowHeight;
            } else {
                pageHeight = rowHeight * pageCount;
            }
            this.scrollView.scrollToOffset(cc.v2(pageHeight * pageIndex, 0), timeSecond);
            return pageHeight * (pageIndex + 1) >= maxHeight;
        }
    }

    // 获取可见区域的最大元素个数。不包含遮挡一半的元素。
    public getVisibleElements(): number {
        let visibleCount = 0;
        const count = this.adapter ? (this.adapter.getCount() || 1) : 1;
        if (this.horizontal) {
            const maxWidth = this.content.width;
            const columnWidth = maxWidth / (count / this.column);
            // 可见区域的总宽度. 还需要进一步缩减为整数个item的区域.
            let pW = this.content.parent.width;
            visibleCount = Math.floor(pW / columnWidth);
        } else {
            const maxHeight = this.content.height;
            const rowHeight = maxHeight / (count / this.column);
            // maskView 的高度.
            let pH = this.content.parent.height;
            visibleCount = Math.floor(pH / rowHeight);
        }
        return visibleCount;
    }

    // 数据变更了需要进行更新UI显示, 可只更新某一条.
    public notifyUpdate() {
        if (this.adapter == null) {
            return;
        }
        if (!this.scrollView || !this.content) {
            return;
        }
        this.visibleRange[0] = this.visibleRange[1] = -1;
        if (this.horizontal) {
            this.content.width = Math.ceil(this.adapter.getCount() / this.column) * (this._itemWidth + this.spacing.x)
                - this.spacing.x + this.margin.x + this.margin.width;
        } else {
            this.content.height = Math.ceil(this.adapter.getCount() / this.column) * (this._itemHeight + this.spacing.y)
                - this.spacing.y + this.margin.y + this.margin.height;
        }
        this.dataChanged = true;
    }

    protected lateUpdate() {
        const range = this.getVisibleRange();
        if (!this.checkNeedUpdate(range)) {
            return;
        }
        this.recycleDirty(range);

        this.updateView(range)
    }

    // 向某位置添加一个item.
    private _layoutVertical(child: cc.Node, posIndex: number) {
        this.content.addChild(child);
        // 当columns 大于1时,从左到右依次排列, 否则进行居中排列.
        const column = posIndex % (this.column || 1);
        const row = Math.floor(posIndex / (this.column || 1));
        child.setPosition(
            this.column > 1 ? this.margin.x + child.width * child.anchorX
                + (child.width + this.spacing.x) * column - this.content.width * this.content.anchorX : 0,
            -this.margin.y - child.height * (child.anchorY + row) - this.spacing.y * row);
    }

    // 向某位置添加一个item.
    private _layoutHorizontal(child: cc.Node, posIndex: number) {
        this.content.addChild(child);
        const row = posIndex % (this.column || 1);
        const column = Math.floor(posIndex / (this.column || 1));
        child.setPosition(
            child.width * (child.anchorX + column) + this.spacing.x * column + this.margin.x,
            this.column > 1 ? this.margin.y + child.height * child.anchorY + (child.height + this.spacing.y) * row
                - this.content.height * this.content.anchorY : 0);
    }

    private recycleAll() {
        for (const child in this._filledIds) {
            if (this._filledIds.hasOwnProperty(child)) {
                this._items.put(this._filledIds[child]);
            }
        }
        this._filledIds = {};
    }

    private recycleDirty(visibleRange: number[]) {
        for (let i = this.visibleRange[0]; i < visibleRange[0]; i++) {
            if (i < 0 || !this._filledIds[i]) {
                continue;
            }
            this._items.put(this._filledIds[i]);
            this._filledIds[i] = null;
        }
        for (let j = this.visibleRange[1]; j > visibleRange[1]; j--) {
            if (j < 0 || !this._filledIds[j]) {
                continue;
            }
            this._items.put(this._filledIds[j]);
            this._filledIds[j] = null;
        }
        this.visibleRange[0] = visibleRange[0];
        this.visibleRange[1] = visibleRange[1];
    }

    private checkNeedUpdate(visibleRange: number[]): boolean {
        return this.visibleRange[0] != visibleRange[0] || this.visibleRange[1] != visibleRange[1];
    }

    // 填充View.
    private updateView(visibleRange: number[]) {
        for (let i = visibleRange[0]; i <= visibleRange[1]; i++) {
            if (!this.dataChanged) {
                if (this._filledIds[i]) {
                    continue;
                }
            }
            let child = this._filledIds[i] || this._items.get() || cc.instantiate(this.itemTemplate);
            child.removeFromParent(false);
            this._filledIds[i] = this.adapter._getView(child, i);
            this.horizontal ?
                this._layoutHorizontal(child, i) :
                this._layoutVertical(child, i);
        }
        this.dataChanged = false;
    }

    // 获取当前屏幕可见元素索引.
    private getVisibleRange(): number[] {
        if (this.adapter == null) {
            return null;
        }
        let scrollOffset = this.scrollView.getScrollOffset();
        let startIndex = 0;

        if (this.horizontal) {
            startIndex = Math.floor(-scrollOffset.x / (this._itemWidth + this.spacing.x))
        } else {
            startIndex = Math.floor(scrollOffset.y / (this._itemHeight + this.spacing.y))
        }
        if (startIndex < 0) {
            startIndex = 0;
        }
        let visible = this.column * (startIndex + this._itemsVisible + this.spawnCount);
        if (visible >= this.adapter.getCount()) {
            visible = this.adapter.getCount() - 1;
        }
        return [startIndex, visible];
    }

    private init() {
        if (this.scrollView) {
            this.content = this.scrollView.content;
            this.horizontal = this.scrollView.horizontal;
            if (this.horizontal) {
                this.scrollView.vertical = false;
                this.content.anchorX = 0;
                this.content.anchorY = this.content.parent.anchorY;
                this.content.x = 0 - this.content.parent.width * this.content.parent.anchorX;
                this.content.y = 0;
            } else {
                this.scrollView.vertical = true;
                this.content.anchorX = this.content.parent.anchorX;
                this.content.anchorY = 1;
                this.content.x = 0;
                this.content.y = this.content.parent.height * this.content.parent.anchorY;
            }
        } else {
            console.error("ListView need a scrollView for showing.")
        }

        let itemOne = this._items.get() || cc.instantiate(this.itemTemplate);
        this._items.put(itemOne);
        this._itemHeight = itemOne.height || 10;
        this._itemWidth = itemOne.width || 10;

        if (this.horizontal) {
            this._itemsVisible = Math.ceil((this.content.parent.width - this.margin.x - this.margin.width) / (this._itemWidth + this.spacing.x));
        } else {
            this._itemsVisible = Math.ceil((this.content.parent.height - this.margin.y - this.margin.height) / (this._itemHeight + this.spacing.y));
        }
        this.notifyUpdate();
    }
}

export class Pager {
    private listView: ListView = null;

    // 每一页的item数量。用于控制一次滚动多少条。
    private pageOfItems: number = 0;

    //当前所在的页数位置。
    private currentPageIndex: number = 0;

    // Page 变化事件。
    private onPageChangeListener: Function = null;

    constructor(listView: ListView, pageOfItems: number = 0) {
        this.listView = listView;
        this.pageOfItems = pageOfItems;
        // this.getPageCount();
    }

    public getPageCount(): number {
        if (!this.listView.getAdapter()) return 1;
        const count = this.listView.getAdapter().getCount();
        if (!this.pageOfItems) {
            this.pageOfItems = this.listView.getVisibleElements();
        }
        if (this.pageOfItems <= 0) {
            this.pageOfItems = 1;
        }
        const pages = Math.ceil(count / this.pageOfItems);
        console.log("page = ", pages);
        return pages;
    }

    public getCurrentPage(): number {
        return this.currentPageIndex;
    }

    public prePage() {
        this.currentPageIndex--;
        if (this.currentPageIndex < 0) this.currentPageIndex = 0;
        this.listView.scrollToPage(this.currentPageIndex, this.pageOfItems, 0.2);
        if (this.onPageChangeListener) {
            this.onPageChangeListener(this.listView, this.currentPageIndex);
        }
    }

    public nextPage() {
        this.currentPageIndex++;
        const pageCount = this.getPageCount();
        if (this.currentPageIndex > pageCount - 1) {
            this.currentPageIndex = pageCount - 1;
        }
        this.listView.scrollToPage(this.currentPageIndex, this.pageOfItems, 0.2);
        if (this.onPageChangeListener) {
            this.onPageChangeListener(this.listView, this.currentPageIndex);
        }
    }

    public canPrePage(): boolean {
        return this.currentPageIndex > 0;
    }

    public canNextPage(): boolean {
        return this.currentPageIndex < this.getPageCount() - 1;
    }

    public setOnPageChangeListener(l: (listView: ListView, curPage: number) => void) {
        this.onPageChangeListener = l;
    }
}

// 数据绑定的辅助适配器
export abstract class AbsAdapter {

    private dataSet: any[] = [];

    public setDataSet(data: any[]) {
        this.dataSet = data;
    }

    public getCount(): number {
        return this.dataSet.length;
    }

    public getItem(posIndex: number): any {
        return this.dataSet[posIndex];
    }

    public _getView(item: cc.Node, posIndex: number): cc.Node {
        this.updateView(item, posIndex);
        return item;
    }

    public abstract updateView(item: cc.Node, posIndex: number);
}

ListAdapter.js

import {AbsAdapter} from "./ListView";

const ListItem = require('./ListItem');

cc.Class({
    extends: AbsAdapter,
    updateView(item, posIndex) {
        //cc.log("posIndex: " + posIndex);
        item.getComponent(ListItem).setLabelStr(posIndex);
    }
});

ListItem.js

cc.Class({
    extends: cc.Component,

    properties: {
        label: {
            default: null,
            type: cc.Label,
        }
     
    },

    setLabelStr(str){
        this.label.string = str;
    }
});

HelloWorld.js

import ListView, {AbsAdapter} from "./ListView";

const ListAdapter = require('./ListAdapter');

cc.Class({
    extends: cc.Component,

    properties: {
        listView: {
            default: null,
            type: ListView
        },
        tipLabel: {
            type: cc.Label,
            default: null
        }
    },

    start() {
        const adapter = new ListAdapter();
        adapter.setDataSet([1, 2, 3, 4, 5, 6, 7, 8, 89, 9, 12, 1243, 45, 564, 6756, 876, 7988, 789, 78987, 978, 45, 6732, 423, 42]);
        this.listView.setAdapter(adapter);
    }
});

 

 

把 ListView.ts 拖到一个节点上,然后设置 ScrollView

 

 

 

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012338130/article/details/104331876

智能推荐

飞凌嵌入式基于全志A40i、T3处理器在台区智能融合终端上的应用_ningmengzier的博客-程序员宅基地

台区智能融合终端也就是配电智能融合终端,配电智能融合终端作为低压配电物联网的核心,是泛在电力物联网技术架构体系中数量最多的边设备,可与低压智能设备、智能电表等就地组网完成台区智能化改造。配电智能融合终端通过物联协议接入物联管理平台,可实现台区设备状态全感知、故障主动研判和抢修、改善供电质量等功能。台区智能融合终端是 TTU 的演进形态,结合了 TTU ,集中器的硬件功能,并且加入了更多的智能监测等功能。 应用云编排 APP实现了低压台区侧、线路侧和客户 侧营配源端数据同步采集和高级应用的边缘计算。台...

在Linux系统中增加一块SCSI硬盘,重新启动计算机。_cheerileeyoki的博客-程序员宅基地_在linux系统中增加一块scsi硬盘

在Linux系统中增加一块SCSI硬盘,重新启动计算机。1.添加新磁盘(不要开启虚拟机)查看新磁盘2.重启虚拟机输入fdisk-l查看磁盘信息3.对新建的磁盘进行分区操作fdisk /dev/sdb输入m可以获取帮助输入m 再次输入n 接着输入p 和1 后面两步默认回车输入W 保存并退出4.对新建的分区进行格式化:mkfs -t ext3 /dev/sdb15.下面便是对于分好区的/dev/sdb1 这一个分区进行挂载及.

Array 分析_chenglang1933的博客-程序员宅基地

参考http://blog.csdn.net/allenlooplee/article/details/4680601 ecall.cpp中宏定义 #define FCFuncFlags(intrinsicID, dynamicID) \ (BYTE*)( (((BYTE...

Tomcat8安装及配置步骤_普通网友的博客-程序员宅基地_tomcat8安装及配置教程

1.下载tomcat8 ,官网地址:http://tomcat.apache.org/根据自己电脑的系统 ,下载对应的版本zip ,我下载的是window64位的zip包下一步:2.解压缩apache-tomcat-8.5.11-windows-x64.zip到D盘3.环境变量配置:(请先看文章结尾)3.1 系统变量 ,新增CATALINA_HOME,地址就是刚才文件夹 ,不需要带/bin3.2 ,PATH中 ,末尾新添加%CATALINA_HOME%in;%CATALINA_HOM

OpenVAS-开放式漏洞评估扫描仪_lin~258的博客-程序员宅基地

OpenVAS是一个功能齐全的漏洞扫描程序。其功能包括未经身份验证和身份验证的测试,各种高级和低级互联网和工业协议,大规模扫描的性能调优以及用于实现任何类型的漏洞测试的强大内部编程语言。扫描程序从具有较长历史记录和每日更新的源中获取用于检测漏洞的测试。OpenVAS自2006年以来一直由Greenbone Networks公司开发和推动。作为商业漏洞管理产品系列 Greenbone Enterprise Appliance 的一部分,扫描程序与其他开源模块一起构成了Greenbone 漏洞管理。它与.

github代码搜索技巧_颖持的博客-程序员宅基地

代码搜索网站: 代码:GitHubCodaseOhlohkrugleMerobase Component FinderGoogle Code Archive SymbolHound 可以搜索特殊符号的搜索引擎,程序员的福音,遇到 Bash、正则之类的问题时候的利器! Hoogle Haskell 的专用函数搜索引擎,妈妈再也不用担心我的 Functional Programming...

随便推点

CCF CSP 201412-2 Z字形扫描_Yuhan の Blog的博客-程序员宅基地_ccf csp z字形扫描

思路:1.规律就是每一次斜着的扫描,这些点的坐标和相等;2.坐标和超过n+1后需要单独考虑;代码:#include&lt;bits/stdc++.h&gt;using namespace std;#define p_b(a) push_back(a)#define rp(i,n) for(int i=0;i&lt;n;i++)#define rpn(i,n) for(int i=1...

【备份】git命令行_wangfy_的博客-程序员宅基地

常用git clonegit checkoutgit status -sgit diffgit loggit reset HEAD^git addgit commitgit pushgit helpPS D:\&gt; git --helpusage: git [--version] [--help] [-C &lt;path&gt;] ...

内存访问越界_a1232345的博客-程序员宅基地

1. 原理分析经常有些新C++程序员问:C++的类的成员个数是不是有限制,为什么我加一个变量后程序就死了?或者说:是不是成员变量的顺序很重要,为什么我两个成员变量顺序换一换程序就不行了?凡此种种之怪现象,往往都是内存访问越界所致。何谓内存访问越界,简单的说,你向系统申请了一块内存,在使用这块内存的时候,超出了你申请的范围。例如,你明明申请的是100字节的空间,但是你由于某种原因写入了1

java实现秒杀业务之实现登陆功能_Richard678的博客-程序员宅基地

第一步:数据库设计:点击转储SQL文件 导出数据和结构DROP TABLE IF EXISTS `user`;CREATE TABLE `user` ( `id` int(11) NOT NULL, `nickname` varchar(255) DEFAULT NULL, `password` varchar(32) DEFAULT NULL, `salt` ...

python3练习题--求质数(素数)_PythonStory的博客-程序员宅基地

题目:求100以内的质数(素数)。代码:#!/usr/bin/python3import mathl = [ ]for a in range(1,100): for b in range(2,int(math.sqrt(a)+1)):#质数只需要不能整除2~根号自己就可以了。 l.append(a%b)#将所有b遍历的结果加到列表中 if 0 not in

latex生成pdf中文标签乱码&pdf复制乱码_doudouwenhui的博客-程序员宅基地_casthesis 书签乱码

一、中文标签乱码1、解决方法:http://www.baidu.com/s?ie=utf-8&amp;tn=98041461_s_hao_pg&amp;wd=csdn\documentclass[pdflatex]{CASthesis}%pdftex 选择pdflatex编译后使用gbk2uni再次使用pdflatex编译2、\documentclass[dvipdfm]{CASt...

推荐文章

热门文章

相关标签