mItemRects;//key 是View的position,保存View的bounds 和 显示标志,
23 |
24 | /* private class FlowItem {
25 | public Rect bounds;//View的边界
26 | public boolean isShow;//View 是否显示
27 |
28 | public FlowItem(Rect bounds, boolean isShow) {
29 | this.bounds = bounds;
30 | this.isShow = isShow;
31 | }
32 | }*/
33 |
34 | public FlowLayoutManager() {
35 | //setAutoMeasureEnabled(true);
36 | mItemRects = new SparseArray<>();
37 | }
38 |
39 | @Override
40 | public RecyclerView.LayoutParams generateDefaultLayoutParams() {
41 | return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
42 | }
43 |
44 | @Override
45 | public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
46 | if (getItemCount() == 0) {//没有Item,界面空着吧
47 | detachAndScrapAttachedViews(recycler);
48 | return;
49 | }
50 | if (getChildCount() == 0 && state.isPreLayout()) {//state.isPreLayout()是支持动画的
51 | return;
52 | }
53 | //onLayoutChildren方法在RecyclerView 初始化时 会执行两遍
54 | detachAndScrapAttachedViews(recycler);
55 |
56 | //初始化区域
57 | mVerticalOffset = 0;
58 | mFirstVisiPos = 0;
59 | mLastVisiPos = getItemCount();
60 |
61 | //初始化时调用 填充childView
62 | fill(recycler, state);
63 |
64 |
65 | }
66 |
67 | /**
68 | * 初始化时调用 填充childView
69 | *
70 | * @param recycler
71 | * @param state
72 | */
73 | private void fill(RecyclerView.Recycler recycler, RecyclerView.State state) {
74 | fill(recycler, state, 0);
75 | }
76 |
77 | /**
78 | * 填充childView的核心方法,应该先填充,再移动。
79 | * 在填充时,预先计算dy的在内,如果View越界,回收掉。
80 | * 一般情况是返回dy,如果出现View数量不足,则返回修正后的dy.
81 | *
82 | * @param recycler
83 | * @param state
84 | * @param dy RecyclerView给我们的位移量,+,显示底端, -,显示头部
85 | * @return 修正以后真正的dy(可能剩余空间不够移动那么多了 所以return <|dy|)
86 | */
87 | private int fill(RecyclerView.Recycler recycler, RecyclerView.State state, int dy) {
88 |
89 | int topOffset = getPaddingTop();
90 |
91 | //回收越界子View
92 | if (getChildCount() > 0) {//滑动时进来的
93 | for (int i = getChildCount() - 1; i >= 0; i--) {
94 | View child = getChildAt(i);
95 | if (dy > 0) {//需要回收当前屏幕,上越界的View
96 | if (getDecoratedBottom(child) - dy < topOffset) {
97 | removeAndRecycleView(child, recycler);
98 | mFirstVisiPos++;
99 | continue;
100 | }
101 | } else if (dy < 0) {//回收当前屏幕,下越界的View
102 | if (getDecoratedTop(child) - dy > getHeight() - getPaddingBottom()) {
103 | removeAndRecycleView(child, recycler);
104 | mLastVisiPos--;
105 | continue;
106 | }
107 | }
108 | }
109 | //detachAndScrapAttachedViews(recycler);
110 | }
111 |
112 | int leftOffset = getPaddingLeft();
113 | int lineMaxHeight = 0;
114 | //布局子View阶段
115 | if (dy >= 0) {
116 | int minPos = mFirstVisiPos;
117 | mLastVisiPos = getItemCount() - 1;
118 | if (getChildCount() > 0) {
119 | View lastView = getChildAt(getChildCount() - 1);
120 | minPos = getPosition(lastView) + 1;//从最后一个View+1开始吧
121 | topOffset = getDecoratedTop(lastView);
122 | leftOffset = getDecoratedRight(lastView);
123 | lineMaxHeight = Math.max(lineMaxHeight, getDecoratedMeasurementVertical(lastView));
124 | }
125 | //顺序addChildView
126 | for (int i = minPos; i <= mLastVisiPos; i++) {
127 | //找recycler要一个childItemView,我们不管它是从scrap里取,还是从RecyclerViewPool里取,亦或是onCreateViewHolder里拿。
128 | View child = recycler.getViewForPosition(i);
129 | addView(child);
130 | measureChildWithMargins(child, 0, 0);
131 | //计算宽度 包括margin
132 | if (leftOffset + getDecoratedMeasurementHorizontal(child) <= getHorizontalSpace()) {//当前行还排列的下
133 | layoutDecorated(child, leftOffset, topOffset, leftOffset + getDecoratedMeasurementHorizontal(child), topOffset + getDecoratedMeasurementVertical(child));
134 |
135 | //保存Rect供逆序layout用
136 | Rect rect = new Rect(leftOffset, topOffset + mVerticalOffset, leftOffset + getDecoratedMeasurementHorizontal(child), topOffset + getDecoratedMeasurementVertical(child) + mVerticalOffset);
137 | mItemRects.put(i, rect);
138 |
139 | //改变 left lineHeight
140 | leftOffset += getDecoratedMeasurementHorizontal(child);
141 | lineMaxHeight = Math.max(lineMaxHeight, getDecoratedMeasurementVertical(child));
142 | } else {//当前行排列不下
143 | //改变top left lineHeight
144 | leftOffset = getPaddingLeft();
145 | topOffset += lineMaxHeight;
146 | lineMaxHeight = 0;
147 |
148 | //新起一行的时候要判断一下边界
149 | if (topOffset - dy > getHeight() - getPaddingBottom()) {
150 | //越界了 就回收
151 | removeAndRecycleView(child, recycler);
152 | mLastVisiPos = i - 1;
153 | } else {
154 | layoutDecorated(child, leftOffset, topOffset, leftOffset + getDecoratedMeasurementHorizontal(child), topOffset + getDecoratedMeasurementVertical(child));
155 |
156 | //保存Rect供逆序layout用
157 | Rect rect = new Rect(leftOffset, topOffset + mVerticalOffset, leftOffset + getDecoratedMeasurementHorizontal(child), topOffset + getDecoratedMeasurementVertical(child) + mVerticalOffset);
158 | mItemRects.put(i, rect);
159 |
160 | //改变 left lineHeight
161 | leftOffset += getDecoratedMeasurementHorizontal(child);
162 | lineMaxHeight = Math.max(lineMaxHeight, getDecoratedMeasurementVertical(child));
163 | }
164 | }
165 | }
166 | //添加完后,判断是否已经没有更多的ItemView,并且此时屏幕仍有空白,则需要修正dy
167 | View lastChild = getChildAt(getChildCount() - 1);
168 | if (getPosition(lastChild) == getItemCount() - 1) {
169 | int gap = getHeight() - getPaddingBottom() - getDecoratedBottom(lastChild);
170 | if (gap > 0) {
171 | dy -= gap;
172 | }
173 |
174 | }
175 |
176 | } else {
177 | /**
178 | * ## 利用Rect保存子View边界
179 | 正序排列时,保存每个子View的Rect,逆序时,直接拿出来layout。
180 | */
181 | int maxPos = getItemCount() - 1;
182 | mFirstVisiPos = 0;
183 | if (getChildCount() > 0) {
184 | View firstView = getChildAt(0);
185 | maxPos = getPosition(firstView) - 1;
186 | }
187 | for (int i = maxPos; i >= mFirstVisiPos; i--) {
188 | Rect rect = mItemRects.get(i);
189 |
190 | if (rect.bottom - mVerticalOffset - dy < getPaddingTop()) {
191 | mFirstVisiPos = i + 1;
192 | break;
193 | } else {
194 | View child = recycler.getViewForPosition(i);
195 | addView(child, 0);//将View添加至RecyclerView中,childIndex为1,但是View的位置还是由layout的位置决定
196 | measureChildWithMargins(child, 0, 0);
197 |
198 | layoutDecorated(child, rect.left, rect.top - mVerticalOffset, rect.right, rect.bottom - mVerticalOffset);
199 | }
200 | }
201 | }
202 |
203 |
204 | Log.d("TAG", "count= [" + getChildCount() + "]" + ",[recycler.getScrapList().size():" + recycler.getScrapList().size() + ", dy:" + dy + ", mVerticalOffset" + mVerticalOffset + ", ");
205 |
206 | return dy;
207 | }
208 |
209 | @Override
210 | public boolean canScrollVertically() {
211 | return true;
212 | }
213 |
214 | @Override
215 | public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
216 | //位移0、没有子View 当然不移动
217 | if (dy == 0 || getChildCount() == 0) {
218 | return 0;
219 | }
220 |
221 | int realOffset = dy;//实际滑动的距离, 可能会在边界处被修复
222 | //边界修复代码
223 | if (mVerticalOffset + realOffset < 0) {//上边界
224 | realOffset = -mVerticalOffset;
225 | } else if (realOffset > 0) {//下边界
226 | //利用最后一个子View比较修正
227 | View lastChild = getChildAt(getChildCount() - 1);
228 | if (getPosition(lastChild) == getItemCount() - 1) {
229 | int gap = getHeight() - getPaddingBottom() - getDecoratedBottom(lastChild);
230 | if (gap > 0) {
231 | realOffset = -gap;
232 | } else if (gap == 0) {
233 | realOffset = 0;
234 | } else {
235 | realOffset = Math.min(realOffset, -gap);
236 | }
237 | }
238 | }
239 |
240 | realOffset = fill(recycler, state, realOffset);//先填充,再位移。
241 |
242 | mVerticalOffset += realOffset;//累加实际滑动距离
243 |
244 | offsetChildrenVertical(-realOffset);//滑动
245 |
246 | return realOffset;
247 | }
248 |
249 | //模仿LLM Horizontal 源码
250 |
251 | /**
252 | * 获取某个childView在水平方向所占的空间
253 | *
254 | * @param view
255 | * @return
256 | */
257 | public int getDecoratedMeasurementHorizontal(View view) {
258 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
259 | view.getLayoutParams();
260 | return getDecoratedMeasuredWidth(view) + params.leftMargin
261 | + params.rightMargin;
262 | }
263 |
264 | /**
265 | * 获取某个childView在竖直方向所占的空间
266 | *
267 | * @param view
268 | * @return
269 | */
270 | public int getDecoratedMeasurementVertical(View view) {
271 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
272 | view.getLayoutParams();
273 | return getDecoratedMeasuredHeight(view) + params.topMargin
274 | + params.bottomMargin;
275 | }
276 |
277 | public int getVerticalSpace() {
278 | return getHeight() - getPaddingTop() - getPaddingBottom();
279 | }
280 |
281 | public int getHorizontalSpace() {
282 | return getWidth() - getPaddingLeft() - getPaddingRight();
283 | }
284 | }
285 |
--------------------------------------------------------------------------------
/layoutmanager/src/main/java/com/mcxtzhang/layoutmanager/swipecard/CardConfig.java:
--------------------------------------------------------------------------------
1 | package com.mcxtzhang.layoutmanager.swipecard;
2 |
3 | import android.content.Context;
4 | import android.util.TypedValue;
5 |
6 | /**
7 | * 介绍:一些配置
8 | * 界面最多显示几个View
9 | * 每一级View之间的Scale差异、translationY等等
10 | *
11 | *
12 | * 作者:zhangxutong
13 | * 邮箱:mcxtzhang@163.com
14 | * 主页:http://blog.csdn.net/zxt0601
15 | * 时间: 16/12/18.
16 | */
17 |
18 | public class CardConfig {
19 | //屏幕上最多同时显示几个Item
20 | public static int MAX_SHOW_COUNT;
21 |
22 | //每一级Scale相差0.05f,translationY相差7dp左右
23 | public static float SCALE_GAP;
24 |
25 | public static float ALPHA_GAP = 0.1f;
26 |
27 | public static int TRANS_Y_GAP;
28 |
29 | public static int TRANS_FLAG = 0;//默认从下往下透
30 |
31 | public static void initConfig(Context context) {
32 | MAX_SHOW_COUNT = 4;
33 | SCALE_GAP = 0.05f;
34 | ALPHA_GAP = 0.1f;
35 | TRANS_Y_GAP = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, context.getResources().getDisplayMetrics());
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/layoutmanager/src/main/java/com/mcxtzhang/layoutmanager/swipecard/OverLayCardLayoutManager.java:
--------------------------------------------------------------------------------
1 | package com.mcxtzhang.layoutmanager.swipecard;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.util.Log;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 |
8 | /**
9 | * 介绍:参考人人影视 最多排列四个
10 | * 重叠卡片布局
11 | * 作者:zhangxutong
12 | * 邮箱:mcxtzhang@163.com
13 | * 主页:http://blog.csdn.net/zxt0601
14 | * 时间: 16/12/15.
15 | */
16 |
17 | public class OverLayCardLayoutManager extends RecyclerView.LayoutManager {
18 | private static final String TAG = "swipecard";
19 |
20 | @Override
21 | public RecyclerView.LayoutParams generateDefaultLayoutParams() {
22 | return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
23 | }
24 |
25 | @Override
26 | public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
27 | Log.e(TAG, "onLayoutChildren() called with: recycler = [" + recycler + "], state = [" + state + "]");
28 | detachAndScrapAttachedViews(recycler);
29 | int itemCount = getItemCount();
30 | if (itemCount < 1) {
31 | return;
32 | }
33 | //top-3View的position
34 | int bottomPosition;
35 | //边界处理
36 | if (itemCount < CardConfig.MAX_SHOW_COUNT) {
37 | bottomPosition = 0;
38 | } else {
39 | bottomPosition = itemCount - CardConfig.MAX_SHOW_COUNT;
40 | }
41 |
42 | //从可见的最底层View开始layout,依次层叠上去
43 | for (int position = bottomPosition; position < itemCount; position++) {
44 | View view = recycler.getViewForPosition(position);
45 | addView(view);
46 | measureChildWithMargins(view, 0, 0);
47 | int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);
48 | int heightSpace = getHeight() - getDecoratedMeasuredHeight(view);
49 | //我们在布局时,将childView居中处理,这里也可以改为只水平居中
50 | layoutDecorated(view, widthSpace / 2, heightSpace / 2,
51 | widthSpace / 2 + getDecoratedMeasuredWidth(view),
52 | heightSpace / 2 + getDecoratedMeasuredHeight(view));
53 | /**
54 | * TopView的Scale 为1,translationY 0
55 | * 每一级Scale相差0.05f,translationY相差7dp左右
56 | *
57 | * 观察人人影视的UI,拖动时,topView被拖动,Scale不变,一直为1.
58 | * top-1View 的Scale慢慢变化至1,translation也慢慢恢复0
59 | * top-2View的Scale慢慢变化至 top-1View的Scale,translation 也慢慢变化只top-1View的translation
60 | * top-3View的Scale要变化,translation岿然不动
61 | */
62 |
63 | //第几层,举例子,count =7, 最后一个TopView(6)是第0层,
64 | int level = itemCount - position - 1;
65 | //除了顶层不需要缩小和位移
66 | if (level > 0 /*&& level < mShowCount - 1*/) {
67 | //每一层都需要X方向的缩小
68 | view.setScaleX(1 - CardConfig.SCALE_GAP * level);
69 | //前N层,依次向下位移和Y方向的缩小
70 | if (level < CardConfig.MAX_SHOW_COUNT - 1) {
71 | if (CardConfig.TRANS_FLAG == 0) {
72 | view.setTranslationY(-CardConfig.TRANS_Y_GAP * level);
73 | } else {
74 | view.setTranslationY(CardConfig.TRANS_Y_GAP * level);
75 | }
76 | view.setScaleY(1 - CardConfig.SCALE_GAP * level);
77 | view.setAlpha(1 - CardConfig.ALPHA_GAP * level);
78 | } else {//第N层在 向下位移和Y方向的缩小的成都与 N-1层保持一致
79 | if (CardConfig.TRANS_FLAG == 0) {
80 | view.setTranslationY(-CardConfig.TRANS_Y_GAP * (level - 1));
81 | } else {
82 | view.setTranslationY(CardConfig.TRANS_Y_GAP * (level - 1));
83 | }
84 | view.setScaleY(1 - CardConfig.SCALE_GAP * (level - 1));
85 | view.setAlpha(0);
86 | }
87 | }
88 | }
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/layoutmanager/src/main/java/com/mcxtzhang/layoutmanager/swipecard/RenRenCallback.java:
--------------------------------------------------------------------------------
1 | package com.mcxtzhang.layoutmanager.swipecard;
2 |
3 | import android.graphics.Canvas;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.support.v7.widget.helper.ItemTouchHelper;
6 | import android.util.Log;
7 | import android.view.View;
8 |
9 | import java.util.List;
10 |
11 | import static com.mcxtzhang.layoutmanager.swipecard.CardConfig.ALPHA_GAP;
12 | import static com.mcxtzhang.layoutmanager.swipecard.CardConfig.MAX_SHOW_COUNT;
13 | import static com.mcxtzhang.layoutmanager.swipecard.CardConfig.SCALE_GAP;
14 | import static com.mcxtzhang.layoutmanager.swipecard.CardConfig.TRANS_Y_GAP;
15 |
16 | /**
17 | * 介绍:人人影视效果的Callback
18 | * 作者:zhangxutong
19 | * 邮箱:mcxtzhang@163.com
20 | * 主页:http://blog.csdn.net/zxt0601
21 | * 时间: 16/12/18.
22 | */
23 |
24 | public class RenRenCallback extends ItemTouchHelper.SimpleCallback {
25 |
26 | protected RecyclerView mRv;
27 | protected List mDatas;
28 | protected RecyclerView.Adapter mAdapter;
29 |
30 | public RenRenCallback(RecyclerView rv, RecyclerView.Adapter adapter, List datas) {
31 | this(0,
32 | ItemTouchHelper.DOWN | ItemTouchHelper.UP | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT,
33 | rv, adapter, datas);
34 | }
35 |
36 | public RenRenCallback(int dragDirs, int swipeDirs
37 | , RecyclerView rv, RecyclerView.Adapter adapter, List datas) {
38 | super(dragDirs, swipeDirs);
39 | mRv = rv;
40 | mAdapter = adapter;
41 | mDatas = datas;
42 | }
43 |
44 | //水平方向是否可以被回收掉的阈值
45 | public float getThreshold(RecyclerView.ViewHolder viewHolder) {
46 | //2016 12 26 考虑 探探垂直上下方向滑动,不删除卡片,这里参照源码写死0.5f
47 | return mRv.getWidth() * /*getSwipeThreshold(viewHolder)*/ 0.5f;
48 | }
49 |
50 | @Override
51 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
52 | return false;
53 | }
54 |
55 | @Override
56 | public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
57 | //Log.e("swipecard", "onSwiped() called with: viewHolder = [" + viewHolder + "], direction = [" + direction + "]");
58 | //rollBack(viewHolder);
59 | //★实现循环的要点
60 | Object remove = mDatas.remove(viewHolder.getLayoutPosition());
61 | mDatas.add(0, remove);
62 | mAdapter.notifyDataSetChanged();
63 |
64 |
65 | }
66 |
67 | @Override
68 | public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
69 | super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
70 | //Log.e("swipecard", "onChildDraw() viewHolder = [" + viewHolder + "], dX = [" + dX + "], dY = [" + dY + "], actionState = [" + actionState + "], isCurrentlyActive = [" + isCurrentlyActive + "]");
71 | //人人影视的效果
72 | //if (isCurrentlyActive) {
73 | //先根据滑动的dxdy 算出现在动画的比例系数fraction
74 | double swipValue = Math.sqrt(dX * dX + dY * dY);
75 | double fraction = swipValue / getThreshold(viewHolder);
76 | //边界修正 最大为1
77 | if (fraction > 1) {
78 | fraction = 1;
79 | }
80 | //对每个ChildView进行缩放 位移
81 | int childCount = recyclerView.getChildCount();
82 | for (int i = 0; i < childCount; i++) {
83 | View child = recyclerView.getChildAt(i);
84 | //第几层,举例子,count =7, 最后一个TopView(6)是第0层,
85 | int level = childCount - i - 1;
86 | if (level > 0) {
87 | child.setScaleX((float) (1 - SCALE_GAP * level + fraction * SCALE_GAP));
88 |
89 | if (level < MAX_SHOW_COUNT - 1) {
90 | child.setScaleY((float) (1 - SCALE_GAP * level + fraction * SCALE_GAP));
91 | child.setAlpha((float) (1 - ALPHA_GAP * level + fraction * ALPHA_GAP));
92 | if (CardConfig.TRANS_FLAG == 0) {
93 | child.setTranslationY(-(float) (TRANS_Y_GAP * level - fraction * TRANS_Y_GAP));
94 | } else {
95 | child.setTranslationY((float) (TRANS_Y_GAP * level - fraction * TRANS_Y_GAP));
96 | }
97 | } else if (level < MAX_SHOW_COUNT) {
98 | child.setAlpha((float) (1 - ALPHA_GAP * (level + 1) + fraction * ALPHA_GAP));
99 | } else {
100 | //child.setTranslationY((float) (mTranslationYGap * (level - 1) - fraction * mTranslationYGap));
101 | }
102 | }
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':layoutmanager'
2 |
--------------------------------------------------------------------------------