package com.beloo.widget.chipslayoutmanager; import android.support.v7.widget.RecyclerView; import android.view.View; import com.beloo.widget.chipslayoutmanager.anchor.AnchorViewState; import com.beloo.widget.chipslayoutmanager.layouter.ICanvas; import com.beloo.widget.chipslayoutmanager.layouter.IStateFactory; abstract class ScrollingController implements IScrollingController { private ChipsLayoutManager lm; private IScrollerListener scrollerListener; private IStateFactory stateFactory; ICanvas canvas; interface IScrollerListener { void onScrolled(IScrollingController scrollingController, RecyclerView.Recycler recycler, RecyclerView.State state); } ScrollingController(ChipsLayoutManager layoutManager, IStateFactory stateFactory, IScrollerListener scrollerListener) { this.lm = layoutManager; this.scrollerListener = scrollerListener; this.stateFactory = stateFactory; this.canvas = layoutManager.getCanvas(); } final int calculateEndGap() { if (lm.getChildCount() == 0) return 0; int visibleViewsCount = lm.getCompletelyVisibleViewsCount(); if (visibleViewsCount == lm.getItemCount()) return 0; int currentEnd = stateFactory.getEndViewBound(); int desiredEnd = stateFactory.getEndAfterPadding(); int diff = desiredEnd - currentEnd; if (diff < 0) return 0; return diff; } final int calculateStartGap() { if (lm.getChildCount() == 0) return 0; int currentStart = stateFactory.getStartViewBound(); int desiredStart = stateFactory.getStartAfterPadding(); int diff = currentStart - desiredStart; if (diff < 0) return 0; return diff; } @Override public final boolean normalizeGaps(RecyclerView.Recycler recycler, RecyclerView.State state) { int backwardGap = calculateStartGap(); if (backwardGap > 0) { offsetChildren(-backwardGap); //if we have normalized start gap, normalizing bottom have no sense return true; } int forwardGap = calculateEndGap(); if (forwardGap > 0) { scrollBy(-forwardGap, recycler, state); return true; } return false; } final int calcOffset(int d) { int childCount = lm.getChildCount(); if (childCount == 0) { return 0; } int delta = 0; if (d < 0) { //if content scrolled down delta = onContentScrolledBackward(d); } else if (d > 0) { //if content scrolled up delta = onContentScrolledForward(d); } return delta; } /** * invoked when content scrolled forward (return to older items) * * @param d not processed changing of x or y axis, depending on lm state * @return delta. Calculated changing of x or y axis, depending on lm state */ final int onContentScrolledBackward(int d) { int delta; AnchorViewState anchor = lm.getAnchor(); if (anchor.getAnchorViewRect() == null) { return 0; } if (anchor.getPosition() != 0) { //in case 0 position haven't added in layout yet delta = d; } else { //in case top view is a first view in adapter and wouldn't be any other view above int startBorder = stateFactory.getStartAfterPadding(); int viewStart = stateFactory.getStart(anchor); int distance; distance = viewStart - startBorder; if (distance >= 0) { // in case over scroll on top border delta = distance; } else { //in case first child showed partially delta = Math.max(distance, d); } } return delta; } /** * invoked when content scrolled up (to newer items) * * @param d not processed changing of x or y axis, depending on lm state * @return delta. Calculated changing of x or y axis, depending on lm state */ final int onContentScrolledForward(int d) { int childCount = lm.getChildCount(); int itemCount = lm.getItemCount(); int delta; View lastView = lm.getChildAt(childCount - 1); int lastViewAdapterPos = lm.getPosition(lastView); if (lastViewAdapterPos < itemCount - 1) { //in case lower view isn't the last view in adapter delta = d; } else { //in case lower view is the last view in adapter and wouldn't be any other view below int viewEnd = stateFactory.getEndViewBound(); int parentEnd = stateFactory.getEndAfterPadding(); delta = Math.min(viewEnd - parentEnd, d); } return delta; } abstract void offsetChildren(int d); @Override public final int scrollHorizontallyBy(int d, RecyclerView.Recycler recycler, RecyclerView.State state) { return canScrollHorizontally()? scrollBy(d, recycler, state) : 0; } @Override public final int scrollVerticallyBy(int d, RecyclerView.Recycler recycler, RecyclerView.State state) { return canScrollVertically()? scrollBy(d, recycler, state) : 0; } private int scrollBy(int d, RecyclerView.Recycler recycler, RecyclerView.State state) { d = calcOffset(d); offsetChildren(-d); scrollerListener.onScrolled(this, recycler, state); return d; } private int getLaidOutArea() { return stateFactory.getEndViewBound() - stateFactory.getStartViewBound(); } /** @see ChipsLayoutManager#computeVerticalScrollOffset(RecyclerView.State) * @see ChipsLayoutManager#computeHorizontalScrollOffset(RecyclerView.State) */ private int computeScrollOffset(RecyclerView.State state) { if (lm.getChildCount() == 0 || state.getItemCount() == 0) { return 0; } int firstVisiblePos = lm.findFirstVisibleItemPosition(); int lastVisiblePos = lm.findLastVisibleItemPosition(); final int itemsBefore = Math.max(0, firstVisiblePos); if (!lm.isSmoothScrollbarEnabled()) { return itemsBefore; } final int itemRange = Math.abs(firstVisiblePos - lastVisiblePos) + 1; final float avgSizePerRow = (float) getLaidOutArea() / itemRange; return Math.round(itemsBefore * avgSizePerRow + (stateFactory.getStartAfterPadding() - stateFactory.getStartViewBound())); } /** @see ChipsLayoutManager#computeVerticalScrollExtent(RecyclerView.State) * @see ChipsLayoutManager#computeHorizontalScrollExtent(RecyclerView.State) */ private int computeScrollExtent(RecyclerView.State state) { if (lm.getChildCount() == 0 || state.getItemCount() == 0) { return 0; } int firstVisiblePos = lm.findFirstVisibleItemPosition(); int lastVisiblePos = lm.findLastVisibleItemPosition(); if (!lm.isSmoothScrollbarEnabled()) { return Math.abs(lastVisiblePos - firstVisiblePos) + 1; } return Math.min(stateFactory.getTotalSpace(), getLaidOutArea()); } private int computeScrollRange(RecyclerView.State state) { if (lm.getChildCount() == 0 || state.getItemCount() == 0) { return 0; } if (!lm.isSmoothScrollbarEnabled()) { return state.getItemCount(); } int firstVisiblePos = lm.findFirstVisibleItemPosition(); int lastVisiblePos = lm.findLastVisibleItemPosition(); // smooth scrollbar enabled. try to estimate better. final int laidOutRange = Math.abs(firstVisiblePos - lastVisiblePos) + 1; // estimate a size for full list. return (int) ((float) getLaidOutArea() / laidOutRange * state.getItemCount()); } @Override public final int computeVerticalScrollExtent(RecyclerView.State state) { return canScrollVertically() ? computeScrollExtent(state) : 0; } @Override public final int computeVerticalScrollRange(RecyclerView.State state) { return canScrollVertically() ? computeScrollRange(state) : 0; } @Override public final int computeVerticalScrollOffset(RecyclerView.State state) { return canScrollVertically() ? computeScrollOffset(state) : 0; } @Override public final int computeHorizontalScrollRange(RecyclerView.State state) { return canScrollHorizontally() ? computeScrollRange(state) : 0; } @Override public final int computeHorizontalScrollOffset(RecyclerView.State state) { return canScrollHorizontally() ? computeScrollOffset(state) : 0; } @Override public final int computeHorizontalScrollExtent(RecyclerView.State state) { return canScrollHorizontally() ? computeScrollExtent(state) : 0; } }