前言
在Android中有了ListView,GridView,为什么还需要RecyclerView这样的控件呢?从整体上看,RecyclerView架构提供了一种插拔式体验,它具有高度的解耦,异常的灵活性和更高的效率,它通过提供LayoutManager,ItemDecoration,ItemAnimator实现丰富多样的效果。
使用案例及步骤:
1.配置Build.Gradle
使用RecyclerView,我们必须导入support-v7包,在项目build.Gradle配置如下:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
......
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support:recyclerview-v7:27.1.1'
.....
} |
2.使用RecyclerView
recyclerviewList = (RecyclerView) findViewById(R.id.recyclerview_list); adapter = new RecyclerViewAdapter(this); listData = new ArrayList<>(); //水平显示 mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true); getData(); adapter.setListData(listData); recyclerviewList.setAdapter(adapter); recyclerviewList.setItemAnimator(new DefaultItemAnimator()); recyclerviewList.setLayoutManager(mLayoutManager); adapter.notifyDataSetChanged(); |
public void setLayoutManager(LayoutManager layout) { if (layout == mLayout) { return; } }
这里的layout参数我们可以不用系统默认的,我们可以自己这样的定义 new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true); public static final int HORIZONTAL = RecyclerView.HORIZONTAL; public static final int VERTICAL = RecyclerView.VERTICAL; |
在这里LinearLayoutManager源码提供两个方法,第一个默认指定垂直,第二个自己指定方向,代码如下:
1. /** * Creates a vertical LinearLayoutManager * * @param context Current context, will be used to access resources. */ public LinearLayoutManager(Context context) { this(context, RecyclerView.DEFAULT_ORIENTATION, false);//默认垂直 }2. /** * @param context Current context, will be used to access resources. * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link * #VERTICAL}. * @param reverseLayout When set to true, layouts from end to start. */ public LinearLayoutManager(Context context, @RecyclerView.Orientation int orientation, boolean reverseLayout) { setOrientation(orientation); setReverseLayout(reverseLayout); } |
上面我们已经说了RecyclerView两种排列方式,但是我们只写了垂直排列的代码,没有写如何设置水平排列
,但是思路已经说过了,那么接下来我们具体看一水平排列代码设置,如下:
//水平显示 LinearLayoutManager mLayoutManager = new LinearLayoutManager(this); mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); recyclerviewList.setLayoutManager(mLayoutManager); |
布局文件 activity_main2.xml如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".Main2Activity"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerview_list" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> |
另外,Adapter需要继承RecyclerView.Adapter,重写三个方法,分别是 onCreateViewHolder创建获取布局,
onBindViewHolder绑定布局,getItemCount获取数据大小size,具体代码如下:
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> { private LayoutInflater inflater; private List<String> listData; private Context mContext; public RecyclerViewAdapter(Context mContext) { this.mContext = mContext; inflater = LayoutInflater.from(mContext); } public void setListData(List<String> listData) { this.listData = listData; } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new ViewHolder(inflater.inflate(R.layout.activity_recyclerview_pattern, parent, false)); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { holder.mTextView.setText(listData.get(position)); } @Override public int getItemCount() { return listData.size() > 0 ? listData.size() : 0; } class ViewHolder extends RecyclerView.ViewHolder { private TextView mTextView; public ViewHolder(View view) { super(view); mTextView = view.findViewById(R.id.textView2); } } } |
3.设置分割线
我们通过使用recyclerviewList.addItemDecoration()方法来加入分割线,设置默认分割线,我们自己定义分割线,我们需要来继承RecyclerView.ItemDecoration实现自定义分割线,代码如下:
recyclerviewList.addItemDecoration(new DividerItemDecoration(this,LinearLayoutManager.VERTICAL)); 效果图: |
public class ItemDecorationDivider extends RecyclerView.ItemDecoration { private Paint mPaint; private Drawable mDivider; /** * 分割线高度,默认为1px */ private int mDividerHeight = 12; /** * 列表的方向:LinearLayoutManager.VERTICAL或LinearLayoutManager.HORIZONTAL */ /** * 分割线缩进值 */ private int inset; private int mOrientation; private static final int[] ATTRS = new int[]{android.R.attr.listDivider}; /** * 默认分割线:高度为2px,颜色为灰色 * * @param context * @param orientation 列表方向 */ public ItemDecorationDivider(Context context, int orientation) { if (orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL) { throw new IllegalArgumentException("请输入正确的参数!"); } mOrientation = orientation; final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); } /** * 自定义分割线 * * @param context * @param orientation 列表方向 * @param drawableId 分割线图片 */ public ItemDecorationDivider(Context context, int orientation, int drawableId) { this(context, orientation); mDivider = ContextCompat.getDrawable(context, drawableId); mDividerHeight = mDivider.getIntrinsicHeight(); } /** * 自定义分割线 * * @param context * @param orientation 列表方向 * @param dividerHeight 分割线高度 * @param dividerColor 分割线颜色 */ public ItemDecorationDivider(Context context, int orientation, int dividerHeight, int dividerColor) { this(context, orientation); mDividerHeight = dividerHeight; mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(dividerColor); mPaint.setStyle(Paint.Style.FILL); } /** * 获取分割线尺寸 * * @param outRect * @param view * @param parent * @param state */ @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); outRect.set(0, 0, 0, mDividerHeight); } /** * 绘制分割线 * * @param c * @param parent * @param state */ @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); if (mOrientation == LinearLayoutManager.VERTICAL) { drawVertical(c, parent); } else { drawHorizontal(c, parent); } } /** * 绘制横向 item 分割线 * * @param canvas * @param parent */ private void drawHorizontal(Canvas canvas, RecyclerView parent) { final int left = parent.getPaddingLeft(); final int right = parent.getMeasuredWidth() - parent.getPaddingRight(); final int childSize = parent.getChildCount(); for (int i = 0; i < childSize; i++) { final View child = parent.getChildAt(i); RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); final int top = child.getBottom() + layoutParams.bottomMargin; final int bottom = top + mDividerHeight; if (mDivider != null) { mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } if (mPaint != null) { canvas.drawRect(left, top, right, bottom, mPaint); } } } /** * 绘制纵向 item 分割线 * * @param canvas * @param parent */ private void drawVertical(Canvas canvas, RecyclerView parent) { final int top = parent.getPaddingTop(); final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom(); final int childSize = parent.getChildCount(); for (int i = 0; i < childSize; i++) { final View child = parent.getChildAt(i); RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); final int left = child.getRight() + layoutParams.rightMargin; final int right = left + mDividerHeight; if (mDivider != null) { mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } if (mPaint != null) { canvas.drawRect(left, top, right, bottom, mPaint); } } // private void drawVertical (Canvas c, RecyclerView parent){ // final int left = parent.getPaddingLeft(); // final int right = parent.getWidth() - parent.getPaddingRight(); // final int childCount = parent.getChildCount(); //最后一个item不画分割线 // for (int i = 0; i < childCount - 1; i++) { // final View child = parent.getChildAt(i); // final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); // final int top = child.getBottom() + params.bottomMargin; // final int bottom = top + mDivider.getIntrinsicHeight(); // if (inset > 0) { // c.drawRect(left, top, right, bottom, paint); // mDivider.setBounds(left + inset, top, right - inset, bottom); // } else { // mDivider.setBounds(left, top, right, bottom); // } // mDivider.draw(c); // } // } // private void drawHorizontal (Canvas c, RecyclerView parent){ // final int top = parent.getPaddingTop(); // final int bottom = parent.getHeight() - parent.getPaddingBottom(); // final int childCount = parent.getChildCount(); // for (int i = 0; i < childCount - 1; i++) { // final View child = parent.getChildAt(i); // final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); // final int left = child.getRight() + params.rightMargin; // final int right = left + mDivider.getIntrinsicHeight(); // mDivider.setBounds(left, top, right, bottom); // mDivider.draw(c); // } // } //由于Divider也有宽高,每一个Item需要向下或者向右偏移 // @Override // public void getItemOffsets (Rect outRect,int itemPosition, RecyclerView parent){ // if (mOrientation == VERTICAL_LIST) { // outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); // } else { // outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); // } // } } } |
这里核心的方法是onDraw,它根据传进来的orientation来绘制item是横向还是纵向,也就是drawHorizontal,drawVertial方法,这里注意的是我们一定要在setAdapter之前加入分割线。
recyclerviewList.addItemDecoration(new ItemDecorationDivider(this,LinearLayoutManager.HORIZONTAL,10, ContextCompat.getColor(this,R.color.colorAccent))); |
recyclerviewList.addItemDecoration(new ItemDecorationDivider(this,LinearLayoutManager.HORIZONTAL ,R.drawable.dividershape)); |
?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="#ff9000" /> <size android:height="5dp" /> </shape> |
效果图如下:
4.实现GridView
只需要自定义横向的分割线,然后在代码中设置:
staggeredGridLayoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL); recyclerviewList.addItemDecoration(new DividerGridItemDecoration(this,R.drawable.dividershape)); recyclerviewList.setLayoutManager(staggeredGridLayoutManager); DividerGridItemDecoration: public class DividerGridItemDecoration extends RecyclerView.ItemDecoration { private static final int[] ATTRS = new int[]{android.R.attr.listDivider}; private Drawable mDivider; private Paint mPaint; private int mDividerHeight = 2; public DividerGridItemDecoration(Context context) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); } /** * 自定义分割线 * @param context * @param drawableId 分割线图片 */ public DividerGridItemDecoration(Context context, int drawableId) { mDivider = ContextCompat.getDrawable(context, drawableId); mDividerHeight = mDivider.getIntrinsicHeight(); } /** * 自定义分割线 * @param context * @param dividerHeight 分割线高度 * @param dividerColor 分割线颜色 */ public DividerGridItemDecoration(Context context, int dividerHeight, int dividerColor) { mDividerHeight = dividerHeight; mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(context.getResources().getColor(dividerColor)); mPaint.setStyle(Paint.Style.FILL); } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { drawHorizontal(c, parent); drawVertical(c, parent); } private int getSpanCount(RecyclerView parent) { // 列数 int spanCount = -1; RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { spanCount = ((GridLayoutManager) layoutManager).getSpanCount(); } else if (layoutManager instanceof StaggeredGridLayoutManager) { spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount(); } return spanCount; } /** * 绘制水平线 * @param c * @param parent */ public void drawHorizontal(Canvas c, RecyclerView parent) { int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); final int left = child.getLeft() - params.leftMargin; final int right = child.getRight() + params.rightMargin + mDividerHeight; final int top = child.getBottom() + params.bottomMargin; final int bottom = top + mDividerHeight; if (mDivider != null) { mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } if (mPaint != null) { c.drawRect(left, top, right, bottom, mPaint); } } } /** * 绘制垂直线 * @param c * @param parent */ public void drawVertical(Canvas c, RecyclerView parent) { final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); final int top = child.getTop() - params.topMargin; final int bottom = child.getBottom() + params.bottomMargin; final int left = child.getRight() + params.rightMargin; final int right = left + mDividerHeight; if (mDivider != null) { mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } if (mPaint != null) { c.drawRect(left, top, right, bottom, mPaint); } } } /** * 判断是否是最后一列 * @param parent * @param pos * @param spanCount * @param childCount * @return */ private boolean isLastColum(RecyclerView parent, int pos, int spanCount, int childCount) { RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { if ((pos + 1) % spanCount == 0) // 如果是最后一列,则不需要绘制右边 { return true; } } else if (layoutManager instanceof StaggeredGridLayoutManager) { int orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation(); if (orientation == StaggeredGridLayoutManager.VERTICAL) { if ((pos + 1) % spanCount == 0) { // 如果是最后一列,则不需要绘制右边 return true; } } else { childCount = childCount - childCount % spanCount; return pos >= childCount; } } return false; } /** * 判断是否是最后一行 * @param parent * @param pos * @param spanCount * @param childCount * @return */ private boolean isLastRaw(RecyclerView parent, int pos, int spanCount, int childCount) { RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { childCount = childCount - childCount % spanCount; if (pos >= childCount) // 如果是最后一行,则不需要绘制底部 { return true; } } else if (layoutManager instanceof StaggeredGridLayoutManager) { int orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation(); // StaggeredGridLayoutManager 且纵向滚动 if (orientation == StaggeredGridLayoutManager.VERTICAL) { childCount = childCount - childCount % spanCount; // 如果是最后一行,则不需要绘制底部 if (pos >= childCount) { return true; } } else // StaggeredGridLayoutManager 且横向滚动 { // 如果是最后一行,则不需要绘制底部 if ((pos + 1) % spanCount == 0) { return true; } } } return false; } @Override public void getItemOffsets (Rect outRect,int itemPosition, RecyclerView parent){ int spanCount = getSpanCount(parent); int childCount = parent.getAdapter().getItemCount(); if (isLastRaw(parent, itemPosition, spanCount, childCount)) // 如果是最后一行,则不需要绘制底部 { outRect.set(0, 0, mDividerHeight, 0); } else if (isLastColum(parent, itemPosition, spanCount, childCount)) // 如果是最后一列,则不需要绘制右边 { outRect.set(0, 0, 0, mDividerHeight); } else { outRect.set(0, 0, mDividerHeight, mDividerHeight); } } } |
5.实现暴瀑流
实现暴瀑流在没有RecyclerView之前是比较困难的,代码还要写一大堆,现在第三方也是很多的,但是Google提供了RecyclerView控件能更容易的实现暴瀑流, 我们没有理由不去用它,因为它更稳定,效率高,自定义能力强。实现暴瀑流其实就是控制每个item的高度的高度就可以了。代码如下:
代码之前不做任何变化只是在如下代码增加设置textView的高度@Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { if (mHeights.size() <= position) { mHeights.add((int) (100 + Math.random() * 300)); } ViewGroup.LayoutParams lp = holder.mTextView.getLayoutParams(); lp.height = mHeights.get(position); holder.mTextView.setLayoutParams(lp); holder.mTextView.setText(listData.get(position)); } |
项目结构
具体代码:
1.Activitypublic class Main2Activity extends AppCompatActivity { private RecyclerView recyclerviewList; private RecyclerViewAdapter adapter; private List<String> listData; private LinearLayoutManager mLayoutManager; private StaggeredGridLayoutManager staggeredGridLayoutManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); super.setContentView(R.layout.activity_main2); initView(); } private void initView() { recyclerviewList = (RecyclerView) findViewById(R.id.recyclerview_list); adapter = new RecyclerViewAdapter(this); listData = new ArrayList<>(); //水平显示 // mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); staggeredGridLayoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL); getData(); adapter.setListData(listData); // recyclerviewList.addItemDecoration(new ItemDecorationDivider(this,LinearLayoutManager.HORIZONTAL,R.drawable.dividershape)); recyclerviewList.addItemDecoration(new DividerGridItemDecoration(this,R.drawable.dividershape)); recyclerviewList.setAdapter(adapter); recyclerviewList.setItemAnimator(new DefaultItemAnimator()); recyclerviewList.setLayoutManager(staggeredGridLayoutManager); adapter.notifyDataSetChanged(); } /** * 获取数据 */ private void getData() { for (int i = 0; i < 20; i++) { listData.add("测试" + i); } } } 2.activity_main2.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".Main2Activity"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerview_list" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>3.adapter public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> { private Context mContext; private LayoutInflater inflater; private List<String> listData; private List<Integer> mHeights; public RecyclerViewAdapter(Context mContext) { this.mContext = mContext; inflater = LayoutInflater.from(mContext); mHeights = new ArrayList<>(); } public void setListData(List<String> listData) { this.listData = listData; } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new ViewHolder(inflater.inflate(R.layout.activity_recyclerview_pattern, parent, false)); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { if (mHeights.size() <= position) { mHeights.add((int) (100 + Math.random() * 300)); } ViewGroup.LayoutParams lp = holder.mTextView.getLayoutParams(); lp.height = mHeights.get(position); holder.mTextView.setLayoutParams(lp); holder.mTextView.setText(listData.get(position)); } @Override public int getItemCount() { return listData.size() > 0 ? listData.size() : 0; } class ViewHolder extends RecyclerView.ViewHolder { private TextView mTextView; public ViewHolder(View view) { super(view); mTextView = view.findViewById(R.id.textView2); } } }4.activity_recyclerview_pattern.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" tools:context=".Main2Activity"> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="50dp" android:layout_weight="1" android:gravity="center" /> </LinearLayout> |
RecyclerView替换ListView和GridView及实现暴瀑流