/* * Copyright © 2016-2017, Turing Technologies, an unincorporated organisation of Wynne Plaga * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.turingtechnologies.materialscrollbar; import android.support.v4.view.ViewCompat; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.util.Log; import android.view.View; /* * Lots of complicated maths taken mostly from Google. Abandon all hope, ye who enter here. */ class ScrollingUtilities { private MaterialScrollBar materialScrollBar; ScrollingUtilities(MaterialScrollBar msb){ materialScrollBar = msb; } ICustomScroller customScroller; private ScrollPositionState scrollPosState = new ScrollPositionState(); private int constant; private LinearLayoutManager layoutManager; private class ScrollPositionState { // The index of the first visible row private int rowIndex; // The offset of the first visible row private int rowTopOffset; // The height of a given row (they are currently all the same height) private int rowHeight; } void scrollHandleAndIndicator(){ int scrollBarY; getCurScrollState(); if(customScroller != null){ constant = customScroller.getDepthForItem(materialScrollBar.recyclerView.getChildAdapterPosition(materialScrollBar.recyclerView.getChildAt(0))); } else { constant = scrollPosState.rowHeight * scrollPosState.rowIndex; } scrollBarY = (int) getScrollPosition(); ViewCompat.setY(materialScrollBar.handleThumb, scrollBarY); materialScrollBar.handleThumb.invalidate(); if(materialScrollBar.indicator != null){ int element; if (materialScrollBar.recyclerView.getLayoutManager() instanceof GridLayoutManager) { element = scrollPosState.rowIndex * ((GridLayoutManager)materialScrollBar.recyclerView.getLayoutManager()).getSpanCount(); } else { element = scrollPosState.rowIndex; } materialScrollBar.indicator.setText(element); materialScrollBar.indicator.setScroll(scrollBarY + materialScrollBar.getTop()); } } private float getScrollPosition(){ getCurScrollState(); int scrollY = materialScrollBar.getPaddingTop() + constant - scrollPosState.rowTopOffset; return ((float) scrollY / getAvailableScrollHeight()) * getAvailableScrollBarHeight(); } private int getRowCount(){ int rowCount = materialScrollBar.recyclerView.getLayoutManager().getItemCount(); if (materialScrollBar.recyclerView.getLayoutManager() instanceof GridLayoutManager) { int spanCount = ((GridLayoutManager) materialScrollBar.recyclerView.getLayoutManager()).getSpanCount(); rowCount = (int) Math.ceil((double) rowCount / spanCount); } return rowCount; } /** * Returns the available scroll bar height: * AvailableScrollBarHeight = Total height of the visible view - thumb height */ int getAvailableScrollBarHeight() { return materialScrollBar.getHeight() - materialScrollBar.handleThumb.getHeight(); } void scrollToPositionAtProgress(float touchFraction) { if(customScroller == null) { int spanCount = 1; if (materialScrollBar.recyclerView.getLayoutManager() instanceof GridLayoutManager) { spanCount = ((GridLayoutManager) materialScrollBar.recyclerView.getLayoutManager()).getSpanCount(); } // Stop the scroller if it is scrolling materialScrollBar.recyclerView.stopScroll(); getCurScrollState(); //The exact position of our desired item int exactItemPos = (int) (getAvailableScrollHeight() * touchFraction); //Scroll to the desired item. The offset used here is kind of hard to explain. //If the position we wish to scroll to is, say, position 10.5, we scroll to position 10, //and then offset by 0.5 * rowHeight. This is how we achieve smooth scrolling. LinearLayoutManager layoutManager = ((LinearLayoutManager) materialScrollBar.recyclerView.getLayoutManager()); layoutManager.scrollToPositionWithOffset(spanCount * exactItemPos / scrollPosState.rowHeight, -(exactItemPos % scrollPosState.rowHeight)); } else { if(layoutManager == null){ layoutManager = ((LinearLayoutManager) materialScrollBar.recyclerView.getLayoutManager()); } int position = customScroller.getItemIndexForScroll(touchFraction); int offset = (int) (customScroller.getDepthForItem(position) - touchFraction * getAvailableScrollHeight()); layoutManager.scrollToPositionWithOffset(position, offset); } } int getAvailableScrollHeight() { int visibleHeight = materialScrollBar.getHeight(); int scrollHeight; if(customScroller != null){ scrollHeight = materialScrollBar.getPaddingTop() + customScroller.getTotalDepth() + materialScrollBar.getPaddingBottom(); } else { scrollHeight = materialScrollBar.getPaddingTop() + getRowCount() * scrollPosState.rowHeight + materialScrollBar.getPaddingBottom(); } return scrollHeight - visibleHeight; } void getCurScrollState() { scrollPosState.rowIndex = -1; scrollPosState.rowTopOffset = -1; scrollPosState.rowHeight = -1; if (materialScrollBar.recyclerView.getAdapter() == null) { Log.e("MaterialScrollBarLib", "The adapter for your recyclerView has not been set; " + "skipping layout."); return; } int itemCount = materialScrollBar.recyclerView.getAdapter().getItemCount(); // Return early if there are no items if (itemCount == 0) { return; } View child = materialScrollBar.recyclerView.getChildAt(0); scrollPosState.rowIndex = materialScrollBar.recyclerView.getChildAdapterPosition(child); if (materialScrollBar.recyclerView.getLayoutManager() instanceof GridLayoutManager) { scrollPosState.rowIndex = scrollPosState.rowIndex / ((GridLayoutManager) materialScrollBar.recyclerView.getLayoutManager()).getSpanCount(); } if(child == null){ scrollPosState.rowTopOffset = 0; scrollPosState.rowHeight = 0; } else { scrollPosState.rowTopOffset = materialScrollBar.recyclerView.getLayoutManager().getDecoratedTop(child); scrollPosState.rowHeight = child.getHeight(); } } }