13 | * The implementation will snap the center of the target child view to the center of
14 | * the attached {@link RecyclerView}.
15 | */
16 | public class CenterSnapHelper extends RecyclerView.OnFlingListener {
17 |
18 | RecyclerView mRecyclerView;
19 | Scroller mGravityScroller;
20 |
21 | /**
22 | * when the dataSet is extremely large
23 | * {@link #snapToCenterView(ViewPagerLayoutManager, ViewPagerLayoutManager.OnPageChangeListener)}
24 | * may keep calling itself because the accuracy of float
25 | */
26 | private boolean snapToCenter = false;
27 |
28 | // Handles the snap on scroll case.
29 | private final RecyclerView.OnScrollListener mScrollListener =
30 | new RecyclerView.OnScrollListener() {
31 |
32 | boolean mScrolled = false;
33 |
34 | @Override
35 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
36 | super.onScrollStateChanged(recyclerView, newState);
37 |
38 | final ViewPagerLayoutManager layoutManager =
39 | (ViewPagerLayoutManager) recyclerView.getLayoutManager();
40 | final ViewPagerLayoutManager.OnPageChangeListener onPageChangeListener =
41 | layoutManager.onPageChangeListener;
42 | if (onPageChangeListener != null) {
43 | onPageChangeListener.onPageScrollStateChanged(newState);
44 | }
45 |
46 | if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) {
47 | mScrolled = false;
48 | if (!snapToCenter) {
49 | snapToCenter = true;
50 | snapToCenterView(layoutManager, onPageChangeListener);
51 | } else {
52 | snapToCenter = false;
53 | }
54 | }
55 | }
56 |
57 | @Override
58 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
59 | if (dx != 0 || dy != 0) {
60 | mScrolled = true;
61 | }
62 | }
63 | };
64 |
65 | @Override
66 | public boolean onFling(int velocityX, int velocityY) {
67 | ViewPagerLayoutManager layoutManager = (ViewPagerLayoutManager) mRecyclerView.getLayoutManager();
68 | if (layoutManager == null) {
69 | return false;
70 | }
71 | RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
72 | if (adapter == null) {
73 | return false;
74 | }
75 |
76 | if (!layoutManager.getInfinite() &&
77 | (layoutManager.mOffset == layoutManager.getMaxOffset()
78 | || layoutManager.mOffset == layoutManager.getMinOffset())) {
79 | return false;
80 | }
81 |
82 | final int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
83 | mGravityScroller.fling(0, 0, velocityX, velocityY,
84 | Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
85 |
86 | if (layoutManager.mOrientation == ViewPagerLayoutManager.VERTICAL
87 | && Math.abs(velocityY) > minFlingVelocity) {
88 | final int currentPosition = layoutManager.getCurrentPositionOffset();
89 | final int offsetPosition = (int) (mGravityScroller.getFinalY() /
90 | layoutManager.mInterval / layoutManager.getDistanceRatio());
91 | ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ?
92 | -currentPosition - offsetPosition : currentPosition + offsetPosition);
93 | return true;
94 | } else if (layoutManager.mOrientation == ViewPagerLayoutManager.HORIZONTAL
95 | && Math.abs(velocityX) > minFlingVelocity) {
96 | final int currentPosition = layoutManager.getCurrentPositionOffset();
97 | final int offsetPosition = (int) (mGravityScroller.getFinalX() /
98 | layoutManager.mInterval / layoutManager.getDistanceRatio());
99 | ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ?
100 | -currentPosition - offsetPosition : currentPosition + offsetPosition);
101 | return true;
102 | }
103 |
104 | return true;
105 | }
106 |
107 | /**
108 | * Please attach after {{@link LayoutManager} is setting}
109 | * Attaches the {@link CenterSnapHelper} to the provided RecyclerView, by calling
110 | * {@link RecyclerView#setOnFlingListener(RecyclerView.OnFlingListener)}.
111 | * You can call this method with {@code null} to detach it from the current RecyclerView.
112 | *
113 | * @param recyclerView The RecyclerView instance to which you want to add this helper or
114 | * {@code null} if you want to remove CenterSnapHelper from the current
115 | * RecyclerView.
116 | * @throws IllegalArgumentException if there is already a {@link RecyclerView.OnFlingListener}
117 | * attached to the provided {@link RecyclerView}.
118 | */
119 | public void attachToRecyclerView(@Nullable RecyclerView recyclerView)
120 | throws IllegalStateException {
121 | if (mRecyclerView == recyclerView) {
122 | return; // nothing to do
123 | }
124 | if (mRecyclerView != null) {
125 | destroyCallbacks();
126 | }
127 | mRecyclerView = recyclerView;
128 | if (mRecyclerView != null) {
129 | final LayoutManager layoutManager = mRecyclerView.getLayoutManager();
130 | if (!(layoutManager instanceof ViewPagerLayoutManager)) return;
131 |
132 | setupCallbacks();
133 | mGravityScroller = new Scroller(mRecyclerView.getContext(),
134 | new DecelerateInterpolator());
135 |
136 | snapToCenterView((ViewPagerLayoutManager) layoutManager,
137 | ((ViewPagerLayoutManager) layoutManager).onPageChangeListener);
138 | }
139 | }
140 |
141 | void snapToCenterView(ViewPagerLayoutManager layoutManager,
142 | ViewPagerLayoutManager.OnPageChangeListener listener) {
143 | final int delta = layoutManager.getOffsetToCenter();
144 | if (delta != 0) {
145 | if (layoutManager.getOrientation()
146 | == ViewPagerLayoutManager.VERTICAL)
147 | mRecyclerView.smoothScrollBy(0, delta);
148 | else
149 | mRecyclerView.smoothScrollBy(delta, 0);
150 | } else {
151 | // set it false to make smoothScrollToPosition keep trigger the listener
152 | snapToCenter = false;
153 | }
154 |
155 | if (listener != null)
156 | listener.onPageSelected(layoutManager.getCurrentPosition());
157 | }
158 |
159 | /**
160 | * Called when an instance of a {@link RecyclerView} is attached.
161 | */
162 | void setupCallbacks() throws IllegalStateException {
163 | if (mRecyclerView.getOnFlingListener() != null) {
164 | throw new IllegalStateException("An instance of OnFlingListener already set.");
165 | }
166 | mRecyclerView.addOnScrollListener(mScrollListener);
167 | mRecyclerView.setOnFlingListener(this);
168 | }
169 |
170 | /**
171 | * Called when the instance of a {@link RecyclerView} is detached.
172 | */
173 | void destroyCallbacks() {
174 | mRecyclerView.removeOnScrollListener(mScrollListener);
175 | mRecyclerView.setOnFlingListener(null);
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/viewpagerlayoutmanager/src/main/java/com/leochuan/GalleryLayoutManager.java:
--------------------------------------------------------------------------------
1 | package com.leochuan;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 |
6 | /**
7 | * An implementation of {@link ViewPagerLayoutManager}
8 | * which will change rotate x or rotate y
9 | */
10 |
11 | @SuppressWarnings({"WeakerAccess", "unused"})
12 | public class GalleryLayoutManager extends ViewPagerLayoutManager {
13 | private final float MAX_ELEVATION = 5F;
14 |
15 | private int itemSpace;
16 | private float moveSpeed;
17 | private float maxAlpha;
18 | private float minAlpha;
19 | private float angle;
20 | private boolean flipRotate;
21 | private boolean rotateFromEdge;
22 |
23 | public GalleryLayoutManager(Context context, int itemSpace) {
24 | this(new Builder(context, itemSpace));
25 | }
26 |
27 | public GalleryLayoutManager(Context context, int itemSpace, int orientation) {
28 | this(new Builder(context, itemSpace).setOrientation(orientation));
29 | }
30 |
31 | public GalleryLayoutManager(Context context, int itemSpace, int orientation, boolean reverseLayout) {
32 | this(new Builder(context, itemSpace).setOrientation(orientation).setReverseLayout(reverseLayout));
33 | }
34 |
35 | public GalleryLayoutManager(Builder builder) {
36 | this(builder.context, builder.itemSpace, builder.angle, builder.maxAlpha, builder.minAlpha,
37 | builder.orientation, builder.moveSpeed, builder.flipRotate, builder.rotateFromEdge,
38 | builder.maxVisibleItemCount, builder.distanceToBottom, builder.reverseLayout);
39 | }
40 |
41 | private GalleryLayoutManager(Context context, int itemSpace, float angle, float maxAlpha, float minAlpha,
42 | int orientation, float moveSpeed, boolean flipRotate, boolean rotateFromEdge,
43 | int maxVisibleItemCount, int distanceToBottom, boolean reverseLayout) {
44 | super(context, orientation, reverseLayout);
45 | setDistanceToBottom(distanceToBottom);
46 | setMaxVisibleItemCount(maxVisibleItemCount);
47 | this.itemSpace = itemSpace;
48 | this.moveSpeed = moveSpeed;
49 | this.angle = angle;
50 | this.maxAlpha = maxAlpha;
51 | this.minAlpha = minAlpha;
52 | this.flipRotate = flipRotate;
53 | this.rotateFromEdge = rotateFromEdge;
54 | }
55 |
56 | public int getItemSpace() {
57 | return itemSpace;
58 | }
59 |
60 | public float getMaxAlpha() {
61 | return maxAlpha;
62 | }
63 |
64 | public float getMinAlpha() {
65 | return minAlpha;
66 | }
67 |
68 | public float getAngle() {
69 | return angle;
70 | }
71 |
72 | public float getMoveSpeed() {
73 | return moveSpeed;
74 | }
75 |
76 | public boolean getFlipRotate() {
77 | return flipRotate;
78 | }
79 |
80 | public boolean getRotateFromEdge() {
81 | return rotateFromEdge;
82 | }
83 |
84 | public void setItemSpace(int itemSpace) {
85 | assertNotInLayoutOrScroll(null);
86 | if (this.itemSpace == itemSpace) return;
87 | this.itemSpace = itemSpace;
88 | removeAllViews();
89 | }
90 |
91 | public void setMoveSpeed(float moveSpeed) {
92 | assertNotInLayoutOrScroll(null);
93 | if (this.moveSpeed == moveSpeed) return;
94 | this.moveSpeed = moveSpeed;
95 | }
96 |
97 | public void setMaxAlpha(float maxAlpha) {
98 | assertNotInLayoutOrScroll(null);
99 | if (maxAlpha > 1) maxAlpha = 1;
100 | if (this.maxAlpha == maxAlpha) return;
101 | this.maxAlpha = maxAlpha;
102 | requestLayout();
103 | }
104 |
105 | public void setMinAlpha(float minAlpha) {
106 | assertNotInLayoutOrScroll(null);
107 | if (minAlpha < 0) minAlpha = 0;
108 | if (this.minAlpha == minAlpha) return;
109 | this.minAlpha = minAlpha;
110 | requestLayout();
111 | }
112 |
113 | public void setAngle(float angle) {
114 | assertNotInLayoutOrScroll(null);
115 | if (this.angle == angle) return;
116 | this.angle = angle;
117 | requestLayout();
118 | }
119 |
120 | public void setFlipRotate(boolean flipRotate) {
121 | assertNotInLayoutOrScroll(null);
122 | if (this.flipRotate == flipRotate) return;
123 | this.flipRotate = flipRotate;
124 | requestLayout();
125 | }
126 |
127 | public void setRotateFromEdge(boolean rotateFromEdge) {
128 | assertNotInLayoutOrScroll(null);
129 | if (this.rotateFromEdge == rotateFromEdge) return;
130 | this.rotateFromEdge = rotateFromEdge;
131 | removeAllViews();
132 | }
133 |
134 | @Override
135 | protected float setInterval() {
136 | return mDecoratedMeasurement + itemSpace;
137 | }
138 |
139 | @Override
140 | protected void setItemViewProperty(View itemView, float targetOffset) {
141 | final float rotation = calRotation(targetOffset);
142 | if (getOrientation() == HORIZONTAL) {
143 | if (rotateFromEdge) {
144 | itemView.setPivotX(rotation > 0 ? 0 : mDecoratedMeasurement);
145 | itemView.setPivotY(mDecoratedMeasurementInOther * 0.5f);
146 | }
147 | if (flipRotate) {
148 | itemView.setRotationX(rotation);
149 | } else {
150 | itemView.setRotationY(rotation);
151 | }
152 | } else {
153 | if (rotateFromEdge) {
154 | itemView.setPivotY(rotation > 0 ? 0 : mDecoratedMeasurement);
155 | itemView.setPivotX(mDecoratedMeasurementInOther * 0.5f);
156 |
157 | }
158 | if (flipRotate) {
159 | itemView.setRotationY(-rotation);
160 | } else {
161 | itemView.setRotationX(-rotation);
162 | }
163 | }
164 | final float alpha = calAlpha(targetOffset);
165 | itemView.setAlpha(alpha);
166 | }
167 |
168 | @Override
169 | protected float setViewElevation(View itemView, float targetOffset) {
170 | final float ele = Math.max(Math.abs(itemView.getRotationX()), Math.abs(itemView.getRotationY())) * MAX_ELEVATION / 360;
171 | return MAX_ELEVATION - ele;
172 | }
173 |
174 | @Override
175 | protected float getDistanceRatio() {
176 | if (moveSpeed == 0) return Float.MAX_VALUE;
177 | return 1 / moveSpeed;
178 | }
179 |
180 | private float calRotation(float targetOffset) {
181 | return -angle / mInterval * targetOffset;
182 | }
183 |
184 | private float calAlpha(float targetOffset) {
185 | final float offset = Math.abs(targetOffset);
186 | float alpha = (minAlpha - maxAlpha) / mInterval * offset + maxAlpha;
187 | if (offset >= mInterval) alpha = minAlpha;
188 | return alpha;
189 | }
190 |
191 | public static class Builder {
192 | private static float INTERVAL_ANGLE = 30f;
193 | private static final float DEFAULT_SPEED = 1f;
194 | private static float MIN_ALPHA = 0.5f;
195 | private static float MAX_ALPHA = 1f;
196 |
197 | private int itemSpace;
198 | private float moveSpeed;
199 | private int orientation;
200 | private float maxAlpha;
201 | private float minAlpha;
202 | private float angle;
203 | private boolean flipRotate;
204 | private boolean reverseLayout;
205 | private Context context;
206 | private int maxVisibleItemCount;
207 | private int distanceToBottom;
208 | private boolean rotateFromEdge;
209 |
210 | public Builder(Context context, int itemSpace) {
211 | this.itemSpace = itemSpace;
212 | this.context = context;
213 | orientation = HORIZONTAL;
214 | angle = INTERVAL_ANGLE;
215 | maxAlpha = MAX_ALPHA;
216 | minAlpha = MIN_ALPHA;
217 | this.moveSpeed = DEFAULT_SPEED;
218 | reverseLayout = false;
219 | flipRotate = false;
220 | rotateFromEdge = false;
221 | distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE;
222 | maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN;
223 | }
224 |
225 | public Builder setItemSpace(int itemSpace) {
226 | this.itemSpace = itemSpace;
227 | return this;
228 | }
229 |
230 | public Builder setMoveSpeed(float moveSpeed) {
231 | this.moveSpeed = moveSpeed;
232 | return this;
233 | }
234 |
235 | public Builder setOrientation(int orientation) {
236 | this.orientation = orientation;
237 | return this;
238 | }
239 |
240 | public Builder setMaxAlpha(float maxAlpha) {
241 | if (maxAlpha > 1) maxAlpha = 1;
242 | this.maxAlpha = maxAlpha;
243 | return this;
244 | }
245 |
246 | public Builder setMinAlpha(float minAlpha) {
247 | if (minAlpha < 0) minAlpha = 0;
248 | this.minAlpha = minAlpha;
249 | return this;
250 | }
251 |
252 | public Builder setAngle(float angle) {
253 | this.angle = angle;
254 | return this;
255 | }
256 |
257 | public Builder setFlipRotate(boolean flipRotate) {
258 | this.flipRotate = flipRotate;
259 | return this;
260 | }
261 |
262 | public Builder setReverseLayout(boolean reverseLayout) {
263 | this.reverseLayout = reverseLayout;
264 | return this;
265 | }
266 |
267 | public Builder setMaxVisibleItemCount(int maxVisibleItemCount) {
268 | this.maxVisibleItemCount = maxVisibleItemCount;
269 | return this;
270 | }
271 |
272 | public Builder setDistanceToBottom(int distanceToBottom) {
273 | this.distanceToBottom = distanceToBottom;
274 | return this;
275 | }
276 |
277 | public Builder setRotateFromEdge(boolean rotateFromEdge) {
278 | this.rotateFromEdge = rotateFromEdge;
279 | return this;
280 | }
281 |
282 | public GalleryLayoutManager build() {
283 | return new GalleryLayoutManager(this);
284 | }
285 | }
286 | }
287 |
--------------------------------------------------------------------------------
/viewpagerlayoutmanager/src/main/java/com/leochuan/PageSnapHelper.java:
--------------------------------------------------------------------------------
1 | package com.leochuan;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 |
5 | /**
6 | * The implementation will snap the center of the target child view to the center of
7 | * the attached {@link RecyclerView}. And per Child per fling.
8 | */
9 |
10 | public class PageSnapHelper extends CenterSnapHelper {
11 |
12 | @Override
13 | public boolean onFling(int velocityX, int velocityY) {
14 | ViewPagerLayoutManager layoutManager = (ViewPagerLayoutManager) mRecyclerView.getLayoutManager();
15 | if (layoutManager == null) {
16 | return false;
17 | }
18 | RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
19 | if (adapter == null) {
20 | return false;
21 | }
22 |
23 | if (!layoutManager.getInfinite() &&
24 | (layoutManager.mOffset == layoutManager.getMaxOffset()
25 | || layoutManager.mOffset == layoutManager.getMinOffset())) {
26 | return false;
27 | }
28 |
29 | final int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
30 | mGravityScroller.fling(0, 0, velocityX, velocityY,
31 | Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
32 |
33 | if (layoutManager.mOrientation == ViewPagerLayoutManager.VERTICAL
34 | && Math.abs(velocityY) > minFlingVelocity) {
35 | final int currentPosition = layoutManager.getCurrentPositionOffset();
36 | final int offsetPosition = mGravityScroller.getFinalY() * layoutManager.getDistanceRatio() > layoutManager.mInterval ? 1 : 0;
37 | ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ?
38 | -currentPosition - offsetPosition : currentPosition + offsetPosition);
39 | return true;
40 | } else if (layoutManager.mOrientation == ViewPagerLayoutManager.HORIZONTAL
41 | && Math.abs(velocityX) > minFlingVelocity) {
42 | final int currentPosition = layoutManager.getCurrentPositionOffset();
43 | final int offsetPosition = mGravityScroller.getFinalX() * layoutManager.getDistanceRatio() > layoutManager.mInterval ? 1 : 0;
44 | ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ?
45 | -currentPosition - offsetPosition : currentPosition + offsetPosition);
46 | return true;
47 | }
48 |
49 | return true;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/viewpagerlayoutmanager/src/main/java/com/leochuan/RotateLayoutManager.java:
--------------------------------------------------------------------------------
1 | package com.leochuan;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 |
6 | /**
7 | * An implementation of {@link ViewPagerLayoutManager}
8 | * which rotates items
9 | */
10 |
11 | @SuppressWarnings({"WeakerAccess", "unused"})
12 | public class RotateLayoutManager extends ViewPagerLayoutManager {
13 |
14 | private int itemSpace;
15 | private float angle;
16 | private float moveSpeed;
17 | private boolean reverseRotate;
18 |
19 | public RotateLayoutManager(Context context, int itemSpace) {
20 | this(new Builder(context, itemSpace));
21 | }
22 |
23 | public RotateLayoutManager(Context context, int itemSpace, int orientation) {
24 | this(new Builder(context, itemSpace).setOrientation(orientation));
25 | }
26 |
27 | public RotateLayoutManager(Context context, int itemSpace, int orientation, boolean reverseLayout) {
28 | this(new Builder(context, itemSpace).setOrientation(orientation).setReverseLayout(reverseLayout));
29 | }
30 |
31 | public RotateLayoutManager(Builder builder) {
32 | this(builder.context, builder.itemSpace, builder.angle, builder.orientation, builder.moveSpeed,
33 | builder.reverseRotate, builder.maxVisibleItemCount, builder.distanceToBottom,
34 | builder.reverseLayout);
35 | }
36 |
37 | private RotateLayoutManager(Context context, int itemSpace, float angle, int orientation, float moveSpeed,
38 | boolean reverseRotate, int maxVisibleItemCount, int distanceToBottom,
39 | boolean reverseLayout) {
40 | super(context, orientation, reverseLayout);
41 | setDistanceToBottom(distanceToBottom);
42 | setMaxVisibleItemCount(maxVisibleItemCount);
43 | this.itemSpace = itemSpace;
44 | this.angle = angle;
45 | this.moveSpeed = moveSpeed;
46 | this.reverseRotate = reverseRotate;
47 | }
48 |
49 | public int getItemSpace() {
50 | return itemSpace;
51 | }
52 |
53 | public float getAngle() {
54 | return angle;
55 | }
56 |
57 | public float getMoveSpeed() {
58 | return moveSpeed;
59 | }
60 |
61 | public boolean getReverseRotate() {
62 | return reverseRotate;
63 | }
64 |
65 | public void setItemSpace(int itemSpace) {
66 | assertNotInLayoutOrScroll(null);
67 | if (this.itemSpace == itemSpace) return;
68 | this.itemSpace = itemSpace;
69 | removeAllViews();
70 | }
71 |
72 | public void setAngle(float centerScale) {
73 | assertNotInLayoutOrScroll(null);
74 | if (this.angle == centerScale) return;
75 | this.angle = centerScale;
76 | requestLayout();
77 | }
78 |
79 | public void setMoveSpeed(float moveSpeed) {
80 | assertNotInLayoutOrScroll(null);
81 | if (this.moveSpeed == moveSpeed) return;
82 | this.moveSpeed = moveSpeed;
83 | }
84 |
85 | public void setReverseRotate(boolean reverseRotate) {
86 | assertNotInLayoutOrScroll(null);
87 | if (this.reverseRotate == reverseRotate) return;
88 | this.reverseRotate = reverseRotate;
89 | requestLayout();
90 | }
91 |
92 | @Override
93 | protected float setInterval() {
94 | return mDecoratedMeasurement + itemSpace;
95 | }
96 |
97 | @Override
98 | protected void setItemViewProperty(View itemView, float targetOffset) {
99 | itemView.setRotation(calRotation(targetOffset));
100 | }
101 |
102 | @Override
103 | protected float getDistanceRatio() {
104 | if (moveSpeed == 0) return Float.MAX_VALUE;
105 | return 1 / moveSpeed;
106 | }
107 |
108 | private float calRotation(float targetOffset) {
109 | final float realAngle = reverseRotate ? angle : -angle;
110 | return realAngle / mInterval * targetOffset;
111 | }
112 |
113 | public static class Builder {
114 | private static float INTERVAL_ANGLE = 360f;
115 | private static final float DEFAULT_SPEED = 1f;
116 |
117 | private int itemSpace;
118 | private int orientation;
119 | private float angle;
120 | private float moveSpeed;
121 | private boolean reverseRotate;
122 | private boolean reverseLayout;
123 | private Context context;
124 | private int maxVisibleItemCount;
125 | private int distanceToBottom;
126 |
127 | public Builder(Context context, int itemSpace) {
128 | this.context = context;
129 | this.itemSpace = itemSpace;
130 | orientation = HORIZONTAL;
131 | angle = INTERVAL_ANGLE;
132 | this.moveSpeed = DEFAULT_SPEED;
133 | reverseRotate = false;
134 | reverseLayout = false;
135 | distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE;
136 | maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN;
137 | }
138 |
139 | public Builder setOrientation(int orientation) {
140 | this.orientation = orientation;
141 | return this;
142 | }
143 |
144 | public Builder setAngle(float angle) {
145 | this.angle = angle;
146 | return this;
147 | }
148 |
149 | public Builder setReverseLayout(boolean reverseLayout) {
150 | this.reverseLayout = reverseLayout;
151 | return this;
152 | }
153 |
154 | public Builder setMoveSpeed(float moveSpeed) {
155 | this.moveSpeed = moveSpeed;
156 | return this;
157 | }
158 |
159 | public Builder setReverseRotate(boolean reverseRotate) {
160 | this.reverseRotate = reverseRotate;
161 | return this;
162 | }
163 |
164 | public Builder setMaxVisibleItemCount(int maxVisibleItemCount) {
165 | this.maxVisibleItemCount = maxVisibleItemCount;
166 | return this;
167 | }
168 |
169 | public Builder setDistanceToBottom(int distanceToBottom) {
170 | this.distanceToBottom = distanceToBottom;
171 | return this;
172 | }
173 |
174 | public RotateLayoutManager build() {
175 | return new RotateLayoutManager(this);
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/viewpagerlayoutmanager/src/main/java/com/leochuan/ScaleLayoutManager.java:
--------------------------------------------------------------------------------
1 | package com.leochuan;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 |
6 | /**
7 | * An implementation of {@link ViewPagerLayoutManager}
8 | * which zooms the center item
9 | */
10 |
11 | @SuppressWarnings({"WeakerAccess", "unused"})
12 | public class ScaleLayoutManager extends ViewPagerLayoutManager {
13 |
14 | private int itemSpace;
15 | private float minScale;
16 | private float moveSpeed;
17 | private float maxAlpha;
18 | private float minAlpha;
19 |
20 | public ScaleLayoutManager(Context context, int itemSpace) {
21 | this(new Builder(context, itemSpace));
22 | }
23 |
24 | public ScaleLayoutManager(Context context, int itemSpace, int orientation) {
25 | this(new Builder(context, itemSpace).setOrientation(orientation));
26 | }
27 |
28 | public ScaleLayoutManager(Context context, int itemSpace, int orientation, boolean reverseLayout) {
29 | this(new Builder(context, itemSpace).setOrientation(orientation).setReverseLayout(reverseLayout));
30 | }
31 |
32 | public ScaleLayoutManager(Builder builder) {
33 | this(builder.context, builder.itemSpace, builder.minScale, builder.maxAlpha, builder.minAlpha,
34 | builder.orientation, builder.moveSpeed, builder.maxVisibleItemCount, builder.distanceToBottom,
35 | builder.reverseLayout);
36 | }
37 |
38 | private ScaleLayoutManager(Context context, int itemSpace, float minScale, float maxAlpha, float minAlpha,
39 | int orientation, float moveSpeed, int maxVisibleItemCount, int distanceToBottom,
40 | boolean reverseLayout) {
41 | super(context, orientation, reverseLayout);
42 | setDistanceToBottom(distanceToBottom);
43 | setMaxVisibleItemCount(maxVisibleItemCount);
44 | this.itemSpace = itemSpace;
45 | this.minScale = minScale;
46 | this.moveSpeed = moveSpeed;
47 | this.maxAlpha = maxAlpha;
48 | this.minAlpha = minAlpha;
49 | }
50 |
51 | public int getItemSpace() {
52 | return itemSpace;
53 | }
54 |
55 | public float getMinScale() {
56 | return minScale;
57 | }
58 |
59 | public float getMoveSpeed() {
60 | return moveSpeed;
61 | }
62 |
63 | public float getMaxAlpha() {
64 | return maxAlpha;
65 | }
66 |
67 | public float getMinAlpha() {
68 | return minAlpha;
69 | }
70 |
71 | public void setItemSpace(int itemSpace) {
72 | assertNotInLayoutOrScroll(null);
73 | if (this.itemSpace == itemSpace) return;
74 | this.itemSpace = itemSpace;
75 | removeAllViews();
76 | }
77 |
78 | public void setMinScale(float minScale) {
79 | assertNotInLayoutOrScroll(null);
80 | if (this.minScale == minScale) return;
81 | this.minScale = minScale;
82 | removeAllViews();
83 | }
84 |
85 | public void setMaxAlpha(float maxAlpha) {
86 | assertNotInLayoutOrScroll(null);
87 | if (maxAlpha > 1) maxAlpha = 1;
88 | if (this.maxAlpha == maxAlpha) return;
89 | this.maxAlpha = maxAlpha;
90 | requestLayout();
91 | }
92 |
93 | public void setMinAlpha(float minAlpha) {
94 | assertNotInLayoutOrScroll(null);
95 | if (minAlpha < 0) minAlpha = 0;
96 | if (this.minAlpha == minAlpha) return;
97 | this.minAlpha = minAlpha;
98 | requestLayout();
99 | }
100 |
101 | public void setMoveSpeed(float moveSpeed) {
102 | assertNotInLayoutOrScroll(null);
103 | if (this.moveSpeed == moveSpeed) return;
104 | this.moveSpeed = moveSpeed;
105 | }
106 |
107 | @Override
108 | protected float setInterval() {
109 | return itemSpace + mDecoratedMeasurement;
110 | }
111 |
112 | @Override
113 | protected void setItemViewProperty(View itemView, float targetOffset) {
114 | float scale = calculateScale(targetOffset + mSpaceMain);
115 | itemView.setScaleX(scale);
116 | itemView.setScaleY(scale);
117 | final float alpha = calAlpha(targetOffset);
118 | itemView.setAlpha(alpha);
119 | }
120 |
121 | private float calAlpha(float targetOffset) {
122 | final float offset = Math.abs(targetOffset);
123 | float alpha = (minAlpha - maxAlpha) / mInterval * offset + maxAlpha;
124 | if (offset >= mInterval) alpha = minAlpha;
125 | return alpha;
126 | }
127 |
128 | @Override
129 | protected float getDistanceRatio() {
130 | if (moveSpeed == 0) return Float.MAX_VALUE;
131 | return 1 / moveSpeed;
132 | }
133 |
134 | /**
135 | * @param x start positon of the view you want scale
136 | * @return the scale rate of current scroll mOffset
137 | */
138 | private float calculateScale(float x) {
139 | float deltaX = Math.abs(x - mSpaceMain);
140 | if (deltaX - mDecoratedMeasurement > 0) deltaX = mDecoratedMeasurement;
141 | return 1f - deltaX / mDecoratedMeasurement * (1f - minScale);
142 | }
143 |
144 | public static class Builder {
145 | private static final float SCALE_RATE = 0.8f;
146 | private static final float DEFAULT_SPEED = 1f;
147 | private static float MIN_ALPHA = 1f;
148 | private static float MAX_ALPHA = 1f;
149 |
150 | private int itemSpace;
151 | private int orientation;
152 | private float minScale;
153 | private float moveSpeed;
154 | private float maxAlpha;
155 | private float minAlpha;
156 | private boolean reverseLayout;
157 | private Context context;
158 | private int maxVisibleItemCount;
159 | private int distanceToBottom;
160 |
161 | public Builder(Context context, int itemSpace) {
162 | this.itemSpace = itemSpace;
163 | this.context = context;
164 | orientation = HORIZONTAL;
165 | minScale = SCALE_RATE;
166 | this.moveSpeed = DEFAULT_SPEED;
167 | maxAlpha = MAX_ALPHA;
168 | minAlpha = MIN_ALPHA;
169 | reverseLayout = false;
170 | distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE;
171 | maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN;
172 | }
173 |
174 | public Builder setOrientation(int orientation) {
175 | this.orientation = orientation;
176 | return this;
177 | }
178 |
179 | public Builder setMinScale(float minScale) {
180 | this.minScale = minScale;
181 | return this;
182 | }
183 |
184 | public Builder setReverseLayout(boolean reverseLayout) {
185 | this.reverseLayout = reverseLayout;
186 | return this;
187 | }
188 |
189 | public Builder setMaxAlpha(float maxAlpha) {
190 | if (maxAlpha > 1) maxAlpha = 1;
191 | this.maxAlpha = maxAlpha;
192 | return this;
193 | }
194 |
195 | public Builder setMinAlpha(float minAlpha) {
196 | if (minAlpha < 0) minAlpha = 0;
197 | this.minAlpha = minAlpha;
198 | return this;
199 | }
200 |
201 | public Builder setMoveSpeed(float moveSpeed) {
202 | this.moveSpeed = moveSpeed;
203 | return this;
204 | }
205 |
206 | public Builder setMaxVisibleItemCount(int maxVisibleItemCount) {
207 | this.maxVisibleItemCount = maxVisibleItemCount;
208 | return this;
209 | }
210 |
211 | public Builder setDistanceToBottom(int distanceToBottom) {
212 | this.distanceToBottom = distanceToBottom;
213 | return this;
214 | }
215 |
216 | public ScaleLayoutManager build() {
217 | return new ScaleLayoutManager(this);
218 | }
219 | }
220 | }
221 |
222 |
--------------------------------------------------------------------------------
/viewpagerlayoutmanager/src/main/java/com/leochuan/ScrollHelper.java:
--------------------------------------------------------------------------------
1 | package com.leochuan;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.view.View;
5 |
6 | public class ScrollHelper {
7 | /* package */ static void smoothScrollToPosition(RecyclerView recyclerView, ViewPagerLayoutManager viewPagerLayoutManager, int targetPosition) {
8 | final int delta = viewPagerLayoutManager.getOffsetToPosition(targetPosition);
9 | if (viewPagerLayoutManager.getOrientation() == ViewPagerLayoutManager.VERTICAL) {
10 | recyclerView.smoothScrollBy(0, delta);
11 | } else {
12 | recyclerView.smoothScrollBy(delta, 0);
13 | }
14 | }
15 |
16 | public static void smoothScrollToTargetView(RecyclerView recyclerView, View targetView) {
17 | final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
18 | if (!(layoutManager instanceof ViewPagerLayoutManager)) return;
19 | final int targetPosition = ((ViewPagerLayoutManager) layoutManager).getLayoutPositionOfView(targetView);
20 | smoothScrollToPosition(recyclerView, (ViewPagerLayoutManager) layoutManager, targetPosition);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/viewpagerlayoutmanager/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |