+ * Make sure you call {@link #onLayoutComplete()} at the end of your LayoutManager's + * {@link RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler, + * RecyclerView.State)} method. + * + * @return The difference between the current total space and previous layout's total space. + * @see #onLayoutComplete() + */ + public int getTotalSpaceChange() { + return INVALID_SIZE == mLastTotalSpace ? 0 : getTotalSpace() - mLastTotalSpace; + } + + /** + * Returns the start of the view including its decoration and margin. + *
+ * For example, for the horizontal helper, if a View's left is at pixel 20, has 2px left + * decoration and 3px left margin, returned value will be 15px. + * + * @param view The view element to check + * @return The first pixel of the element + * @see #getDecoratedEnd(View) + */ + public abstract int getDecoratedStart(View view); + + /** + * Returns the end of the view including its decoration and margin. + *
+ * For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right + * decoration and 3px right margin, returned value will be 205. + * + * @param view The view element to check + * @return The last pixel of the element + * @see #getDecoratedStart(View) + */ + public abstract int getDecoratedEnd(View view); + + /** + * Returns the end of the View after its matrix transformations are applied to its layout + * position. + *
+ * This method is useful when trying to detect the visible edge of a View. + *
+ * It includes the decorations but does not include the margins. + * + * @param view The view whose transformed end will be returned + * @return The end of the View after its decor insets and transformation matrix is applied to + * its position + * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect) + */ + public abstract int getTransformedEndWithDecoration(View view); + + /** + * Returns the start of the View after its matrix transformations are applied to its layout + * position. + *
+ * This method is useful when trying to detect the visible edge of a View. + *
+ * It includes the decorations but does not include the margins.
+ *
+ * @param view The view whose transformed start will be returned
+ * @return The start of the View after its decor insets and transformation matrix is applied to
+ * its position
+ * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect)
+ */
+ public abstract int getTransformedStartWithDecoration(View view);
+
+ /**
+ * Returns the space occupied by this View in the current orientation including decorations and
+ * margins.
+ *
+ * @param view The view element to check
+ * @return Total space occupied by this view
+ * @see #getDecoratedMeasurementInOther(View)
+ */
+ public abstract int getDecoratedMeasurement(View view);
+
+ /**
+ * Returns the space occupied by this View in the perpendicular orientation including
+ * decorations and margins.
+ *
+ * @param view The view element to check
+ * @return Total space occupied by this view in the perpendicular orientation to current one
+ * @see #getDecoratedMeasurement(View)
+ */
+ public abstract int getDecoratedMeasurementInOther(View view);
+
+ /**
+ * Returns the start position of the layout after the start padding is added.
+ *
+ * @return The very first pixel we can draw.
+ */
+ public abstract int getStartAfterPadding();
+
+ /**
+ * Returns the end position of the layout after the end padding is removed.
+ *
+ * @return The end boundary for this layout.
+ */
+ public abstract int getEndAfterPadding();
+
+ /**
+ * Returns the end position of the layout without taking padding into account.
+ *
+ * @return The end boundary for this layout without considering padding.
+ */
+ public abstract int getEnd();
+
+ /**
+ * Returns the total space to layout. This number is the difference between
+ * {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}.
+ *
+ * @return Total space to layout children
+ */
+ public abstract int getTotalSpace();
+
+ /**
+ * Returns the total space in other direction to layout. This number is the difference between
+ * {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}.
+ *
+ * @return Total space to layout children
+ */
+ public abstract int getTotalSpaceInOther();
+
+ /**
+ * Returns the padding at the end of the layout. For horizontal helper, this is the right
+ * padding and for vertical helper, this is the bottom padding. This method does not check
+ * whether the layout is RTL or not.
+ *
+ * @return The padding at the end of the layout.
+ */
+ public abstract int getEndPadding();
+
+ /**
+ * Returns the MeasureSpec mode for the current orientation from the LayoutManager.
+ *
+ * @return The current measure spec mode.
+ * @see View.MeasureSpec
+ * @see RecyclerView.LayoutManager#getWidthMode()
+ * @see RecyclerView.LayoutManager#getHeightMode()
+ */
+ public abstract int getMode();
+
+ /**
+ * Returns the MeasureSpec mode for the perpendicular orientation from the LayoutManager.
+ *
+ * @return The current measure spec mode.
+ * @see View.MeasureSpec
+ * @see RecyclerView.LayoutManager#getWidthMode()
+ * @see RecyclerView.LayoutManager#getHeightMode()
+ */
+ public abstract int getModeInOther();
+
+ /**
+ * Creates an OrientationHelper for the given LayoutManager and orientation.
+ *
+ * @param layoutManager LayoutManager to attach to
+ * @param orientation Desired orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}
+ * @return A new OrientationHelper
+ */
+ public static OrientationHelper createOrientationHelper(
+ RecyclerView.LayoutManager layoutManager, int orientation) {
+ switch (orientation) {
+ case HORIZONTAL:
+ return createHorizontalHelper(layoutManager);
+ case VERTICAL:
+ return createVerticalHelper(layoutManager);
+ }
+ throw new IllegalArgumentException("invalid orientation");
+ }
+
+ /**
+ * Creates a horizontal OrientationHelper for the given LayoutManager.
+ *
+ * @param layoutManager The LayoutManager to attach to.
+ * @return A new OrientationHelper
+ */
+ public static OrientationHelper createHorizontalHelper(
+ RecyclerView.LayoutManager layoutManager) {
+ return new OrientationHelper(layoutManager) {
+ @Override
+ public int getEndAfterPadding() {
+ return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight();
+ }
+
+ @Override
+ public int getEnd() {
+ return mLayoutManager.getWidth();
+ }
+
+ @Override
+ public int getStartAfterPadding() {
+ return mLayoutManager.getPaddingLeft();
+ }
+
+ @Override
+ public int getDecoratedMeasurement(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
+ + params.rightMargin;
+ }
+
+ @Override
+ public int getDecoratedMeasurementInOther(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
+ + params.bottomMargin;
+ }
+
+ @Override
+ public int getDecoratedEnd(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedRight(view) + params.rightMargin;
+ }
+
+ @Override
+ public int getDecoratedStart(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedLeft(view) - params.leftMargin;
+ }
+
+ @Override
+ public int getTransformedEndWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.right;
+ }
+
+ @Override
+ public int getTransformedStartWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.left;
+ }
+
+ @Override
+ public int getTotalSpace() {
+ return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
+ - mLayoutManager.getPaddingRight();
+ }
+
+ @Override
+ public int getTotalSpaceInOther() {
+ return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
+ - mLayoutManager.getPaddingBottom();
+ }
+
+ @Override
+ public int getEndPadding() {
+ return mLayoutManager.getPaddingRight();
+ }
+
+ @Override
+ public int getMode() {
+ return mLayoutManager.getWidthMode();
+ }
+
+ @Override
+ public int getModeInOther() {
+ return mLayoutManager.getHeightMode();
+ }
+ };
+ }
+
+ /**
+ * Creates a vertical OrientationHelper for the given LayoutManager.
+ *
+ * @param layoutManager The LayoutManager to attach to.
+ * @return A new OrientationHelper
+ */
+ public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {
+ return new OrientationHelper(layoutManager) {
+ @Override
+ public int getEndAfterPadding() {
+ return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom();
+ }
+
+ @Override
+ public int getEnd() {
+ return mLayoutManager.getHeight();
+ }
+
+ @Override
+ public int getStartAfterPadding() {
+ return mLayoutManager.getPaddingTop();
+ }
+
+ @Override
+ public int getDecoratedMeasurement(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
+ + params.bottomMargin;
+ }
+
+ @Override
+ public int getDecoratedMeasurementInOther(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
+ + params.rightMargin;
+ }
+
+ @Override
+ public int getDecoratedEnd(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin;
+ }
+
+ @Override
+ public int getDecoratedStart(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedTop(view) - params.topMargin;
+ }
+
+ @Override
+ public int getTransformedEndWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.bottom;
+ }
+
+ @Override
+ public int getTransformedStartWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.top;
+ }
+
+ @Override
+ public int getTotalSpace() {
+ return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
+ - mLayoutManager.getPaddingBottom();
+ }
+
+ @Override
+ public int getTotalSpaceInOther() {
+ return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
+ - mLayoutManager.getPaddingRight();
+ }
+
+ @Override
+ public int getEndPadding() {
+ return mLayoutManager.getPaddingBottom();
+ }
+
+ @Override
+ public int getMode() {
+ return mLayoutManager.getHeightMode();
+ }
+
+ @Override
+ public int getModeInOther() {
+ return mLayoutManager.getWidthMode();
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/PageSnapHelper.java b/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/PageSnapHelper.java
new file mode 100644
index 0000000..6d9bdb2
--- /dev/null
+++ b/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/PageSnapHelper.java
@@ -0,0 +1,46 @@
+package com.common.commonlib.view.viewpagerlayoutmanager;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+public class PageSnapHelper extends CenterSnapHelper {
+
+ @Override
+ public boolean onFling(int velocityX, int velocityY) {
+ ViewPagerLayoutManager layoutManager = (ViewPagerLayoutManager) mRecyclerView.getLayoutManager();
+ if (layoutManager == null) {
+ return false;
+ }
+ RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
+ if (adapter == null) {
+ return false;
+ }
+
+ if (!layoutManager.getInfinite() &&
+ (layoutManager.mOffset == layoutManager.getMaxOffset()
+ || layoutManager.mOffset == layoutManager.getMinOffset())) {
+ return false;
+ }
+
+ final int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
+ mGravityScroller.fling(0, 0, velocityX, velocityY,
+ Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
+
+ if (layoutManager.mOrientation == ViewPagerLayoutManager.VERTICAL
+ && Math.abs(velocityY) > minFlingVelocity) {
+ final int currentPosition = layoutManager.getCurrentPositionOffset();
+ final int offsetPosition = mGravityScroller.getFinalY() * layoutManager.getDistanceRatio() > layoutManager.mInterval ? 1 : 0;
+ ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ?
+ -currentPosition - offsetPosition : currentPosition + offsetPosition);
+ return true;
+ } else if (layoutManager.mOrientation == ViewPagerLayoutManager.HORIZONTAL
+ && Math.abs(velocityX) > minFlingVelocity) {
+ final int currentPosition = layoutManager.getCurrentPositionOffset();
+ final int offsetPosition = mGravityScroller.getFinalX() * layoutManager.getDistanceRatio() > layoutManager.mInterval ? 1 : 0;
+ ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ?
+ -currentPosition - offsetPosition : currentPosition + offsetPosition);
+ return true;
+ }
+
+ return true;
+ }
+}
diff --git a/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/RotateLayoutManager.java b/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/RotateLayoutManager.java
new file mode 100644
index 0000000..b2860ee
--- /dev/null
+++ b/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/RotateLayoutManager.java
@@ -0,0 +1,178 @@
+package com.common.commonlib.view.viewpagerlayoutmanager;
+
+import android.content.Context;
+import android.view.View;
+
+/**
+ * An implementation of {@link ViewPagerLayoutManager}
+ * which rotates items
+ */
+
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class RotateLayoutManager extends ViewPagerLayoutManager {
+
+ private int itemSpace;
+ private float angle;
+ private float moveSpeed;
+ private boolean reverseRotate;
+
+ public RotateLayoutManager(Context context, int itemSpace) {
+ this(new Builder(context, itemSpace));
+ }
+
+ public RotateLayoutManager(Context context, int itemSpace, int orientation) {
+ this(new Builder(context, itemSpace).setOrientation(orientation));
+ }
+
+ public RotateLayoutManager(Context context, int itemSpace, int orientation, boolean reverseLayout) {
+ this(new Builder(context, itemSpace).setOrientation(orientation).setReverseLayout(reverseLayout));
+ }
+
+ public RotateLayoutManager(Builder builder) {
+ this(builder.context, builder.itemSpace, builder.angle, builder.orientation, builder.moveSpeed,
+ builder.reverseRotate, builder.maxVisibleItemCount, builder.distanceToBottom,
+ builder.reverseLayout);
+ }
+
+ private RotateLayoutManager(Context context, int itemSpace, float angle, int orientation, float moveSpeed,
+ boolean reverseRotate, int maxVisibleItemCount, int distanceToBottom,
+ boolean reverseLayout) {
+ super(context, orientation, reverseLayout);
+ setDistanceToBottom(distanceToBottom);
+ setMaxVisibleItemCount(maxVisibleItemCount);
+ this.itemSpace = itemSpace;
+ this.angle = angle;
+ this.moveSpeed = moveSpeed;
+ this.reverseRotate = reverseRotate;
+ }
+
+ public int getItemSpace() {
+ return itemSpace;
+ }
+
+ public float getAngle() {
+ return angle;
+ }
+
+ public float getMoveSpeed() {
+ return moveSpeed;
+ }
+
+ public boolean getReverseRotate() {
+ return reverseRotate;
+ }
+
+ public void setItemSpace(int itemSpace) {
+ assertNotInLayoutOrScroll(null);
+ if (this.itemSpace == itemSpace) return;
+ this.itemSpace = itemSpace;
+ removeAllViews();
+ }
+
+ public void setAngle(float centerScale) {
+ assertNotInLayoutOrScroll(null);
+ if (this.angle == centerScale) return;
+ this.angle = centerScale;
+ requestLayout();
+ }
+
+ public void setMoveSpeed(float moveSpeed) {
+ assertNotInLayoutOrScroll(null);
+ if (this.moveSpeed == moveSpeed) return;
+ this.moveSpeed = moveSpeed;
+ }
+
+ public void setReverseRotate(boolean reverseRotate) {
+ assertNotInLayoutOrScroll(null);
+ if (this.reverseRotate == reverseRotate) return;
+ this.reverseRotate = reverseRotate;
+ requestLayout();
+ }
+
+ @Override
+ protected float setInterval() {
+ return mDecoratedMeasurement + itemSpace;
+ }
+
+ @Override
+ protected void setItemViewProperty(View itemView, float targetOffset) {
+ itemView.setRotation(calRotation(targetOffset));
+ }
+
+ @Override
+ protected float getDistanceRatio() {
+ if (moveSpeed == 0) return Float.MAX_VALUE;
+ return 1 / moveSpeed;
+ }
+
+ private float calRotation(float targetOffset) {
+ final float realAngle = reverseRotate ? angle : -angle;
+ return realAngle / mInterval * targetOffset;
+ }
+
+ public static class Builder {
+ private static float INTERVAL_ANGLE = 360f;
+ private static final float DEFAULT_SPEED = 1f;
+
+ private int itemSpace;
+ private int orientation;
+ private float angle;
+ private float moveSpeed;
+ private boolean reverseRotate;
+ private boolean reverseLayout;
+ private Context context;
+ private int maxVisibleItemCount;
+ private int distanceToBottom;
+
+ public Builder(Context context, int itemSpace) {
+ this.context = context;
+ this.itemSpace = itemSpace;
+ orientation = HORIZONTAL;
+ angle = INTERVAL_ANGLE;
+ this.moveSpeed = DEFAULT_SPEED;
+ reverseRotate = false;
+ reverseLayout = false;
+ distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE;
+ maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN;
+ }
+
+ public Builder setOrientation(int orientation) {
+ this.orientation = orientation;
+ return this;
+ }
+
+ public Builder setAngle(float angle) {
+ this.angle = angle;
+ return this;
+ }
+
+ public Builder setReverseLayout(boolean reverseLayout) {
+ this.reverseLayout = reverseLayout;
+ return this;
+ }
+
+ public Builder setMoveSpeed(float moveSpeed) {
+ this.moveSpeed = moveSpeed;
+ return this;
+ }
+
+ public Builder setReverseRotate(boolean reverseRotate) {
+ this.reverseRotate = reverseRotate;
+ return this;
+ }
+
+ public Builder setMaxVisibleItemCount(int maxVisibleItemCount) {
+ this.maxVisibleItemCount = maxVisibleItemCount;
+ return this;
+ }
+
+ public Builder setDistanceToBottom(int distanceToBottom) {
+ this.distanceToBottom = distanceToBottom;
+ return this;
+ }
+
+ public RotateLayoutManager build() {
+ return new RotateLayoutManager(this);
+ }
+ }
+}
diff --git a/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ScaleLayoutManager.java b/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ScaleLayoutManager.java
new file mode 100644
index 0000000..9b9d418
--- /dev/null
+++ b/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ScaleLayoutManager.java
@@ -0,0 +1,221 @@
+package com.common.commonlib.view.viewpagerlayoutmanager;
+
+import android.content.Context;
+import android.view.View;
+
+/**
+ * An implementation of {@link ViewPagerLayoutManager}
+ * which zooms the center item
+ */
+
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class ScaleLayoutManager extends ViewPagerLayoutManager {
+
+ private int itemSpace;
+ private float minScale;
+ private float moveSpeed;
+ private float maxAlpha;
+ private float minAlpha;
+
+ public ScaleLayoutManager(Context context, int itemSpace) {
+ this(new Builder(context, itemSpace));
+ }
+
+ public ScaleLayoutManager(Context context, int itemSpace, int orientation) {
+ this(new Builder(context, itemSpace).setOrientation(orientation));
+ }
+
+ public ScaleLayoutManager(Context context, int itemSpace, int orientation, boolean reverseLayout) {
+ this(new Builder(context, itemSpace).setOrientation(orientation).setReverseLayout(reverseLayout));
+ }
+
+ public ScaleLayoutManager(Builder builder) {
+ this(builder.context, builder.itemSpace, builder.minScale, builder.maxAlpha, builder.minAlpha,
+ builder.orientation, builder.moveSpeed, builder.maxVisibleItemCount, builder.distanceToBottom,
+ builder.reverseLayout);
+ }
+
+ private ScaleLayoutManager(Context context, int itemSpace, float minScale, float maxAlpha, float minAlpha,
+ int orientation, float moveSpeed, int maxVisibleItemCount, int distanceToBottom,
+ boolean reverseLayout) {
+ super(context, orientation, reverseLayout);
+ setDistanceToBottom(distanceToBottom);
+ setMaxVisibleItemCount(maxVisibleItemCount);
+ this.itemSpace = itemSpace;
+ this.minScale = minScale;
+ this.moveSpeed = moveSpeed;
+ this.maxAlpha = maxAlpha;
+ this.minAlpha = minAlpha;
+ }
+
+ public int getItemSpace() {
+ return itemSpace;
+ }
+
+ public float getMinScale() {
+ return minScale;
+ }
+
+ public float getMoveSpeed() {
+ return moveSpeed;
+ }
+
+ public float getMaxAlpha() {
+ return maxAlpha;
+ }
+
+ public float getMinAlpha() {
+ return minAlpha;
+ }
+
+ public void setItemSpace(int itemSpace) {
+ assertNotInLayoutOrScroll(null);
+ if (this.itemSpace == itemSpace) return;
+ this.itemSpace = itemSpace;
+ removeAllViews();
+ }
+
+ public void setMinScale(float minScale) {
+ assertNotInLayoutOrScroll(null);
+ if (this.minScale == minScale) return;
+ this.minScale = minScale;
+ removeAllViews();
+ }
+
+ public void setMaxAlpha(float maxAlpha) {
+ assertNotInLayoutOrScroll(null);
+ if (maxAlpha > 1) maxAlpha = 1;
+ if (this.maxAlpha == maxAlpha) return;
+ this.maxAlpha = maxAlpha;
+ requestLayout();
+ }
+
+ public void setMinAlpha(float minAlpha) {
+ assertNotInLayoutOrScroll(null);
+ if (minAlpha < 0) minAlpha = 0;
+ if (this.minAlpha == minAlpha) return;
+ this.minAlpha = minAlpha;
+ requestLayout();
+ }
+
+ public void setMoveSpeed(float moveSpeed) {
+ assertNotInLayoutOrScroll(null);
+ if (this.moveSpeed == moveSpeed) return;
+ this.moveSpeed = moveSpeed;
+ }
+
+ @Override
+ protected float setInterval() {
+ return itemSpace + mDecoratedMeasurement;
+ }
+
+ @Override
+ protected void setItemViewProperty(View itemView, float targetOffset) {
+ float scale = calculateScale(targetOffset + mSpaceMain);
+ itemView.setScaleX(scale);
+ itemView.setScaleY(scale);
+ final float alpha = calAlpha(targetOffset);
+ itemView.setAlpha(alpha);
+ }
+
+ private float calAlpha(float targetOffset) {
+ final float offset = Math.abs(targetOffset);
+ float alpha = (minAlpha - maxAlpha) / mInterval * offset + maxAlpha;
+ if (offset >= mInterval) alpha = minAlpha;
+ return alpha;
+ }
+
+ @Override
+ protected float getDistanceRatio() {
+ if (moveSpeed == 0) return Float.MAX_VALUE;
+ return 1 / moveSpeed;
+ }
+
+ /**
+ * @param x start positon of the view you want scale
+ * @return the scale rate of current scroll mOffset
+ */
+ private float calculateScale(float x) {
+ float deltaX = Math.abs(x - mSpaceMain);
+ if (deltaX - mDecoratedMeasurement > 0) deltaX = mDecoratedMeasurement;
+ return 1f - deltaX / mDecoratedMeasurement * (1f - minScale);
+ }
+
+ public static class Builder {
+ private static final float SCALE_RATE = 0.8f;
+ private static final float DEFAULT_SPEED = 1f;
+ private static float MIN_ALPHA = 1f;
+ private static float MAX_ALPHA = 1f;
+
+ private int itemSpace;
+ private int orientation;
+ private float minScale;
+ private float moveSpeed;
+ private float maxAlpha;
+ private float minAlpha;
+ private boolean reverseLayout;
+ private Context context;
+ private int maxVisibleItemCount;
+ private int distanceToBottom;
+
+ public Builder(Context context, int itemSpace) {
+ this.itemSpace = itemSpace;
+ this.context = context;
+ orientation = HORIZONTAL;
+ minScale = SCALE_RATE;
+ this.moveSpeed = DEFAULT_SPEED;
+ maxAlpha = MAX_ALPHA;
+ minAlpha = MIN_ALPHA;
+ reverseLayout = false;
+ distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE;
+ maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN;
+ }
+
+ public Builder setOrientation(int orientation) {
+ this.orientation = orientation;
+ return this;
+ }
+
+ public Builder setMinScale(float minScale) {
+ this.minScale = minScale;
+ return this;
+ }
+
+ public Builder setReverseLayout(boolean reverseLayout) {
+ this.reverseLayout = reverseLayout;
+ return this;
+ }
+
+ public Builder setMaxAlpha(float maxAlpha) {
+ if (maxAlpha > 1) maxAlpha = 1;
+ this.maxAlpha = maxAlpha;
+ return this;
+ }
+
+ public Builder setMinAlpha(float minAlpha) {
+ if (minAlpha < 0) minAlpha = 0;
+ this.minAlpha = minAlpha;
+ return this;
+ }
+
+ public Builder setMoveSpeed(float moveSpeed) {
+ this.moveSpeed = moveSpeed;
+ return this;
+ }
+
+ public Builder setMaxVisibleItemCount(int maxVisibleItemCount) {
+ this.maxVisibleItemCount = maxVisibleItemCount;
+ return this;
+ }
+
+ public Builder setDistanceToBottom(int distanceToBottom) {
+ this.distanceToBottom = distanceToBottom;
+ return this;
+ }
+
+ public ScaleLayoutManager build() {
+ return new ScaleLayoutManager(this);
+ }
+ }
+}
+
diff --git a/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ScrollHelper.java b/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ScrollHelper.java
new file mode 100644
index 0000000..8839dec
--- /dev/null
+++ b/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ScrollHelper.java
@@ -0,0 +1,24 @@
+package com.common.commonlib.view.viewpagerlayoutmanager;
+
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+public class ScrollHelper {
+ /* package */
+ static void smoothScrollToPosition(RecyclerView recyclerView, ViewPagerLayoutManager viewPagerLayoutManager, int targetPosition) {
+ final int delta = viewPagerLayoutManager.getOffsetToPosition(targetPosition);
+ if (viewPagerLayoutManager.getOrientation() == RecyclerView.VERTICAL) {
+ recyclerView.smoothScrollBy(0, delta);
+ } else {
+ recyclerView.smoothScrollBy(delta, 0);
+ }
+ }
+
+ public static void smoothScrollToTargetView(RecyclerView recyclerView, View targetView) {
+ final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
+ if (!(layoutManager instanceof ViewPagerLayoutManager)) return;
+ final int targetPosition = ((ViewPagerLayoutManager) layoutManager).getLayoutPositionOfView(targetView);
+ smoothScrollToPosition(recyclerView, (ViewPagerLayoutManager) layoutManager, targetPosition);
+ }
+}
diff --git a/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ViewPagerLayoutManager.java b/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ViewPagerLayoutManager.java
new file mode 100644
index 0000000..a1b07a9
--- /dev/null
+++ b/commonLib/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ViewPagerLayoutManager.java
@@ -0,0 +1,936 @@
+package com.common.commonlib.view.viewpagerlayoutmanager;
+
+import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+
+@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue"})
+public abstract class ViewPagerLayoutManager extends LinearLayoutManager {
+
+ public static final int DETERMINE_BY_MAX_AND_MIN = -1;
+
+ public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
+
+ public static final int VERTICAL = OrientationHelper.VERTICAL;
+ protected static final int INVALID_SIZE = Integer.MAX_VALUE;
+ private static final int DIRECTION_NO_WHERE = -1;
+ private static final int DIRECTION_FORWARD = 0;
+ private static final int DIRECTION_BACKWARD = 1;
+ protected int mDecoratedMeasurement;
+ protected int mDecoratedMeasurementInOther;
+ protected int mSpaceMain;
+ protected int mSpaceInOther;
+ /**
+ * The offset of property which will change while scrolling
+ */
+ protected float mOffset;
+ /**
+ * Many calculations are made depending on orientation. To keep it clean, this interface
+ * helps {@link LinearLayoutManager} make those decisions.
+ * Based on {@link #mOrientation}, an implementation is lazily created in
+ * method.
+ */
+ protected OrientationHelper mOrientationHelper;
+ protected float mInterval; //the mInterval of each item's mOffset
+ /**
+ * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}
+ */
+ int mOrientation;
+ /* package */ OnPageChangeListener onPageChangeListener;
+ private SparseArray
+ * If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set
+ * this flag to
+ * Note that, setting this flag will result in a performance drop if RecyclerView
+ * is restored.
+ *
+ * @param recycleChildrenOnDetach Whether children should be recycled in detach or not.
+ */
+ public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
+ mRecycleChildrenOnDetach = recycleChildrenOnDetach;
+ }
+
+ @Override
+ public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
+ super.onDetachedFromWindow(view, recycler);
+ if (mRecycleChildrenOnDetach) {
+ removeAndRecycleAllViews(recycler);
+ recycler.clear();
+ }
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ if (mPendingSavedState != null) {
+ return new SavedState(mPendingSavedState);
+ }
+ SavedState savedState = new SavedState();
+ savedState.position = mPendingScrollPosition;
+ savedState.offset = mOffset;
+ savedState.isReverseLayout = mShouldReverseLayout;
+ return savedState;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ if (state instanceof SavedState) {
+ mPendingSavedState = new SavedState((SavedState) state);
+ requestLayout();
+ }
+ }
+
+ /**
+ * @return true if {@link #getOrientation()} is {@link #HORIZONTAL}
+ */
+ @Override
+ public boolean canScrollHorizontally() {
+ return mOrientation == HORIZONTAL;
+ }
+
+ /**
+ * @return true if {@link #getOrientation()} is {@link #VERTICAL}
+ */
+ @Override
+ public boolean canScrollVertically() {
+ return mOrientation == VERTICAL;
+ }
+
+ /**
+ * Returns the current orientation of the layout.
+ *
+ * @return Current orientation, either {@link #HORIZONTAL} or {@link #VERTICAL}
+ * @see #setOrientation(int)
+ */
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ /**
+ * Sets the orientation of the layout. {@link ViewPagerLayoutManager}
+ * will do its best to keep scroll position.
+ *
+ * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
+ */
+ public void setOrientation(int orientation) {
+ if (orientation != HORIZONTAL && orientation != VERTICAL) {
+ throw new IllegalArgumentException("invalid orientation:" + orientation);
+ }
+ assertNotInLayoutOrScroll(null);
+ if (orientation == mOrientation) {
+ return;
+ }
+ mOrientation = orientation;
+ mOrientationHelper = null;
+ mDistanceToBottom = INVALID_SIZE;
+ removeAllViews();
+ }
+
+ /**
+ * Returns the max visible item count, {@link #DETERMINE_BY_MAX_AND_MIN} means it haven't been set now
+ * And it will use {@link #maxRemoveOffset()} and {@link #minRemoveOffset()} to handle the range
+ *
+ * @return Max visible item count
+ */
+ public int getMaxVisibleItemCount() {
+ return mMaxVisibleItemCount;
+ }
+
+ /**
+ * Set the max visible item count, {@link #DETERMINE_BY_MAX_AND_MIN} means it haven't been set now
+ * And it will use {@link #maxRemoveOffset()} and {@link #minRemoveOffset()} to handle the range
+ *
+ * @param mMaxVisibleItemCount Max visible item count
+ */
+ public void setMaxVisibleItemCount(int mMaxVisibleItemCount) {
+ assertNotInLayoutOrScroll(null);
+ if (this.mMaxVisibleItemCount == mMaxVisibleItemCount) return;
+ this.mMaxVisibleItemCount = mMaxVisibleItemCount;
+ removeAllViews();
+ }
+
+ /**
+ * Calculates the view layout order. (e.g. from end to start or start to end)
+ * RTL layout support is applied automatically. So if layout is RTL and
+ * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
+ */
+ private void resolveShouldLayoutReverse() {
+ // A == B is the same result, but we rather keep it readable
+ if (mOrientation == VERTICAL || !isLayoutRTL()) {
+ mShouldReverseLayout = mReverseLayout;
+ } else {
+ mShouldReverseLayout = !mReverseLayout;
+ }
+ }
+
+ /**
+ * Returns if views are laid out from the opposite direction of the layout.
+ *
+ * @return If layout is reversed or not.
+ * @see #setReverseLayout(boolean)
+ */
+ public boolean getReverseLayout() {
+ return mReverseLayout;
+ }
+
+ public void setReverseLayout(boolean reverseLayout) {
+ assertNotInLayoutOrScroll(null);
+ if (reverseLayout == mReverseLayout) {
+ return;
+ }
+ mReverseLayout = reverseLayout;
+ removeAllViews();
+ }
+
+ public void setSmoothScrollInterpolator(Interpolator smoothScrollInterpolator) {
+ this.mSmoothScrollInterpolator = smoothScrollInterpolator;
+ }
+
+ @Override
+ public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
+ final int offsetPosition;
+
+ // fix wrong scroll direction when infinite enable
+ if (mInfinite) {
+ final int currentPosition = getCurrentPosition();
+ final int total = getItemCount();
+ final int targetPosition;
+ if (position < currentPosition) {
+ int d1 = currentPosition - position;
+ int d2 = total - currentPosition + position;
+ targetPosition = d1 < d2 ? (currentPosition - d1) : (currentPosition + d2);
+ } else {
+ int d1 = position - currentPosition;
+ int d2 = currentPosition + total - position;
+ targetPosition = d1 < d2 ? (currentPosition + d1) : (currentPosition - d2);
+ }
+
+ offsetPosition = getOffsetToPosition(targetPosition);
+ } else {
+ offsetPosition = getOffsetToPosition(position);
+ }
+
+ if (mOrientation == VERTICAL) {
+ recyclerView.smoothScrollBy(0, offsetPosition, mSmoothScrollInterpolator);
+ } else {
+ recyclerView.smoothScrollBy(offsetPosition, 0, mSmoothScrollInterpolator);
+ }
+ }
+
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ if (state.getItemCount() == 0) {
+ removeAndRecycleAllViews(recycler);
+ mOffset = 0;
+ return;
+ }
+
+ ensureLayoutState();
+ resolveShouldLayoutReverse();
+
+ //make sure properties are correct while measure more than once
+ View scrap = getMeasureView(recycler, state, 0);
+ if (scrap == null) {
+ removeAndRecycleAllViews(recycler);
+ mOffset = 0;
+ return;
+ }
+
+ measureChildWithMargins(scrap, 0, 0);
+ mDecoratedMeasurement = mOrientationHelper.getDecoratedMeasurement(scrap);
+ mDecoratedMeasurementInOther = mOrientationHelper.getDecoratedMeasurementInOther(scrap);
+ mSpaceMain = (mOrientationHelper.getTotalSpace() - mDecoratedMeasurement) / 2;
+ if (mDistanceToBottom == INVALID_SIZE) {
+ mSpaceInOther = (mOrientationHelper.getTotalSpaceInOther() - mDecoratedMeasurementInOther) / 2;
+ } else {
+ mSpaceInOther = mOrientationHelper.getTotalSpaceInOther() - mDecoratedMeasurementInOther - mDistanceToBottom;
+ }
+
+ mInterval = setInterval();
+ setUp();
+ if (mInterval == 0) {
+ mLeftItems = 1;
+ mRightItems = 1;
+ } else {
+ mLeftItems = (int) Math.abs(minRemoveOffset() / mInterval) + 1;
+ mRightItems = (int) Math.abs(maxRemoveOffset() / mInterval) + 1;
+ }
+
+ if (mPendingSavedState != null) {
+ mShouldReverseLayout = mPendingSavedState.isReverseLayout;
+ mPendingScrollPosition = mPendingSavedState.position;
+ mOffset = mPendingSavedState.offset;
+ }
+
+ if (mPendingScrollPosition != NO_POSITION) {
+ mOffset = mShouldReverseLayout ?
+ mPendingScrollPosition * -mInterval : mPendingScrollPosition * mInterval;
+ }
+
+ layoutItems(recycler);
+ }
+
+ private View getMeasureView(RecyclerView.Recycler recycler, RecyclerView.State state, int index) {
+ if (index >= state.getItemCount() || index < 0) return null;
+ try {
+ return recycler.getViewForPosition(index);
+ } catch (Exception e) {
+ return getMeasureView(recycler, state, index + 1);
+ }
+ }
+
+ @Override
+ public void onLayoutCompleted(RecyclerView.State state) {
+ super.onLayoutCompleted(state);
+ mPendingSavedState = null;
+ mPendingScrollPosition = NO_POSITION;
+ }
+
+ @Override
+ public boolean onAddFocusables(RecyclerView recyclerView, ArrayList
+ * When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based
+ * solely on the number of items in the adapter and the position of the visible items inside
+ * the adapter. This provides a stable scrollbar as the user navigates through a list of items
+ * with varying widths / heights.
+ *
+ * @param enabled Whether or not to enable smooth scrollbar.
+ * @see #setSmoothScrollbarEnabled(boolean)
+ */
+ public void setSmoothScrollbarEnabled(boolean enabled) {
+ mSmoothScrollbarEnabled = enabled;
+ }
+
+ public interface OnPageChangeListener {
+ void onPageSelected(int position);
+
+ void onPageScrollStateChanged(int state);
+ }
+
+ private static class SavedState implements Parcelable {
+ public static final Creatortrue
so that views will be available to other RecyclerViews
+ * immediately.
+ *