RecyclerView可以替换ListView,GridView完成线性、网格、瀑布的显示,我们只需要根据需求设置对应的LayoutManager就可以了。
LinearLayoutManager
GridLayoutManager
StaggeredGridLayoutManager
RecyclerView基本方法使用步骤:
1、在布局中增加RecyclerView控件
mRecyclerView = findViewById(R.id.recyclerview);
2、设置适配器
MyRecyclerAdapter adapter = new MyRecyclerAdapter(getIntList(), width);
mRecyclerView.setAdapter(adapter);
3、设置动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
4、设置布局
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
5、设置分割线
mRecyclerView.addItemDecoration(new LinearItemDecoration(this, LinearLayoutManager.VERTICAL, false));
1、线性RecyclerView
先来看下采用LinearLayoutManager的代码
其中adapter、LinearItemDecoration均需要自定义,如下是这两个类的关键方法
MyRecyclerAdapter继承于RecyclerView.Adapter
public MyRecyclerAdapter(List<String> list, int screenWidth) {
super();
mList = list;
mScreenWidth = screenWidth;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_list, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
((MyRecyclerAdapter.MyViewHolder)holder).textView.setText((String)mList.get(position));
}
@Override
public int getItemCount() {
return mList.size();
}
在MyRecyclerAdapter中定义了内部类
class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textview);
textView.setOnClickListener(mViewOnClickListener);
}
}
该写法跟ListView很类似,只是要注意onCreateViewHolder用来创建ViewHolder,而onBindViewHolder用来完成数据绑定。
接下来完成分割线的绘制,我们只需要继承RecyclerView.ItemDecoration
后在指定的方法中绘制分割线就可以了,一般在onDraw方法中完成,而onDrawOver是在RecyclerView绘制完后会被调用,可以用来绘制悬浮标题。getItemOffsets方法的作用是获取子View偏移值,类似于padding值,在这里重写的目的是偏移一定的空间用来绘制分割线。具体实现如下:
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == LinearLayoutManager.VERTICAL) {
drawVertical(c, parent);
} else if (mOrientation == LinearLayoutManager.HORIZONTAL) {
drawHorizontal(c, parent);
}
}
private void drawVertical(Canvas c, RecyclerView parent) {
int left, right, top, buttom;
int childCount = parent.getChildCount();
left = parent.getPaddingLeft();
right = parent.getWidth() - left - parent.getPaddingRight();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
top = child.getBottom() + layoutParams.bottomMargin + Math.round(ViewCompat.getTranslationY(child));
buttom = top + mDrawable.getIntrinsicHeight();
mDrawable.setBounds(left, top, right, buttom);
mDrawable.draw(c);
}
}
private void drawHorizontal(Canvas c, RecyclerView parent) {
int left, right, top, buttom;
int childCount = parent.getChildCount();
top = parent.getPaddingTop();
buttom = parent.getHeight() - top - parent.getPaddingBottom();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
left = child.getRight() + layoutParams.rightMargin + Math.round(ViewCompat.getTranslationX(child));
right = left + mDrawable.getIntrinsicWidth();
mDrawable.setBounds(left, top, right, buttom);
mDrawable.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == LinearLayoutManager.VERTICAL) {
outRect.set(0, 0, 0, mDrawable.getIntrinsicHeight());
} else if (mOrientation == LinearLayoutManager.HORIZONTAL) {
outRect.set(0, 0, mDrawable.getIntrinsicWidth(), 0);
}
}
这样,基本的线性RecyclerView就完成了,效果图如下:
2、网格RecyclerView
接下来是网格列表,需要设置LayoutManager和ItemDecoration
mRecyclerView.setLayoutManager(new GridLayoutManager(this, 5));
mRecyclerView.addItemDecoration(new GridItemDecoration(this, 5));
这里的数字5代表的是一行有5个子View
来看下GridItemDecoration的实现,这里需要注意的是要画垂直和水平的分割线,思路是针对垂直线,只需要画子View右边的分割线,最右边的子View则不需要画分割线;针对水平线,只需要画子View下面的分割线。而getItemOffsets同样也是要考虑最右边和最底层时,不要进行偏移的。
public class GridItemDecoration extends RecyclerView.ItemDecoration {
private int attrs[] = new int[] {android.R.attr.listDivider};
private Context mContext;
private Drawable mDrawable;
private int mSpanCount;
public GridItemDecoration(Context context, int spanCount) {
mContext = context;
mSpanCount = spanCount;
TypedArray typedArray = context.obtainStyledAttributes(attrs);
mDrawable = typedArray.getDrawable(0);
typedArray.recycle();
}
@Override
public void onDraw(Canvas c, RecyclerView parent) {
drawVertical(c,parent);
drawHorizontal(c, parent);
}
private void drawVertical(Canvas c, RecyclerView parent) {// 竖线
int count = parent.getChildCount();
int left, top, right, buttom;
for (int i = 0; i < count; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)child.getLayoutParams();
left = child.getRight() + params.rightMargin;
right = left + mDrawable.getIntrinsicWidth();
top = child.getTop() - params.topMargin;
buttom = child.getBottom() + params.bottomMargin;
mDrawable.setBounds(left, top, right, buttom);
mDrawable.draw(c);
}
}
private void drawHorizontal(Canvas c, RecyclerView parent) {// 横线
int count = parent.getChildCount();
int left, top, right, buttom;
for (int i = 0; i < count; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)child.getLayoutParams();
left = child.getLeft() - params.leftMargin;
right = child.getRight() + params.rightMargin;
top = child.getBottom() + params.bottomMargin;
buttom = top + mDrawable.getIntrinsicHeight();
mDrawable.setBounds(left, top, right, buttom);
mDrawable.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int right = mDrawable.getIntrinsicWidth();
int buttom = mDrawable.getIntrinsicHeight();
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
int position = params.getViewAdapterPosition() + 1;
if (isLastColum(position, parent)) {
right = 0;
}
if (isLastRow(position, parent)) {
buttom = 0;
}
outRect.set(0, 0, right, buttom);
}
private boolean isLastRow(int position, RecyclerView parent) {
int count = parent.getAdapter().getItemCount();
int lastRowCount = count % mSpanCount;
int rowCount = lastRowCount == 0 ? count / mSpanCount : count / mSpanCount + 1;
if (position / mSpanCount == rowCount || (position % mSpanCount != 0
&& position / mSpanCount == rowCount -1)) {
return true;
}
return false;
}
private boolean isLastColum(int position, RecyclerView parent) {
return position % mSpanCount == 0;
}
}
效果图如下:
3、瀑布RecyclerView
最后是瀑布网格列表,同样需要设置需要设置LayoutManager和ItemDecoration
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(5, StaggeredGridLayoutManager.VERTICAL));
mRecyclerView.addItemDecoration(new StaggeredItemDecoration(this, 5));
这里为了体现瀑布流中各单元大小不一致,在MyRecyclerAdapter修改了子View高度:
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.textView.setText((String)mList.get(position));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mScreenWidth / 5, 150);
if (position % 2 != 0) {
params.height = 200;
holder.textView.setBackgroundColor(Color.GREEN);
} else {
holder.textView.setBackgroundColor(Color.YELLOW);
}
holder.textView.setLayoutParams(params);
}
然后到了画分割线的时候了,这里要注意的一点是,由于高度不一致,子View的布局并不会按照从上往下,从左往右的顺序排列,当上一行排列完成后,下以后的第一个View会优先选择从左往右的第一个高度较小的。换一句话说,之前的线性和网格布局使用的position,在这里无法继续使用了。不过思路还是一样的,先考虑偏移量,偏移量出来了,我们就用分割线去填充。设置一个space,子View左右两边偏移space,第二行开始,子View上面偏移2 * space,这里2倍的意思是两个View的中间距离实际是一个View的右偏移量加上第二个View的左偏移量。然后画垂直的分割线,这里要注意的是宽度是一定的即2 * space,高度则是子View 的top到buttom的距离,而水平分割线为了把对角的子View间的空间填充,必须将左右距离设置为从子View的left到right + 2 * space。代码如下:
public class StaggeredItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDrawable;
private Context mContext;
private static final int SPACE = 5;
private int mSpanCount;
private int attrs[] = new int[]{android.R.attr.listDivider};
public StaggeredItemDecoration(Context context, int spanCount) {
mContext = context;
mSpanCount = spanCount;
TypedArray typedArray = context.obtainStyledAttributes(attrs);
mDrawable = typedArray.getDrawable(0);
typedArray.recycle();
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildLayoutPosition(view);
int top = 0;
int left = SPACE;
int right = SPACE;
if (position >= mSpanCount) {
top = SPACE * 2;
}
outRect.set(left, top, right, 0);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
drawHorizontal(c, parent);
drawVertical(c, parent);
}
private void drawVertical(Canvas c, RecyclerView parent) {
int count = parent.getChildCount();
int left = 0, top = 0, right = 0, buttom = 0;
for (int i = 0; i < count; i++) {
View view = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
left = view.getRight();
right = left + SPACE * 2;
top = view.getTop();
buttom = view.getBottom();
mDrawable.setBounds(left, top, right, buttom);
mDrawable.draw(c);
}
}
private void drawHorizontal(Canvas c, RecyclerView parent) {
int count = parent.getChildCount();
int left = 0, top = 0, right = 0, buttom = 0;
for (int i = 0; i < count; i++) {
View view = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
top = view.getBottom();
buttom = top + SPACE * 2;
left = view.getLeft();
right = view.getRight() + SPACE * 2;
mDrawable.setBounds(left, top, right, buttom);
mDrawable.draw(c);
}
}
}
效果图如下: