本文介绍如何通过继承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);
}
}