本文介绍如何通过继承RecyclerView.LayoutManager的方式,完成一个简单流式布局。
简介
RecyclerView自带了三种布局,即GridLayoutManager,StaggeredGridLayoutManager和LinearLayoutManager。LinearLayoutManager只能显示一行数据,GridLayoutManager和StaggeredGridLayoutManager能显示多行数据,但是列数(或者行数)是固定的,如果想要实现根据元素宽度(或者高度)自动换行(或者换列)的话,必需继承LayoutManager。
原理介绍
跟普通视图一样,首先在onMeasure函数中计算元素的宽度和是否需要换行,从而判断每个元素的坐标,然后把每个元素的坐标保存在数组中。最后在onLayoutChildren函数中设置每个元素的坐标。这样保证了最小次数的计算。
代码实现
public class FlowLayoutManager extends RecyclerView.LayoutManager { private final String TAG = this.getClass().getName(); private SparseArray<View> cachedViews = new SparseArray(); private SparseArray<Rect> layoutPoints = new SparseArray<>(); private int totalWidth; private int totalHeight; private int mContentHeight; private int mOffset; private boolean mIsFullyLayout; public FlowLayoutManager(Context context, boolean isFullyLayout) { mIsFullyLayout = isFullyLayout; } @Override public boolean supportsPredictiveItemAnimations() { return true; } @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { for (int i = 0; i < getItemCount(); ++i) { View v = cachedViews.get(i); Rect rect = layoutPoints.get(i); layoutDecorated(v, rect.left, rect.top, rect.right, rect.bottom); } } @Override public RecyclerView.LayoutParams generateDefaultLayoutParams() { return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT, RecyclerView.LayoutParams.WRAP_CONTENT); } @Override public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) { return dx; } @Override public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { int shouldOffset = 0; if (mContentHeight - totalHeight > 0) { int targetOffset = mOffset + dy; if (targetOffset < 0) { targetOffset = 0; } else if (targetOffset > (mContentHeight - totalHeight)) { targetOffset = (mContentHeight - totalHeight); } shouldOffset = targetOffset - mOffset; offsetChildrenVertical(-shouldOffset); mOffset = targetOffset; } if (mIsFullyLayout) { shouldOffset = dy; } return shouldOffset; } @Override public boolean canScrollHorizontally() { return true; } @Override public boolean canScrollVertically() { return true; } @Override public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) { removeAllViews(); } @Override public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { super.onMeasure(recycler, state, widthSpec, heightSpec); final int widthMode = View.MeasureSpec.getMode(widthSpec); final int heightMode = View.MeasureSpec.getMode(heightSpec); final int widthSize = View.MeasureSpec.getSize(widthSpec); final int heightSize = View.MeasureSpec.getSize(heightSpec); int height; switch (widthMode) { case View.MeasureSpec.UNSPECIFIED: Log.d(TAG, "WidthMode is unspecified."); break; case View.MeasureSpec.AT_MOST: break; case View.MeasureSpec.EXACTLY: break; } removeAndRecycleAllViews(recycler); recycler.clear(); cachedViews.clear(); mContentHeight = 0; totalWidth = widthSize - getPaddingRight() - getPaddingLeft(); int left = getPaddingLeft(); int top = getPaddingTop(); int maxTop = top; for (int i = 0; i < getItemCount(); ++i) { View v = recycler.getViewForPosition(i); addView(v); measureChildWithMargins(v, 0, 0); cachedViews.put(i, v); } for (int i = 0; i < getItemCount(); ++i) { View v = cachedViews.get(i); int w = getDecoratedMeasuredWidth(v); int h = getDecoratedMeasuredHeight(v); if (w > totalWidth - left) { left = getPaddingLeft(); top = maxTop; } Rect rect = new Rect(left, top, left + w, top + h); layoutPoints.put(i, rect); left = left + w; if (top + h >= maxTop) { maxTop = top + h; } } mContentHeight = maxTop - getPaddingTop(); height = mContentHeight + getPaddingTop() + getPaddingBottom(); switch (heightMode) { case View.MeasureSpec.EXACTLY: height = heightSize; break; case View.MeasureSpec.AT_MOST: if (height > heightSize) { height = heightSize; } break; case View.MeasureSpec.UNSPECIFIED: break; } totalHeight = height - getPaddingTop() - getPaddingBottom(); setMeasuredDimension(widthSize, height); } }
原文
totalHeight = height - getPaddingTop() - getPaddingBottom();
setMeasuredDimension(widthSize, height);
}
}