1

I would like to get an advice on how to go around implementing a feature seen in many apps like Whatsapp or Facebook, where you have a list and a header which is not visible always, but is gradually shown when the user begins to scroll upwards from any place within a list.

In whatsapp and facebook the upward scrolling gesture causes the Search bar to slowly appear at the top of the screen, while the list itself is not scrolling until the search bar appears at full (at least this is the android implementation).

I need an advice on how to implement this using Nativescript angular with Telerik RadListView (android + ios). As far as I know, putting a ListView inside a ScrollView generally is not advised by telerik.

Thanks!

Edit: I learned it is called a parallax effect, and found examples of it in native android, however, not in nativescript with ListView (did find an example with ScrollView and regular StackLayout, not with a ListView inside).

2 Answers 2

2

You can check the available "Implementing a Parallax Scroll Effect" sample in the 'Samples' section of the official NativeScript marketplace website that shows how to implement just that effect. Just go to Market.nativescript.org and search for 'parallax'. Also there is a plugin that provides such functionality but I am not sure of its quality.

Sign up to request clarification or add additional context in comments.

7 Comments

Hi, thanks for replying. I'm aware of the example you are referring to, this is the one I had in mind saying this: "did find an example with ScrollView and regular StackLayout, not with a ListView inside". It implements parallax in a very simple way, however, the scrolling part is a simple StackLayout, not a ListView. And because everything is contained within a ScrollView, it cannot be implemented the same way, because of the constraint of not putting Listview in a ScrollView.
Hmm got you. Well I have not seen any such examples or discussions regarding a parallax effect inside a list component. The way you are describing the "not visible always" maybe you are talking about "dynamic actionbar" rather than a parallax effect?
Hi, I'm not sure what the exact term would be, what do you have in mind when you say "dynamic action bar"? I googled for it but did not find an example which would demonstrate what I'm going for. The best example I can give is the way that SearchBar hides and appears on android in Whatsapp and Facebook apps, while scrolling the list up and down. Some apps also have a nice animation effect while this happens, very similar to the parallax example, the one which causes the header to appear\shrink while scrolling the bottom layout.
If you are talking about something like this: androcode.es/wp-content/uploads/2015/10/simple_coordinator.gif , on Android that comes from the "CoordinatorLayout" I am not sure if iOS has something similar built-in. In NativeScript the CoordinatorLayout is not exposed by the framework but someone could implement and expose it via plugin.
Yes, it is very similar, thanks for bringing the correct term to my knowledge. I found this feature request github.com/NativeScript/NativeScript/issues/4908 which talks about it too.
|
1

Here is an example of a scrollable parallax effect RadListView implemented using Angular (no ScrollView needed). It also provides an example of sticking the list header at the top.

Please me know if it works for you.

Component template:

<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>
Component scss:
.fixed-header-container {
  height: 200;
  padding: 0 16;
  background-color: green;

  label {
    font-size: 30;
    font-weight: 700;
    color: $white;
  }
}

.list-header-container {
  margin-top: -12;
  border-radius: 12 12 0 0;
  background-color: #ffffff;

  label {
    margin: 16 0;
    font-size: 22;
    color: black;
    vertical-align: center;
  }

  .smaller-label {
    font-size: 12;
    color: #909090;
  }
}

RadListView {
  height: 100%;
  background-color: #ffffff;
}

.docked-label-wrapper {
  margin: 0 0 10;
  background-color: #ffffff;

  .fas {
    margin: 0;
    font-size: 18;
  }

  label {
    font-size: 18;
    color: black;
    vertical-align: center;
  }
}

Component 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;
    }
  }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.