Nativescript在ListView中向上滚动时显示元素-视差效果

Nativescript在ListView中向上滚动时显示元素-视差效果,nativescript,nativescript-telerik-ui,nativescript-angular,radlistview,Nativescript,Nativescript Telerik Ui,Nativescript Angular,Radlistview,我想得到一个关于如何实现Whatsapp或Facebook等应用程序中的一个功能的建议,在这些应用程序中,你有一个列表和一个标题,它并不总是可见的,但当用户开始从列表中的任何位置向上滚动时,就会逐渐显示出来 在whatsapp和facebook中,向上滚动的手势会使搜索栏慢慢出现在屏幕顶部,而列表本身在搜索栏显示满之前不会滚动(至少这是android实现) 我需要一个关于如何使用Nativescript angular和Telerik RadListView(android+ios)实现这一点的

我想得到一个关于如何实现Whatsapp或Facebook等应用程序中的一个功能的建议,在这些应用程序中,你有一个列表和一个标题,它并不总是可见的,但当用户开始从列表中的任何位置向上滚动时,就会逐渐显示出来

在whatsapp和facebook中,向上滚动的手势会使搜索栏慢慢出现在屏幕顶部,而列表本身在搜索栏显示满之前不会滚动(至少这是android实现)

我需要一个关于如何使用Nativescript angular和Telerik RadListView(android+ios)实现这一点的建议。据我所知,telerik通常不建议将ListView放在ScrollView中

谢谢


编辑:我了解到它被称为视差效果,并在本机android中找到了它的示例,但在带有ListView的nativescript中没有找到(确实找到了一个带有ScrollView和常规StackLayout的示例,但里面没有ListView)。

您可以查看可用的“实现视差滚动效果”NativeScript marketplace官方网站“示例”部分中的示例展示了如何实现这种效果。只需转到并搜索“视差”。还有一个提供了这种功能,但我不确定它的质量。

这里是一个使用Angular(无需ScrollView)实现的可滚动视差效果RadListView的示例。它还提供了将列表标题粘贴在顶部的示例

请告诉我它是否适合你

组件模板:

<GridLayout class="page">
<RadListView (scrolled)="onScroll($event)" [items]="dataItems" itemReorder="true"
    (itemReordered)="onItemReordered($event)">
  <ListViewGridLayout tkListViewLayout scrollDirection="Vertical" spanCount="1" ios:itemHeight="150"
      dynamicItemSize="false"></ListViewGridLayout>

  <ng-template tkListItemTemplate let-item="item">
    <StackLayout orientation="vertical">
      <!-- list item content goes here -->
    </StackLayout>
  </ng-template>

  <ng-template tkListViewHeader>
    <StackLayout>
      <GridLayout #fixedHeaderContainer class="fixed-header-container">
        <label text="Fixed Content" verticalAlignment="center"></label>
      </GridLayout>

      <StackLayout class="list-header-container">
        <StackLayout #listHeaderContainer>
          <label text="List Title"></label>
        </StackLayout>
      </StackLayout>
    </StackLayout>
  </ng-template>
</RadListView>

<GridLayout verticalAlignment="top" [height]="dockContainerHeight" [opacity]="dockContainerOpacity">
  <FlexboxLayout justifyContent="flex-start" alignItems="center" class="docked-label-wrapper">
    <button class="fas" text="&#xf053;"></button>

    <StackLayout flexGrow="1" height="100%" [opacity]="dockContentOpacity" orientation="horizontal">
      <label text="List Title"></label>
    </StackLayout>
  </FlexboxLayout>
</GridLayout>
组件ts:

  import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
  import { ListViewEventData, ListViewScrollEventData } from 'nativescript-ui-listview';
  import { DataItem, DataItemService } from './data-items.service';

  export const DOCK_HEADER_HEIGHT = 58;

  @Component({
    moduleId: module.id,
    selector: 'app-comp-name',
    templateUrl: './comp-name.component.html',
    styleUrls: ['./comp-name.component.scss']
  })
  export class CompNameComponent implements OnInit {
    dataItems: DataItem[];
    dockContainerHeight = DOCK_HEADER_HEIGHT;
    dockContainerOpacity = 0;
    dockContentOpacity = 0;

    @ViewChild('fixedHeaderContainer', { static: false })
    fixedHeaderContainerRef: ElementRef;

    @ViewChild('listHeaderContainer')
    listHeaderContainerRef: ElementRef;

    constructor(private _dataItemService: DataItemService) {}

    ngOnInit(): void {
        this.dataItems = this._dataItemService.getDataItems();
    }

    onItemReordered(args: ListViewEventData) {
        console.log('Item reordered. Old index: ' + args.index + ' ' + 'new index: ' + args.data.targetIndex);
    }

    onScroll(args: ListViewScrollEventData) {
        if (!this.fixedHeaderContainerRef) {
        return;
        }

        const offset = args.scrollOffset < 0 ? 0 : args.scrollOffset;
        const fixedHeaderHeight = this.fixedHeaderContainerRef.nativeElement.getActualSize().height;

        this.applyFixedHeaderTransition(offset);
        this.applyTitleTransition(offset, fixedHeaderHeight);
        this.applyDockHeaderTransition(offset, fixedHeaderHeight);
    }

    private applyFixedHeaderTransition(scrollOffset: number) {
        this.fixedHeaderContainerRef.nativeElement.translateY = scrollOffset;
    }

    private applyTitleTransition(scrollOffset: number, fixedHeaderHeight: number) {
        const maxHeightChange = fixedHeaderHeight - DOCK_HEADER_HEIGHT;
        const titleElement = this.listHeaderContainerRef.nativeElement;

        if (maxHeightChange < scrollOffset) {
        titleElement.translateX = -(scrollOffset - maxHeightChange) / 1.2;
        titleElement.translateY = -(scrollOffset - maxHeightChange) * 2;
        titleElement.scaleX = 1 - (scrollOffset - maxHeightChange) / fixedHeaderHeight;
        titleElement.scaleY = 1 - (scrollOffset - maxHeightChange) / fixedHeaderHeight;
        } else {
        titleElement.translateX = 0;
        titleElement.translateY = 0;
        titleElement.scaleX = 1;
        titleElement.scaleY = 1;
        }
    }

    private applyDockHeaderTransition(scrollOffset: number, fixedHeaderHeight: number) {
        const maxHeightChange = fixedHeaderHeight - DOCK_HEADER_HEIGHT;
        const containerOpacity = 1 - scrollOffset / maxHeightChange;

        this.dockContainerOpacity = containerOpacity <= 0 ? 1 : 0;
        this.dockContentOpacity = (scrollOffset - (fixedHeaderHeight - DOCK_HEADER_HEIGHT)) / DOCK_HEADER_HEIGHT - 0.2;
    }
  }
从'@angular/core'导入{Component,ElementRef,OnInit,ViewChild};
从“nativescript ui listview”导入{ListViewEventData,ListViewScrollEventData};
从“./data items.service”导入{DataItem,DataItemService};
导出常数DOCK_HEADER_HEIGHT=58;
@组成部分({
moduleId:module.id,
选择器:“应用程序组件名称”,
templateUrl:“./comp name.component.html”,
样式URL:['./comp name.component.scss']
})
导出类CompNameComponent实现OnInit{
数据项:数据项[];
dockContainerHeight=船坞\船头\船坞高度;
dockContainerCapacity=0;
DockContentoCapacity=0;
@ViewChild('fixedHeaderContainer',{static:false})
fixedHeaderContainerRef:ElementRef;
@ViewChild('listHeaderContainer')
listHeaderContainerRef:ElementRef;
构造函数(私有_dataItemService:dataItemService){}
ngOnInit():void{
this.dataItems=this.\u dataItemService.getDataItems();
}
onItemReordered(参数:ListViewEventData){
log('项目重新排序。旧索引:'+args.index+'+'新索引:'+args.data.targetIndex);
}
onScroll(参数:ListViewScrollEventData){
如果(!this.fixedHeaderContainerRef){
返回;
}
常量偏移=args.scrollOffset<0?0:args.scrollOffset;
const fixedHeaderHeight=this.fixedHeaderContainerRef.nativeElement.getActualSize().height;
此。applyFixedHeaderTransition(偏移);
此.ApplyTitle转换(偏移,固定磁头高度);
此.applyDockHeaderTransition(偏移,固定磁头高度);
}
专用applyFixedHeaderTransition(滚动偏移量:编号){
this.fixedHeaderContainerRef.nativeElement.translateY=滚动偏移量;
}
私有ApplyTitle转换(scrollOffset:编号,fixedHeaderHeight:编号){
const maxHeightChange=固定头高度-船坞头高度;
const titleElement=this.listHeaderContainerRef.nativeElement;
如果(最大高度变化<滚动偏移){
titleElement.translateX=-(scrollOffset-maxHeightChange)/1.2;
titleElement.translateY=-(scrollOffset-maxHeightChange)*2;
titleElement.scaleX=1-(scrollOffset-maxHeightChange)/fixedHeaderHeight;
titleElement.scaleY=1-(scrollOffset-maxHeightChange)/fixedHeaderHeight;
}否则{
titleElement.translateX=0;
titleElement.translateY=0;
titleElement.scaleX=1;
titleElement.scaleY=1;
}
}
专用applyDockHeaderTransition(滚动偏移量:编号,固定磁头高度:编号){
const maxHeightChange=固定头高度-船坞头高度;
常量容器容量=1-滚动偏移量/最大高度变化;

this.dockContainerOpacity=containerOpacity您好,谢谢您的回复。我知道您提到的示例,这就是我想说的:“我找到了一个包含ScrollView和常规StackLayout的示例,而不是包含ListView的示例”。它以一种非常简单的方式实现视差,然而,滚动部分是一个简单的堆栈布局,而不是列表视图。由于所有内容都包含在一个滚动视图中,因此不能以相同的方式实现,因为不能将列表视图放在滚动视图中。嗯,明白了。我没有看到任何这样的示例或讨论在列表组件中定义视差效果。您描述“不总是可见”的方式可能是指“动态操作栏”,而不是视差效果?嗨,我不确定确切的术语是什么,当您说“动态操作栏”时,您在想什么?我在谷歌上搜索了一下,但没有找到一个能说明我的目的的例子。我能给出的最好的例子是搜索栏在Whatsapp和Facebook应用程序中隐藏和显示在android上的方式,同时上下滚动列表。一些应用程序在这种情况下也有很好的动画效果,非常类似于视差示例一种在滚动底部布局时导致标题出现\shrink的方法。如果你说的是这样的:,在Android上,来自“CoordinatorLayout”,我不确定iOS是否有类似的内置功能。在NativeScript中,CoordinatorLayout不由框架公开,但有人可以实现
  import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
  import { ListViewEventData, ListViewScrollEventData } from 'nativescript-ui-listview';
  import { DataItem, DataItemService } from './data-items.service';

  export const DOCK_HEADER_HEIGHT = 58;

  @Component({
    moduleId: module.id,
    selector: 'app-comp-name',
    templateUrl: './comp-name.component.html',
    styleUrls: ['./comp-name.component.scss']
  })
  export class CompNameComponent implements OnInit {
    dataItems: DataItem[];
    dockContainerHeight = DOCK_HEADER_HEIGHT;
    dockContainerOpacity = 0;
    dockContentOpacity = 0;

    @ViewChild('fixedHeaderContainer', { static: false })
    fixedHeaderContainerRef: ElementRef;

    @ViewChild('listHeaderContainer')
    listHeaderContainerRef: ElementRef;

    constructor(private _dataItemService: DataItemService) {}

    ngOnInit(): void {
        this.dataItems = this._dataItemService.getDataItems();
    }

    onItemReordered(args: ListViewEventData) {
        console.log('Item reordered. Old index: ' + args.index + ' ' + 'new index: ' + args.data.targetIndex);
    }

    onScroll(args: ListViewScrollEventData) {
        if (!this.fixedHeaderContainerRef) {
        return;
        }

        const offset = args.scrollOffset < 0 ? 0 : args.scrollOffset;
        const fixedHeaderHeight = this.fixedHeaderContainerRef.nativeElement.getActualSize().height;

        this.applyFixedHeaderTransition(offset);
        this.applyTitleTransition(offset, fixedHeaderHeight);
        this.applyDockHeaderTransition(offset, fixedHeaderHeight);
    }

    private applyFixedHeaderTransition(scrollOffset: number) {
        this.fixedHeaderContainerRef.nativeElement.translateY = scrollOffset;
    }

    private applyTitleTransition(scrollOffset: number, fixedHeaderHeight: number) {
        const maxHeightChange = fixedHeaderHeight - DOCK_HEADER_HEIGHT;
        const titleElement = this.listHeaderContainerRef.nativeElement;

        if (maxHeightChange < scrollOffset) {
        titleElement.translateX = -(scrollOffset - maxHeightChange) / 1.2;
        titleElement.translateY = -(scrollOffset - maxHeightChange) * 2;
        titleElement.scaleX = 1 - (scrollOffset - maxHeightChange) / fixedHeaderHeight;
        titleElement.scaleY = 1 - (scrollOffset - maxHeightChange) / fixedHeaderHeight;
        } else {
        titleElement.translateX = 0;
        titleElement.translateY = 0;
        titleElement.scaleX = 1;
        titleElement.scaleY = 1;
        }
    }

    private applyDockHeaderTransition(scrollOffset: number, fixedHeaderHeight: number) {
        const maxHeightChange = fixedHeaderHeight - DOCK_HEADER_HEIGHT;
        const containerOpacity = 1 - scrollOffset / maxHeightChange;

        this.dockContainerOpacity = containerOpacity <= 0 ? 1 : 0;
        this.dockContentOpacity = (scrollOffset - (fixedHeaderHeight - DOCK_HEADER_HEIGHT)) / DOCK_HEADER_HEIGHT - 0.2;
    }
  }