felipecsl/QuickReturn

The firstVisibleItem position is wrong when the top target view is out of screen.

Opened this issue · 1 comments

In the sample, in the list view's onScroll method.

Re-produce step:
Scroll up and make the top target disappear, then the firstVisibleItem is immediately 1 which should be 0.
It will be bigger than expected if you continue to scroll up.

I add adjustFirstVisibleItem method, and changes the CompositeAbsListViewOnScrollListener's onScroll method to deliver the modification to other onScrollListener.

 public class AbsListViewScrollTarget
        extends QuickReturnTargetView
        implements AbsListView.OnScrollListener {

    private final AbsListView listView;
    private int quickViewHeight;
    private int itemHeight;
    private int adjustedFirstVisibleItem;

    public AbsListViewScrollTarget(AbsListView listView, View targetView,
                                   int position) {
        this(listView, targetView, position, 0);
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public AbsListViewScrollTarget(AbsListView listView, View targetView,
                                   int position, int targetViewHeight) {
        super(targetView, position);

        quickViewHeight = targetViewHeight;
        this.listView = listView;
        QuickReturnAdapter adapter = getAdapter();

        if (adapter == null) {
            throw new UnsupportedOperationException(
                    "You need to set the listView adapter before adding a targetView");
        }

        if (position == POSITION_TOP) {
            adapter.setTargetViewHeight(targetViewHeight);
        }

        if (listView instanceof ListView) {
            adapter.setVerticalSpacing(((ListView) listView).getDividerHeight());
        } else if (listView instanceof GridView
                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            adapter.setVerticalSpacing(((GridView) listView).getVerticalSpacing());
        }
    }

    @Override
    protected int getComputedScrollY() {
        if (listView.getChildCount() == 0 || listView.getAdapter() == null) {
            return 0;
        }

        int pos = listView.getFirstVisiblePosition();
        View view = listView.getChildAt(0);
        return getAdapter().getPositionVerticalOffset(pos) - view.getTop();
    }

    private QuickReturnAdapter getAdapter() {
        ListAdapter adapter = listView.getAdapter();

        if (adapter instanceof WrapperListAdapter) {
            adapter = ((WrapperListAdapter) adapter).getWrappedAdapter();
        }

        if (!(adapter instanceof QuickReturnAdapter)) {
            throw new UnsupportedOperationException(
                    "Your QuickReturn ListView adapter must be an instance of QuickReturnAdapter.");
        }

        return (QuickReturnAdapter) adapter;
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
    }

    @Override
    public void onScroll(@NonNull AbsListView view, int firstVisibleItem,
                         int visibleItemCount, int totalItemCount) {
        if (listView.getAdapter() == null || quickReturnView == null) {
            return;
        }

        calculateItemHeight(firstVisibleItem);
        // The maxVerticalOffset is less than 1.
        int maxVerticalOffset = getAdapter().getMaxVerticalOffset() + itemHeight;
        int listViewHeight = listView.getHeight();
        int rawY = -Math.min(maxVerticalOffset > listViewHeight
                ? maxVerticalOffset - listViewHeight
                : listViewHeight, getComputedScrollY());

        int translationY = currentTransition.determineState(rawY, quickReturnView.getHeight());

        translateTo(translationY);
        Log.d(TAG, "maxVerticalOffset: " + maxVerticalOffset +
                ", listViewHeight: " + listViewHeight +
                ", rawY: " + rawY +
                ", getComputedScrollY(): " + getComputedScrollY() +
                ", translationY: " + translationY);

        adjustFirstVisibleItem(firstVisibleItem, rawY, translationY);
    }

    private void calculateItemHeight(int firstVisibleItem) {
        if (itemHeight == 0) {
            View view = listView.getAdapter().getView(firstVisibleItem, null, listView);
            view.measure(
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
            itemHeight = view.getMeasuredHeight();
        }
    }

    private void adjustFirstVisibleItem(int firstVisibleItem, int rawY, int translationY) {
        if (listView.getAdapter().getCount() == 0) return;
        if (translationY > 0) {
            return;
        }

        if (itemHeight == 0) return;

        int f;

        if (rawY > 0) {
            rawY = 0;
        }
        if (translationY > 0) {
            translationY = 0;
        }
        if (translationY < -quickViewHeight) {
            f = Math.abs(rawY + quickViewHeight) / itemHeight;
        } else if (-quickViewHeight < translationY && translationY < 0) {
            f = Math.abs(rawY + Math.abs(quickViewHeight + translationY)) / itemHeight;
        } else {
            f = Math.abs(rawY) / itemHeight;
        }

        Log.d(TAG, "actual first visible item: " + f);
        adjustedFirstVisibleItem = f;
    }

    public int getAdjustedFirstVisibleItem() {
        return adjustedFirstVisibleItem;
    }
}
public class CompositeAbsListViewOnScrollListener
        extends ArrayList<AbsListView.OnScrollListener>
        implements AbsListView.OnScrollListener {

    public void registerOnScrollListener(final AbsListView.OnScrollListener listener) {
        add(listener);
    }

    public void unregisterOnScrollListener(final AbsListView.OnScrollListener listener) {
        remove(listener);
    }

    @Override
    public void onScrollStateChanged(final AbsListView view, final int scrollState) {
        for (AbsListView.OnScrollListener listener : this) {
            listener.onScrollStateChanged(view, scrollState);
        }
    }

    @Override
    public void onScroll(final AbsListView view, final int firstVisibleItem,
                         final int visibleItemCount, final int totalItemCount) {
        if (this.size() <= 0) {
            return;
        }
        AbsListViewScrollTarget adjustedListener = (AbsListViewScrollTarget)this.get(0);
        adjustedListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
        int f = adjustedListener.getAdjustedFirstVisibleItem();
        Log.d("CompositeAbsList", "adjusted: " + f);
        for (int i = 1; i < this.size(); ++i) {
            this.get(i).onScroll(view, f, visibleItemCount, totalItemCount);
        }
    }
}