ksoichiro / Android-ObservableScrollView

Android library to observe scroll events on scrollable views.
http://ksoichiro.github.io/Android-ObservableScrollView/
Apache License 2.0
9.66k stars 2.06k forks source link

ObservableListView get scrollY incorrect in some cases #3

Open ongakuer opened 10 years ago

ongakuer commented 10 years ago

I want to scroll listview to the bottom when Activity launched, but scrollY incorrect.

public class MainActivity extends ActionBarActivity implements ObservableScrollViewCallbacks {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        List<String> items = new ArrayList<String>();
        for (int i = 1; i <= 100; i++) {
            items.add("Item " + i);
        }
        final ObservableListView listView = (ObservableListView) findViewById(R.id.listview);
        listView.setScrollViewCallbacks(this);
        final ArrayAdapter<String> adapter =
                new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items);
        listView.setAdapter(adapter);
        ViewTreeObserver vto = listView.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                    listView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                } else {
                    listView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }
                int count = adapter.getCount() - 1;
                listView.setSelection(count == 0 ? 1 : count > 0 ? count : 0);
            }
        });
    }

    @Override public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) {
        Log.e("MainActivity", "onScrollChanged scrollY = " + scrollY);
    }

    @Override public void onDownMotionEvent() {

    }

    @Override public void onUpOrCancelMotionEvent(ScrollState scrollState) {

    }
}
ksoichiro commented 10 years ago

@ongakuer Thanks for your feedback. I ran your code (with a little modification) and I got logs like this after the Activity launched:

11-19 21:35:28.550: E/MainActivity(30584): onScrollChanged scrollY = 0
11-19 21:35:28.563: E/MainActivity(30584): onScrollChanged scrollY = 1801

Assuming that your are talking about "scrollY = 0 is incorrect"... ObservableScrollView is currently depends on its parent AbsListView's onScrollListener, and to intercept into its original listener, onScrollChanged happens at the very beginning of the view creation, and I think it's inevitable currently. If it is a problem, could you please provide more information? I don't know if I can solve this problem, but I'll try.

ongakuer commented 10 years ago

@ksoichiro Thanks for your reply. "scrollY = 0 " is correct , but "scrollY = 1801" is incorrect. When you scroll to top , scrollY's value would be negative.

That's may be a weakness of ListView. In OnScrollListener 's onScroll method: The first time , firstVisibleItem is 0. scrollY = 0. The second time , firstVisibleItem is 88 (maybe) . mChildrenHeights haven't enough values and mPrevScrolledChildrenHeight is incorrect, so scrollY's value begin strange.

ksoichiro commented 10 years ago

@ongakuer Thank you! I understood the issue. (But still don't know how to fix it.) Please give me some time.

ongakuer commented 10 years ago

@ksoichiro http://developer.android.com/reference/android/widget/AbsListView.html#computeVerticalScrollOffset() Use computeVerticalScrollOffset can get scrollY simply.

mCallbacks.onScrollChanged(computeVerticalScrollOffset(), mFirstScroll, mDragging);

ksoichiro commented 10 years ago

@ongakuer Thanks for sharing this. But is it correct offset? As written in the document above, it seems that it is the offset of the scrollbar's thumb and not the scroll position...

ongakuer commented 10 years ago

Yes, you can try it.

ksoichiro commented 10 years ago

I tried and tested using ToolbarControlListViewActivity in sample code.

At first, I enabled this debug log just before mCallbacks.onScrollChanged in ObservableListView,

LogUtils.v(TAG, "first: " + firstVisiblePosition + " scrollY: " + mScrollY + " first height: " + firstVisibleChild.getHeight() + " first top: " + firstVisibleChild.getTop());

and I scrolled to the bottom of the list. I got this log:

first: 90 scrollY: 13057 first height: 144 first top: -49

and then, I changed mScrollY in the log code above to computeVerticalScrollOffset(), I got this:

first: 90 scrollY: 9034 first height: 144 first top: -49

I tested on xxhdpi device, so the height of the each items are:

items item height total
0 and 1 168px 168 * 2 = 336px
2 to 89 144px 144 * 88 = 12672px

91st(position 90) item's offset is -49, so the scrollY should be 336 + 12672 + 49 = 13057px, in this case. Therefore we can't use computeVerticalScrollOffset() instead of mScrollY...

ongakuer commented 10 years ago

Well,computeVerticalScrollOffset() can not return exact offset . But the result was calculated by using mFirstPosition and getChildAt(0).

We animated Toolbar by using the difference between mPrevScrollY and mScrollY. The relative value maybe is right whether manually scroll or scroll by setting position.

ksoichiro commented 10 years ago

Thanks for checking this! Then, using computeVerticalScrollOffset() can be a workaround in your case. But I think, replacing mScrollY into computeVerticalScrollOffset() in ObservableListView has some side effects (because it's almost always incorrect offset) and it may break some standard usage: ListView that starts from position 0 and its scrollY is used for the calculation for some other widgets animation or something. And you can call computeVerticalScrollOffset() with the current implementation.

So... how about approximating each item's height to getChildAt(0).getHeight() when mChildrenHeights doesn't know their height? It's not a complete and correct solution, but better than now. This solution doesn't affect to the ListViews that start from position 0. With this solution and on my device, the first case (scrollY is 1801) became 12889, it's almost correct.

ongakuer commented 10 years ago

I think the scrollY may still be negative if ListView start from bottom and scroll back to top. Anyway, it's almost correct.

ksoichiro commented 10 years ago

As you say, it may still be negative if the approximation is incorrect. So long as the views are not drawn even once, we can not know the exactly correct scroll position, I think. I made a fix using getChildAt(0).getHeight() and added a sample of ListView that is scrolling from the bottom. (Sorry but currently I have no idea of how to fix this completely.)

Thank you so much for all these discussions and investigations.