Angular cdk 学习之 Scrolling

壤驷旭
2023-12-01

       Angular cdk Scrolling包给我们提供了一些指令和Service来应对滑动事件。推荐大家尽量去看官网上的介绍https://material.angular.io/cdk/scrolling/overview

       想要在我们项目中使用cdk Scrolling功能,需要在我们模块里面 import {ScrollDispatchModule} from ‘@angular/cdk/scrolling’;

一 cdkScrollable and ScrollDispatcher

       cdkScrollable指令单独使用的时候没有特别的效果,cdkScrollable指令一般用来配合ScrollDispatcher Service使用。任何添加了cdkScrollable指令的视图元素都会注册到ScrollDispatcher里面去。然后可以在ScrollDispatcher里面去监听元素的滑动事件。

1.1 ScrollDispatcher常用方法介绍

export declare class ScrollDispatcher implements OnDestroy {

    /**
     * 订阅全局`scroll`和`resize`
     */
    _globalSubscription: Subscription | null;
    /**
     * 注册到ScrollDispatcher里面的元素(添加了CdkScrollable指令的元素)
     */
    scrollContainers: Map<CdkScrollable, Subscription>;
    /**
     * 注册CdkScrollable(我们不用管,我们在某个视图元素上添加CdkScrollable指令会自动注册的)
     */
    register(scrollable: CdkScrollable): void;
    /**
     * 取消注册
     */
    deregister(scrollable: CdkScrollable): void;
    /**
     * 可以去订阅任务一个注册CdkScrollable的scroll事件
     *
     * **Note:** 为了避免每次滚动都发送状态变化检测,内部采用的是NgZone.runOutsideAngular。所以如果你想每次都检测变化就采用NgZone.run的放
     * 有疑问可以去搜下NgZone的使用
     */
    scrolled(auditTimeInMs?: number): Observable<CdkScrollable | void>;
    /**
     * 监听elementRef视图元素的祖先元素有滚动事件,当然了对应的祖先元素需要添加CdkScrollable指令
     * auditTimeInMs参数是为了防止事件发送过快,可以设置多长时间收一次事件
     */
    ancestorScrolled(elementRef: ElementRef, auditTimeInMs?: number): Observable<CdkScrollable | void>;
    /**
     *  elementRef添加了CdkScrollable指令的祖先元素
     */
    getAncestorScrollContainers(elementRef: ElementRef): CdkScrollable[];
}

1.2 cdkScrollable and ScrollDispatcher使用

import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {CdkScrollable, ScrollDispatcher} from '@angular/cdk/overlay';

@Component({
    selector: 'app-cdk-scrolling',
    template: `
        <!-- 通过cdkScrollable指令配合ScrollDispatcher来监听节点的scrolling -->
        <div cdkScrollable class="scrolling-parent">
            <div #scrollingParent class="scrolling-item">item 1</div>
            <div class="scrolling-item">item 2</div>
            <div class="scrolling-item">item 3</div>
        </div>
        <!-- 这个div没有添加cdkScrollable指令,所以这个div的scrolling事件ScrollDispatcher获取不到 -->
        <div class="scrolling-parent">
            <div class="scrolling-item">item 1</div>
            <div class="scrolling-item">item 2</div>
            <div class="scrolling-item">item 3</div>
        </div>
    `,
    styles: [`
        .scrolling-parent {
            height: 100px;
            width: 200px;
            border: 1px solid black;
            padding: 8px;
            overflow-y: auto;
        }
        .scrolling-item {
            height: 50px;
        }
    `]
})
export class CdkScrollingComponent implements OnInit, AfterViewInit {

    @ViewChild('scrollingParent')
    childDiv: ElementRef;

    constructor(private scrollDispatcher: ScrollDispatcher) {
    }

    ngOnInit() {
        /**
         * 监听所有ScrollDispatcher里面注册的CdkScrollable的scroll事件
         */
        this.scrollDispatcher.scrolled().subscribe((scrollable: CdkScrollable) => {
            if (scrollable) {
                console.log('发生scroll了,來源于:');
                console.log(scrollable.getElementRef().nativeElement);
            }
        });
    }

    ngAfterViewInit(): void {
        /**
         * 第二个参数auditTimeInMs表示事件延时多少秒发生
         * 当祖先设置了cdkScrollable指令,在孩子里面也能抓到scrolling事件
         */
        this.scrollDispatcher.ancestorScrolled(this.childDiv).subscribe((scrollable: CdkScrollable) => {
            if (scrollable) {
                console.log('祖先发生scroll了,來源于:');
                console.log(scrollable.getElementRef().nativeElement);
            }
        });
        // 获取ScrollDispatcher里面所有注册了scrolling的组件信息
        console.log(this.scrollDispatcher.scrollContainers);
    }
}

二 ViewportRuler

       ViewportRuler也是cdk Scrolling里面提供的一个Service。用来测量浏览器的视图窗口信息。

2.1 ViewportRuler常用方法

export declare class ViewportRuler implements OnDestroy {
    /**
     * 获取浏览器窗口的宽度和高度 
     */
    getViewportSize(): Readonly<{
        width: number;
        height: number;
    }>;
    /**
     * 获取浏览器窗口的rect信息(left、top、right、bottom) 
     */
    getViewportRect(): ClientRect;
    /**
     * 获取浏览器窗口的滑动位置(top、left) 
     */
    getViewportScrollPosition(): ViewportScrollPosition;
    /**
     * 监听浏览器窗口大小变化
     */
    change(throttleTime?: number): Observable<Event>;
}

2.2 ViewportRuler使用实例

import {Component, OnInit} from '@angular/core';
import {ViewportRuler} from '@angular/cdk/overlay';

@Component({
    selector: 'app-cdk-scrolling',
    template: `
    `
})
export class CdkScrollingComponent implements OnInit {


    constructor(private viewPortRuler: ViewportRuler) {
    }

    ngOnInit() {
        /**
         * ViewportRuler 用来监听窗口的大小
         */
        // { width, height }
        console.log(this.viewPortRuler.getViewportSize());

        // { bottom, height, left, right, top, width }
        console.log(this.viewPortRuler.getViewportRect());

        // { top, left }
        console.log(this.viewPortRuler.getViewportScrollPosition());

        // native resize event object
        this.viewPortRuler.change().subscribe(resizeEvent => console.log(resizeEvent));

    }

}

三 CdkVirtualScrollViewport

       angular cdk 里面给提供了一个scrolling的组件CdkVirtualScrollViewport - cdk-virtual-scroll-viewport。CdkVirtualScrollViewport组件在list展示的时候非常有用,比如有我们list里面的item有1000项的时候。CdkVirtualScrollViewport 并不会直接给我们加载出1000项。只会加载部分项。一边滑动一边加载。如果有做过android的话,应该会知道ListView里面的回收机制。CdkVirtualScrollViewport组件做的也是类似的事情。

3.1 CdkVirtualScrollViewport组件主要方法介绍

export class CdkVirtualScrollViewport extends CdkScrollable implements OnInit, OnDestroy {

    /**
     * 这个是CdkFixedSizeVirtualScroll里面的属性,也是可以设置在CdkVirtualScrollViewport组件上的
     * CdkVirtualScrollViewport组件里面每个item的大小(通过orientation来知道是高度还宽度)
     */
    @Input()
    get itemSize(): number { return this._itemSize; }

    /**
     * 这个是CdkFixedSizeVirtualScroll里面的属性,也是可以设置在CdkVirtualScrollViewport组件上的
     * 多加载滚动范围之后多少像素的距离(最小值),比如CdkVirtualScrollViewport的高度是200px,minBufferPx
     * 是100px;  item有好多的情况下不用全部绘制出来。绘制200像素的内容就好了。
     */
    @Input()
    get minBufferPx(): number { return this._minBufferPx; }

    /**
     * 这个是CdkFixedSizeVirtualScroll里面的属性,也是可以设置在CdkVirtualScrollViewport组件上的
     * 多加载滚动范围之后多少像素的距离(最大值)
     */
    @Input()
    get maxBufferPx(): number { return this._maxBufferPx; }

    /**
     * viewport 方向
     */
    @Input() orientation: 'horizontal' | 'vertical' = 'vertical';

    /**
     * 活动过程中,当前可见第一项的index
     */
    @Output() scrolledIndexChange: Observable<number> =
        Observable.create((observer: Observer<number>) =>
            this._scrollStrategy.scrolledIndexChange.subscribe(index =>
                Promise.resolve().then(() => this.ngZone.run(() => observer.next(index)))));

    /**
     * 设置滑动位置
     */
    scrollToOffset(offset: number, behavior: ScrollBehavior = 'auto'){}

    /**
     * 设置滑动的index
     */
    scrollToIndex(index: number,  behavior: ScrollBehavior = 'auto') {}

    /**
     * 测量可滑动的大小
     */
    measureScrollOffset(from?: 'top' | 'left' | 'right' | 'bottom' | 'start' | 'end'): number {}

    /**
     * 测量所有item在一起的高度
     */
    measureRenderedContentSize(): number{}}


上面主要方法介绍我把CdkVirtualScrollViewport组件和CdkFixedSizeVirtualScroll指令揉到一起去介绍了,因为它俩本来就是一起配合使用的。

3.2 CdkVirtualForOf指令

       Selector: [cdkVirtualFor][cdkVirtualForOf]

       CdkVirtualScrollViewport适用于list的情况,list循环的时候得配合CdkVirtualForOf指令来使用,CdkVirtualForOf的用法和ngFor的用法是一样的。CdkVirtualForOf指令是专门为CdkVirtualScrollViewport组件提供的。 就不用ngFor指令了。切记。如果想咱们平常一样用ngFor指令去循环,很多CdkVirtualScrollViewport组件里面特有的功能就用不了了。

3.3 CdkVirtualScrollViewport组件使用

       想使用angular cdk Scrolling里面的功能先导入ScrollDispatchModule模块。

import {ScrollDispatchModule} from '@angular/cdk/scrolling';

3.3.1 CdkVirtualForOf指令的使用

       主要是知道CdkVirtualForOf里面有哪些参数是可用的

import {Component, ViewChild} from '@angular/core';
import {CdkVirtualScrollViewport} from "@angular/cdk/scrolling";

@Component({
    selector: 'app-virtual-scroll',
    template: `
        <!-- itemSize是每个item的高度 -->
        <cdk-virtual-scroll-viewport #scrollComponent [itemSize]="18 * 7" class="example-viewport">
            <!-- cdkVirtualFor的可以用参数如下 -->
            <div *cdkVirtualFor="let item of items;
                       let index = index;
                       let count = count;
                       let first = first;
                       let last = last;
                       let even = even;
                       let odd = odd;" [class.example-alternate]="odd">
                <div class="example-item-detail">Item: {{item}}</div>
                <div class="example-item-detail">Index: {{index}}</div>
                <div class="example-item-detail">Count: {{count}}</div>
                <div class="example-item-detail">First: {{first ? 'Yes' : 'No'}}</div>
                <div class="example-item-detail">Last: {{last ? 'Yes' : 'No'}}</div>
                <div class="example-item-detail">Even: {{even ? 'Yes' : 'No'}}</div>
                <div class="example-item-detail">Odd: {{odd ? 'Yes' : 'No'}}</div>
            </div>
        </cdk-virtual-scroll-viewport>

        <button style="margin-top: 8px; margin-bottom: 8px; background-color: #007bff; color: red"
                (click)="onButtonClick()">
            点击跳转到第10个item
        </button>
    `,
    styles: [`
        .example-viewport {
            height: 200px;
            width: 200px;
            border: 1px solid black;
        }

        .example-item-detail {
            height: 18px;
        }
    `]
})
export class VirtualScrollComponent {

    @ViewChild('scrollComponent')
    private _scrollViewport: CdkVirtualScrollViewport;

    /**
     * CdkVirtualScrollViewport组件的数据源
     */
    items = Array.from({length: 100000}).map((_, i) => `Item #${i}`);

    /**
     * 点击按钮的时候跳转到第10项
     */
    onButtonClick() {
        this._scrollViewport.scrollToIndex(10);
    }
}

3.3.2 水平方向滚动的CdkVirtualScrollViewport组件

       orientation属性的使用

import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core';

@Component({
    selector: 'app-virtual-scroll-horizontal',
    template: `
        <div class="cdk-virtual-scroll-data-source-example">
            <cdk-virtual-scroll-viewport orientation="horizontal" itemSize="50" class="example-viewport">
                <div *cdkVirtualFor="let item of items" class="example-item">{{item}}</div>
            </cdk-virtual-scroll-viewport>
        </div>
    `,
    styles: [`
        .cdk-virtual-scroll-data-source-example .example-viewport {
            height: 200px;
            width: 200px;
            border: 1px solid black;
        }

        .cdk-virtual-scroll-data-source-example .example-viewport .cdk-virtual-scroll-content-wrapper {
            display: flex;
            flex-direction: row;
        }

        .cdk-virtual-scroll-data-source-example .example-item {
            width: 50px;
            height: 100%;
            writing-mode: vertical-lr;
        }

    `],
    encapsulation: ViewEncapsulation.None,
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class VirtualScrollHorizontalComponent {

    /**
     * CdkVirtualScrollViewport组件里面的数据源
     */
    items = Array.from({length: 100000}).map((_, i) => `Item #${i}`);

}


       关于angular cdk Scrolling里面咱们就先讲这么些,主要讲了cdkScrollable指令、ScrollDispatcher Service、ViewportRuler Service、CdkVirtualScrollViewport组件等一些很浅显的用法。如果大家有什么疑问欢迎留言。文章中涉及到的例子链接地址 https://github.com/tuacy/angular-cdk-study

 类似资料: