Java tutorial
/* * Copyright (C) 2017 AlexMofer * * 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 am.widget.multifunctionalrecyclerview.layoutmanager; import android.content.Context; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.v4.view.AbsSavedState; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.View; /** * ??? * Created by Alex on 2017/11/3. */ @SuppressWarnings({ "WeakerAccess", "unused" }) public class BothDirectionsScrollLayoutManager extends CenterLinearLayoutManager { public static final float INVALID_PERCENTAGE = -1; private int mChildMaxWidth; private int mChildMaxHeight; private int mLeftDecorationMaxWidthOfChildMaxWidth; private int mRightDecorationMaxWidthOfChildMaxWidth; private int mTopDecorationMaxWidthOfChildMaxHeight; private int mBottomDecorationMaxWidthOfChildMaxHeight; private int mOffset = 0; private float mPercentage = INVALID_PERCENTAGE; private float mPendingPercentage = INVALID_PERCENTAGE; private int mWidthSize; private int mHeightSize; public BothDirectionsScrollLayoutManager(Context context) { super(context); } public BothDirectionsScrollLayoutManager(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } public BothDirectionsScrollLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override public int getHeightMode() { return View.MeasureSpec.UNSPECIFIED; } @Override public int getWidthMode() { return View.MeasureSpec.UNSPECIFIED; } @Override public void onMeasure(@NonNull RecyclerView.Recycler recycler, @NonNull RecyclerView.State state, int widthSpec, int heightSpec) { mWidthSize = View.MeasureSpec.getSize(widthSpec); mHeightSize = View.MeasureSpec.getSize(heightSpec); final int maxOffset = computeAnotherDirectionMaxScrollOffset(); if (maxOffset > 0) { if (mPercentage == INVALID_PERCENTAGE) { mPercentage = getDefaultScrollOffsetPercentage(); } if (mPendingPercentage != INVALID_PERCENTAGE) { mPercentage = mPendingPercentage; mPendingPercentage = INVALID_PERCENTAGE; } mOffset = Math.round(mPercentage * maxOffset); } else { mPercentage = INVALID_PERCENTAGE; mOffset = 0; } super.onMeasure(recycler, state, widthSpec, heightSpec); } /** * ?RecyclerView? * * @return ? */ public int getMeasuredWidth() { return mWidthSize; } /** * ?RecyclerView? * * @return ? */ public int getMeasuredHeight() { return mHeightSize; } @Override public void setOrientation(int orientation) { if (orientation != getOrientation()) { mPercentage = INVALID_PERCENTAGE; } super.setOrientation(orientation); } @Override public Parcelable onSaveInstanceState() { return new SavedState(super.onSaveInstanceState(), mPercentage); } @Override public void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SavedState)) super.onRestoreInstanceState(state); else { final SavedState saved = (SavedState) state; super.onRestoreInstanceState(saved.getSuperState()); mPendingPercentage = saved.getPercentage(); } } /** * ???? * * @return ?? */ protected float getDefaultScrollOffsetPercentage() { if (isLayoutInCenter()) return 0.5f; return 0; } /** * ???? * * @return ?? */ protected int computeAnotherDirectionDefaultScrollOffset() { return Math.round(getDefaultScrollOffsetPercentage() * computeAnotherDirectionMaxScrollOffset()); } /** * ???? * * @return ?? */ protected int computeAnotherDirectionMaxScrollOffset() { final int offset; if (getOrientation() == HORIZONTAL) { final int range = computeVerticalScrollRange(); final int extent = computeVerticalScrollExtent(); offset = range - extent; } else { final int range = computeHorizontalScrollRange(); final int extent = computeHorizontalScrollExtent(); offset = range - extent; } return offset < 0 ? 0 : offset; } @Override public void layoutDecorated(@NonNull View child, int left, int top, int right, int bottom) { super.layoutDecorated(child, left, top, right, bottom); final int offset = computeAnotherDirectionDefaultScrollOffset(); final int move = offset - mOffset; if (move == 0) return; if (getOrientation() == HORIZONTAL) { child.offsetTopAndBottom(move); } else { child.offsetLeftAndRight(move); } } @Override public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right, int bottom) { super.layoutDecoratedWithMargins(child, left, top, right, bottom); final int offset = computeAnotherDirectionDefaultScrollOffset(); final int move = offset - mOffset; if (move == 0) return; if (getOrientation() == HORIZONTAL) { child.offsetTopAndBottom(move); } else { child.offsetLeftAndRight(move); } } /** * ???? * * @return ?? */ public int computeAnotherDirectionScrollOffset() { return mOffset; } @Override public int computeHorizontalScrollOffset(RecyclerView.State state) { if (getOrientation() == HORIZONTAL) { return super.computeHorizontalScrollOffset(state); } return computeHorizontalScrollOffset(); } /** * ?? * * @return ?? */ public int computeHorizontalScrollOffset() { if (getOrientation() == HORIZONTAL) { final RecyclerView view = getRecyclerView(); return view == null ? 0 : view.computeHorizontalScrollOffset(); } return computeAnotherDirectionScrollOffset(); } @Override public int computeVerticalScrollOffset(RecyclerView.State state) { if (getOrientation() == VERTICAL) { return super.computeVerticalScrollOffset(state); } return computeVerticalScrollOffset(); } /** * ?? * * @return ?? */ public int computeVerticalScrollOffset() { if (getOrientation() == VERTICAL) { final RecyclerView view = getRecyclerView(); return view == null ? 0 : view.computeVerticalScrollOffset(); } return computeAnotherDirectionScrollOffset(); } @Override public int computeHorizontalScrollExtent(RecyclerView.State state) { if (getOrientation() == HORIZONTAL) { return super.computeHorizontalScrollExtent(state); } return computeHorizontalScrollExtent(); } /** * * * @return */ protected int computeHorizontalScrollExtent() { if (getOrientation() == HORIZONTAL) { final RecyclerView view = getRecyclerView(); return view == null ? 0 : view.computeHorizontalScrollExtent(); } return getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); } @Override public int computeVerticalScrollExtent(RecyclerView.State state) { if (getOrientation() == VERTICAL) { return super.computeVerticalScrollExtent(state); } return computeVerticalScrollExtent(); } /** * * * @return */ protected int computeVerticalScrollExtent() { if (getOrientation() == VERTICAL) { final RecyclerView view = getRecyclerView(); return view == null ? 0 : view.computeVerticalScrollExtent(); } return getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); } @Override public int computeHorizontalScrollRange(RecyclerView.State state) { if (getOrientation() == HORIZONTAL) { return super.computeHorizontalScrollRange(state); } return computeHorizontalScrollRange(); } /** * * * @return */ protected int computeHorizontalScrollRange() { if (getOrientation() == HORIZONTAL) { final RecyclerView view = getRecyclerView(); return view == null ? 0 : view.computeHorizontalScrollRange(); } return getChildMaxWidth(mChildMaxWidth) + mLeftDecorationMaxWidthOfChildMaxWidth + mRightDecorationMaxWidthOfChildMaxWidth; } /** * ??? * * @param width * @return */ protected int getChildMaxWidth(int width) { return width; } @Override public int computeVerticalScrollRange(RecyclerView.State state) { if (getOrientation() == VERTICAL) { return super.computeVerticalScrollRange(state); } return computeVerticalScrollRange(); } /** * * * @return */ protected int computeVerticalScrollRange() { if (getOrientation() == VERTICAL) { final RecyclerView view = getRecyclerView(); return view == null ? 0 : view.computeVerticalScrollRange(); } return getChildMaxHeight(mChildMaxHeight) + mTopDecorationMaxWidthOfChildMaxHeight + mBottomDecorationMaxWidthOfChildMaxHeight; } /** * ??? * * @param height * @return */ protected int getChildMaxHeight(int height) { return height; } /** * ??? * * @return ? */ public boolean canScrollAnotherDirection() { if (getOrientation() == HORIZONTAL) { return canScrollVertically(); } else { return canScrollHorizontally(); } } @Override public boolean canScrollHorizontally() { return super.canScrollHorizontally() || computeHorizontalScrollRange() > computeHorizontalScrollExtent(); } @Override public boolean canScrollVertically() { return super.canScrollVertically() || computeVerticalScrollRange() > computeVerticalScrollExtent(); } @Override public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) { if (getOrientation() == HORIZONTAL) { return super.scrollHorizontallyBy(dx, recycler, state); } return scrollBy(dx); } @Override public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { if (getOrientation() == VERTICAL) { return super.scrollVerticallyBy(dy, recycler, state); } return scrollBy(dy); } private int scrollBy(int distance) { if (distance == 0) return 0; final int max = computeAnotherDirectionMaxScrollOffset(); int offset = mOffset + distance; if (offset < 0) { offset = 0; } else if (offset > max) { offset = max; } final int move = mOffset - offset; if (move == 0) return 0; if (getOrientation() == HORIZONTAL) { offsetChildrenVertical(move); } else { offsetChildrenHorizontal(move); } mOffset = offset; mPercentage = ((float) mOffset) / max; return -move; } /** * ?? * * @param distance ? */ public void scrollAnotherDirectionBy(int distance) { scrollBy(distance); } /** * ????? * * @return */ public float getAnotherDirectionScrollOffsetPercentage() { return mPercentage == INVALID_PERCENTAGE ? 0 : mPercentage; } /** * ???? * * @param percentage */ public void setAnotherDirectionScrollOffsetPercentage(float percentage) { setAnotherDirectionScrollOffsetPercentage(percentage, true); } /** * ???? * * @param percentage * @param updateLayout */ public void setAnotherDirectionScrollOffsetPercentage(float percentage, boolean updateLayout) { if (mPendingPercentage == percentage || percentage < 0 || percentage > 1) return; mPendingPercentage = percentage; if (updateLayout) requestLayout(); } /** * ? * * @param position ? * @param offset ??? * @param percentage ???? */ public void scrollToPositionWithOffsetAndPercentage(int position, int offset, float percentage) { if (mPendingPercentage == percentage || percentage < 0 || percentage > 1) { scrollToPositionWithOffset(position, offset); return; } mPendingPercentage = percentage; scrollToPositionWithOffset(position, offset); } /** * ? * * @param width * @param height */ public void setChildMaxSize(int width, int height) { mChildMaxWidth = width; mChildMaxHeight = height; } /** * ?? * * @return */ public int getChildMaxWidth() { return mChildMaxWidth; } /** * ?? * * @return */ public int getChildMaxHeight() { return mChildMaxHeight; } /** * ?? * * @return */ public int getLeftDecorationMaxWidthOfChildMaxWidth() { return mLeftDecorationMaxWidthOfChildMaxWidth; } /** * ??? * * @return */ public int getRightDecorationMaxWidthOfChildMaxWidth() { return mRightDecorationMaxWidthOfChildMaxWidth; } /** * ?? * * @return */ public int getTopDecorationMaxWidthOfChildMaxHeight() { return mTopDecorationMaxWidthOfChildMaxHeight; } /** * ?? * * @return */ public int getBottomDecorationMaxWidthOfChildMaxHeight() { return mBottomDecorationMaxWidthOfChildMaxHeight; } /** * ??? * * @param left * @param right ? * @param top * @param bottom */ public void setDecorationMaxWidthOfChildWithMaxSize(int left, int right, int top, int bottom) { mLeftDecorationMaxWidthOfChildMaxWidth = left; mRightDecorationMaxWidthOfChildMaxWidth = right; mTopDecorationMaxWidthOfChildMaxHeight = top; mBottomDecorationMaxWidthOfChildMaxHeight = bottom; } protected static class SavedState extends AbsSavedState { public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() { @Override public SavedState createFromParcel(Parcel source, ClassLoader loader) { return new SavedState(source, loader); } @Override public SavedState createFromParcel(Parcel in) { return new SavedState(in, null); } @Override public SavedState[] newArray(int size) { return new SavedState[size]; } }; private final float mPercentage; private SavedState(Parcel in, ClassLoader loader) { super(in, loader); mPercentage = in.readFloat(); } private SavedState(Parcelable superState, float percentage) { super(superState); mPercentage = percentage; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeFloat(mPercentage); } float getPercentage() { return mPercentage; } } }