tabris-js icon indicating copy to clipboard operation
tabris-js copied to clipboard

CollectionView "scroll" event deltas sometimes cannot be used to reliably track scroll offset

Open cpetrov opened this issue 4 years ago • 2 comments

Problem description

CollectionView does not have an API like ScrollView's offsetY. The only option to track the scroll offset is to manually sum deltaY provided by the scroll event. However, on Android, in some cases deltaY might not reflect the reality. The delta sum gets inaccurate particularly when calling CollectionView#reveal(). Maybe some scroll events are not getting triggered in this scenario.

Expected behavior

Fix deltaY so that it can be used to track the CollectionView scroll offset in conjunction with reveal() and/or provide an API like ScrollView's offsetY in addition.

Environment

  • Tabris.js version: nightly, 2.9.0
  • OS: Android 9

Code snippet

Scroll to the bottom of the list manually and scroll to the top manually. The offset calculated by using the deltas will be almost 0 as expected.

Scroll to the bottom of the list manually and tap on the "scroll to top" button. The offset will be off by a small margin. Repeat this a few times and the error of the sum will increase.

import {$, CollectionView, contentView, TextView, Composite, Button} from 'tabris';

/** @param {tabris.Attributes<TextView>=} attributes */
const SectionCell = attributes =>
  <TextView background='#aaaaaa' textColor='white' font='bold 24px' alignment='centerX' {...attributes}/>;

/** @param {tabris.Attributes<TextView>=} attributes */
const ItemCell = attributes =>
  <TextView padding={[2, 5]} font='14px' alignment='left' {...attributes}/>;

const items = createItems();

contentView.append(
  <$>
    <CollectionView stretch
        itemCount={items.length}
        cellType={index => items[index].type}
        cellHeight={(_, type) => type === 'section' ? 48 : 32}
        createCell={type => type === 'section' ? SectionCell() : ItemCell()}
        updateCell={(cell, index) => cell.text = items[index].name}
        onScroll={handleScroll}/>
    <TextView id='tracker' center />
    <SectionCell stretchX height={48} id='floatingSection' text={items[0].name}/>
    <Button centerX centerY={32} text='scroll to top' onTap={scrollToTop}/>
  </$>
);

function scrollToTop() {
  tabris.contentView.find(tabris.CollectionView).first().reveal(0);
}

let offset = 0;
/** @param {tabris.CollectionViewScrollEvent<CollectionView<TextView>>} ev */
function handleScroll({deltaY}) {
  offset += deltaY;
  contentView.find('#tracker').first(TextView).text = offset.toString();
}

function createItems() {
  let itemCount = 1;
  /** @type {Array<{name: string, type: 'section' | 'item'}>} */
  const result = [];
  for (let j = 1; j <= 10; j++) {
    result.push({name: 'Section ' + itemCount++, type: 'section'});
    for (let i = 0; i < 5; i++) {
      result.push({name: 'Item ' + itemCount++, type: 'item'});
    }
  }
  return result;
}


cpetrov avatar Oct 22 '20 20:10 cpetrov

In such a case you could get the absolute position of the view in question this will be more stable. See https://docs.tabris.com/latest/layout.html#properties-bounds-and-absolutebounds

mpost avatar Oct 23 '20 07:10 mpost

Thanks @cpetrov for reporting the issue.

After investigating the issue, we found that the total deltaY that we get from scroll event always is 1 pixel less than expected(actual) when we call CollectionView#reveal(0).

P.S. 1 px is converted to Dip which was approximately 0.399... in my testing device.

The issue occurs because of the unexpected behavior of native Android components mentioned above. So it seems we have nothing to do right now.

I would suggest a hacky solution that resets offset value when it is less than 1.

const APPROXIMATION_ERROR = 1;
...
function handleScroll({deltaY}) {
  offset += deltaY;
  if (offset < APPROXIMATION_ERROR) {
    offset = 0;
  }
  contentView.find('#tracker').first(TextView).text = offset.toString();
}

We can get different values on different devices when we convert 1 pixel to Dip. So I think 1 is the average...

elshadsm avatar Nov 12 '20 10:11 elshadsm