Skip to content

Latest commit

 

History

History
549 lines (415 loc) · 19.2 KB

FlexScrollView.md

File metadata and controls

549 lines (415 loc) · 19.2 KB

FlexScrollView

FlexScrollView is a high performance scroll-view for famo.us based on the famous-flex LayoutController technology. The scrollview separates the actual layout algorithm from the scrollview, making it possible to select any layout into the scrollview.

By default FlexScrollView uses the ListLayout layout, which supports features such as sticky headers and margins & spacing.

Index

Getting started

To use the FlexScrollView in your project, install famous-flex using npm or bower:

npm install famous-flex

bower install famous-flex

To create the FlexScrollView use:

var FlexScrollView = require('famous-flex/FlexScrollView');

var scrollView = new FlexScrollView();
this.add(scrollView);

Flow mode animations

By enabling flow mode, renderables are smoothly transitioned to their new state whenever items are inserted, removed or the scrollview is resized.

FlowMode

The effect as seen above is created by setting the following options:

var scrollView = new FlexScrollView({
    flow: true,             // enable flow-mode (can only be enabled from the constructor)
    insertSpec: {           // render-spec used when inserting renderables
        opacity: 0          // start opacity is 0, causing a fade-in effect,
        //size: [0, 0],     // uncommented to create a grow-effect
        //transform: Transform.translate(-300, 0, 0) // uncomment for slide-in effect
    },
    //removeSpec: {...},    // render-spec used when removing renderables
    nodeSpring: {           // spring-options used when transitioning between states
        dampingRatio: 0.8,  // spring damping ratio
        period: 1000        // duration of the animation
    }
});

Collection layout

Any layout can be selected into the FlexScrollView. Even layouts that do not support scrolling can be selected (in that case scrolling is disabled). For instance, to use the multi-cell CollectionLayout layout use:

var CollectionLayout = require('famous-flex/layouts/CollectionLayout');

var scrollView = new FlexScrollView({
    layout: CollectionLayout,
    layoutOptions: {
        itemSize: [50, 50],
        margins: [50, 10, 50, 10],
        spacing: [10, 10]
    }
});

Pagination

Pagination causes the scrollview to neatly align the renderables with the edge of the scrollview. This option is disabled by default. To enable it, set the paginated option to true in the constructor or using setOptions:

var scrollView = new FlexScrollView({
    paginated: true
});

Pagination modes:

FlowMode FlowMode

mode description
PaginationMode.PAGE Paginates to the page immediately after the last touch/mouse event. (default)
PaginationMode.SCROLL Scrolls the view but only paginates when the energy of the scroll-particle is below a certain thresshold.

To change the pagination-mode or thresshold use:

var scrollView = new FlexScrollView({
    paginated: true,
    paginationMode: FlexScrollView.PaginationMode.SCROLL,
    paginationEnergyThresshold: 0.01
});

Inserting & removing items

Inserting & removing items can be done in various ways.

When using the insert/push/remove functions, you can specify a render-spec to animate the renderable when inserting/removing (possible when flow mode is enabled).

scrollView.insert(0, new Surface({}));  // insert at the beginning
scrollView.insert(3, new Surface({}), {opacity: 0}); // insert after index 3, with fade-in effect
scrollView.insert(-1, new Surface({})); // insert after the end
scrollView.push(new Surface({}));       // insert after the end
scrollView.push(new Surface({}), {size: [0, 0]}); // insert with grow effect
scrollView.remove(0);                   // remove at index 0
scrollView.remove(-1, {opacity: 0});    // remove last item and fade-out
scrollView.removeAll();                 // removes all items

Using setDataSource or sequenceFrom:

// Using an array
scrollView.setDataSource([surface1, surface2, ...]);
scrollView.sequenceFrom([surface1, surface2, ...]);

// Using a ViewSequence
var viewSequence = new ViewSequence();
viewSequence.push(surface1);
viewSequence.push(surface2);
viewSequence.push(...);
scrollView.setDataSource(viewSequence);
scrollView.sequenceFrom(viewSequence); // or using sequenceFrom

Auto event piping

When inserting renderables, you typically have to pipe the events from the renderable to the scrollview. This step can be automated by enabling the autoPipeEvents option.

var scrollView = new FlexScrollView({
    autoPipeEvents: true
});

// No need to call surface.pipe, the scrollview does this automatically for you.
scrollView.push(new Surface({
    content: 'My surface'
}));

Getting the visible item(s)

To get the currently visible items, use the following functions:

var arr = scrollView.getVisibleItems();       // array of completely or partially visible items
var first = scrollView.getFirstVisibleItem(); // first visible item
var last = scrollView.getLastVisibleItem();   // last visible item

var index = scrollView.getCurrentIndex();     // quickly get index of first visible item

The result of these functions is an object or array of objects with the following properties:

var item = {
    index: number,           // index within the data-source
    renderNode: renderable,  // renderable that was added to the datasource
    viewSequence: node,      // view-sequence node associated with the renderable
    visiblePerc: [0..1]      // percentage of how much of the renderable is visible
};

The getFirstVisibleItem and getLastVisibleItem functions return the first or last item that is completely visible or the item that is partially visible and satisfies the visibleItemThresshold option. By default, this option is set to 0.5, which means that when an item is 50% percent visible, it is considered to be visible.

Scrolling

To scroll the view use the following functions:

scrollView.goToPage(index);              // scrolls to the renderable at the given index
scrollView.goToFirstPage();              // scrolls to the first renderable
scrollView.goToLastPage();               // scrolls to the last renderable
scrollView.goToPreviousPage();           // scrolls to the previous renderable
scrollView.goToNextPage();               // scrolls to the next renderable
scrollView.goToRenderNode(renderable);   // scrolls to the given renderable

scrollView.scroll(delta);                // scrolls x pixels in previous or next direction
var delta = scrollView.canScroll(delta); // tests whether the view can scroll the given delta

// `ensureVisible` optionally scrolls the view the least amount to ensure
// that the given item is fully visible
scrollView.ensureVisible(index);         // renderable at the given index
scrollView.ensureVisible(viewSequence);  // view-sequence node
scrollView.ensureVisible(renderable);    // renderable

By default the FlexScrollView listens to touch-events and mouse-wheel (trackpad) events only. It is also possible to enable scrolling by pressing down on the mouse and moving the mouse. To enable this option use:

var scrollView = new FlexScrollView({
    mouseMove: true
});

Sometimes it is useful to disable scrolling (through user-inputs). To enable or disable scrolling use the enabled option:

scrollView.setOptions({
    enabled: false   // disables scrolling through touch/trackpad/mouse events
});

By default, the scroll-view uses an overscroll effect when the bounds are reached. To disable overscroll use:

scrollView.setOptions({
    overscroll: false   // disable overscroll
});

Margins & Spacing (ListLayout)

Margins & Spacing

Margins and spacing are part of the ListLayout that is selected into the scrollview by default. They can be set in the constructor or by using the setLayoutOptions() function:

var scrollView = new FlexScrollView({
    layoutOptions: {
        margins: [10, 5, 0, 15], // margins in clockwise order: top, right, bottom, left
        spacing: 5
    }
});

Instead of specifying all margins separately, you can also use the shorthand notation:

scrollView.setLayoutOptions({ margins: 5 }); // set all margins to 5
scrollView.setLayoutOptions({ margins: [5] }); // set all margins to 5
scrollView.setLayoutOptions({ margins: [5, 10] }); // set top/bottom to 5, left/right to 10

Direction & alignment

Both X and Y directions are supported. By default, content is layed out in the Y-direction. To set the direction, use the constructor options or the setOptions function:

var Utility = require('famous/utilities/Utility');
var scrollView = new FlexScrollView({
    direction: Utility.Direction.X // set direction to horizontal
});

scrollView.setOptions({
    direction: Utility.Direction.Y // direction & alignment can be changed on the fly
});

By default, renderables are aligned to the top (for direction: Y) and to the left (for direction: X). In some cases you may want to align the renderables to the bottom or the right. A good example of this is a chat application. When a keyboard is shown on a tablet or phone, it shifts the whole screen up. In this case you want to align the renderables to the bottom for when the screen is resized.

To set the alignment, use the alignment option. The alignment option can have two values, 0 (default) which aligns to the top or left, and 1 for aligning to the bottom or right.

// create a vertical scrollview which is aligned to the bottom
var scrollView = new FlexScrollView({
    direction: Utility.Direction.Y,
    alignment: 1
});

Clipping & ContainerSurfaces

When you need to clip the contents of the scrollview so it doesn't overflow, you typically have create a ContainerSurface and embed the scrollview inside it. The FlexScrollView can perform this task for you through the useContainer option. When useContainer is set to true, the scrollview is wrapped inside a ContainerSurface with the properties set to overflow: hidden by default:

var scrollView = new FlexScrollView({
    useContainer: true // wraps scrollview inside a ContainerSurface
});

Use the container option to override the options for the ContainerSurface:

var scrollView = new FlexScrollView({
    useContainer: true,
    container: { // options passed to the ContainerSurface
        properties: {
            overflow: 'hidden',
            backgroundColor: 'blue'
        }
    }
});

Sticky headers (ListLayout)

Sticky Headers

To enable sticky headers, set the isSectionCallback layout-option to a function which returns true when a renderable is a section:

var scrollView = new FlexScrollView({
    layoutOptions: {
        isSectionCallback: function(renderNode) {
            return renderNode.isSection;
        }
    }

A section is a regular renderable, just like any other renderable. In the following example we add a property isSection so the section can be detected by the isSectionCallback() function. Also, the renderable is moved in front so that it overlaps non-section items.

function _createSection() {
    var surface = new Surface({..});
    surface.isSection = true;
    var renderNode = new RenderNode(new Modifier({
        transform: Transform.infront
    }));
    renderNode.add(surface);
    return renderNode;
}

To add sections & items, just add them in the order that you want them to be displayed:

scrollView.push(_createSection());
scrollView.push(new Surface({}));
scrollView.push(new Surface({}));
scrollView.push(_createSection());
scrollView.push(new Surface({}));

Pull to refresh

Pull to refresh

To enable pull to refresh, assign a renderable to the pullToRefreshHeader or pullToRefreshFooter option:

var scrollView = new FlexScrollView({
    autoPipeEvents: true,
    pullToRefreshHeader: new Surface({
        size: [undefined, 50], // required
        content: 'pull to refresh header'
    }),
    pullToRefreshHeader: new Surface({...})
});

Whenever the user pulls on header or footer, the refresh event is emitted:

scrollView.on('refresh', function(event) {
    Timer.setTimeout(function() { // perform your refresh logic here

        // On completion, hide the pull to refresh header
        scrollView.hidePullToRefresh();
    }, 2000);
});

When using both header and footer pull to refresh, use event.footer to check which event was emitted:

scrollView.on('refresh', function(event) {
    if (event.footer) {
        fetchNextPage(function() { // perform your logic here

            // when done, hide the pull to refresh footer
            scrollView.hidePullToRefresh(true);
        });
    }
    else {
        fetchPreviousPage(function() { // perform your logic here

            // when done, hide the pull to refresh header
            scrollView.hidePullToRefresh(false);
        });
    }
});

To programatically show or hide the pull to refresh renderables, use the following functions:

scrollView.showPullToRefresh(footer);
scrollView.hidePullToRefresh(footer);
scrollView.isPullToRefreshVisible(footer);

To animate your pull to refresh renderable while pulling, create a custom view and implement the setPullToRefreshStatus on it. When pulling starts, stops or changes state, the FlexScrollView will call setPullToRefreshStatus(state) on the renderable to indicate these state changes. Additionally, the FlexScrollView asks the renderable for the desired pull length for triggering the refresh (getPullToRefreshSize).

See famous-refresh-loader for an example on how to create a responsive pull to refresh view.

Using RefreshLoader:

var RefreshLoader = require('famous-refresh-loader/RefreshLoader');

var scrollView = new FlexScrollView({
    autoPipeEvents: true,
    pullToRefreshHeader: new RefreshLoader({
        size: [undefined, 60],
        pullToRefresh: true,
        color: 'green'
        backgroundColor: 'white',
        particleCount: 8,
        particleSize: 7
    })
});

Advanced effects

Embedded scrollview linking

Scrollview linking

The example above shows a scrollview embedded inside another scrollview. Whenever the bottom (news) scrollview reaches the top, it delegates the scroll-events to the outer scrollview, creating a seemless scrolling experience. To create this effect, set the leadingScrollView or trailingScrollView options in the constructor:

// Create outer scrollview
var scrollView = new FlexScrollView({
    autoPipeEvents: true
});
scrollView.push(new Surface{
    size: [undefined, 100],
    content: 'top surface'
});

// Add embedded scrollview
var embeddedScrollView = new FlexScrollView({
    autoPipeEvents: true,
    leadingScrollView: scrollView
});
scrollView.push(embeddedScrollView, undefined, false); // add, but don't pipe-events!
embeddedScrollView.push(new Surface{
    size: [undefined, 100],
    content: 'top surface'
});

Similarly, this can be done for the bottom part of scrollview. In that case, set the trailingScrollView option in the constructor:

var embeddedScrollView = new FlexScrollView({
    trailingScrollView: scrollView
});

Embedded scrollview scrolling restrictions

Scrollview restrictions

When inserting a horizontal scrollview inside a vertical scrollview (or vice versa), both scrollview's respond to both vertical and horizontal input events. This is because no-one ever scrolls perfectly horizontal or vertical causing both scrollview's to scroll on their axes. To prevent both scrollview's from scrolling, you can use the following technique:

// Create vertical and horizontal scrollview and embed
// one scrollview inside the other one.
var vertScrollView = new FlexScrollView({
    touchMoveDirectionThresshold: 0.5
});
var horzScrollView = new FlexScrollView({
    touchMoveDirectionThresshold: 0.5
});
horzScrollView.push(vertScrollView);

// When the vertical scrollview starts scrolling, capture all events
// and disable scrolling on the horizontal scrollview
vertScrollView.on('scrollstart', function(event) {
    horzScrollView.setOptions({ enabled: false });
    vertScrollView.setOptions({ touchMoveDirectionThresshold: undefined });
});
vertScrollView.on('scrollend', function(event) {
    horzScrollView.setOptions({ enabled: true });
    vertScrollView.setOptions({ touchMoveDirectionThresshold: 0.5 });
});

// When the horizontal scrollview starts scrolling, capture all events
// and disable scrolling on the vertical scrollview
horzScrollView.on('scrollstart', function(event) {
    vertScrollView.setOptions({ enabled: false });
    horzScrollView.setOptions({ touchMoveDirectionThresshold: undefined });
});
horzScrollView.on('scrollend', function(event) {
    vertScrollView.setOptions({ enabled: true });
    horzScrollView.setOptions({ touchMoveDirectionThresshold: 0.5 });
});

Events

The FlexScrollView emits the following events:

event description
layoutstart Emitted before the layout-function is executed.
layoutend Emitted after the layout-function has been executed.
reflow Emitted for every render-cycle that items are reflowing into their new state.
scrollstart Emitted when scrolling starts.
scroll Emitted as the content is being scrolled (once for each frame the visible offset has changed).
pagechange Emitted whenever the visible page changes.
scrollend Emitted after scrolling stops (when the scroll particle settles).

These events are passed an event object with the following properties:

{
    target: flexScrollView,
    oldSize: [100, 100],
    size: [200, 200],
    oldScrollOffset: Number,
    scrollOffset: Number
}

When a pull-to-refresh header or footer is attached, it also emits the refresh event (see Pull to refresh).

© 2014 - 2015 IjzerenHein