辛苦堆砌,转载请注明出处,谢谢!
Android中的RecyclerView类似于ListView,但是它使用更加灵活,可以实现更复杂的列表结构。使用RecyclerView需要了解几个概念。
首先是布局管理器,RecyclerView使用布局管理器控制子视图之间的布局关系,有两种布局管理器:(1)LinearLayoutManager:把子视图按照垂直或者水平的方式进行排列;(2)GridLayoutManager:把子视图按照垂直或者水平的方式放置在网格中,网格不要求必须是矩阵形式的,可以是每行的列数不同表格。其次是RecyclerView.Adapter,它用来将数据集的变化反映到视图中。我们必须派生Adapter类,实现数据更新逻辑。RecyclerView.Adapter使用ViewHolder模式,我们需要派生RecyclerView. ViewHolder,这样可以防止过多调用findViewById,提高效率。
最后是RecyclerView.ItemDecoration,它用来提供子视图之间填充的内容,如果不提供,默认和ListView类似,子视图之间紧密挨在一起,我们可以派生该类,实现自己的填充。该类使用装饰器模式,我们可以为RecyclerView添加和移除各种装饰,装饰可以相互叠加。
我们示例代码的结果如图所示,分别给出了垂直,网格和水平三种布局方式:

水平的不太好展示,可以自己运行程序,然后左右滑动看看。下面看看我们的代码,我们首先定义了两个尺寸,在实现decoration时会用到,在dimens.xml文件中
<dimen name="decoration_margin">10dp</dimen><dimen name="decoration_line_stroke">2dp</dimen>然后看看添加空隙的decorationpackage com.yjp.recyclerviewtest;import android.content.Context;import android.graphics.Rect;import android.support.v7.widget.RecyclerView;import android.view.View;public class MarginDecoration extends RecyclerView.ItemDecoration { PRivate int mDimen; public MarginDecoration(Context context) { super(); mDimen = context.getResources().getDimensionPixelOffset(R.dimen.decoration_margin); } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { outRect.set(mDimen, mDimen, mDimen, mDimen); }}MarginDecoration派生自RecyclerView.ItemDecoration,在构造函数中加载了dimens.xml中定义的decoration_margin,然后重写了getItemOffsets方法,该方法返回一个矩形,用来说明两个条目之间的间隙。上面示例图片中在网格布局中,一列两个条目之间用短横线连接,这是由另一个decoration实现的
package com.yjp.recyclerviewtest;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.support.v7.widget.RecyclerView;import android.view.View;public class LineDecoration extends RecyclerView.ItemDecoration { private Paint mLinePaint; private int mLineLength; public LineDecoration(Context context) { super(); mLineLength = context.getResources() .getDimensionPixelOffset(R.dimen.decoration_margin); int lineStroke = context.getResources() .getDimensionPixelOffset(R.dimen.decoration_line_stroke); mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mLinePaint.setColor(Color.RED); mLinePaint.setStyle(Paint.Style.STROKE); mLinePaint.setStrokeWidth(lineStroke); } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); for (int i = 0; i < parent.getChildCount(); i++) { View view = parent.getChildAt(i); boolean isLeft = parent.getChildAdapterPosition(view) % 3 == 1; if (isLeft) { final int childRight = layoutManager.getDecoratedRight(view); final int childTop = layoutManager.getDecoratedTop(view); final int childBottom = layoutManager.getDecoratedBottom(view); int y = childTop + (childBottom - childTop) / 2; c.drawLine(childRight - mLineLength, y, childRight + mLineLength, y, mLinePaint); } } }}该decoration重写了onDraw方法,通过position判断是否是两个一行的前一个条目,如果是的话,绘制短直线。现在我们就准备好了两个Decoration,后面会将其交给RecyclerView使用。现将他们放在这里,我们再来看看Adapter实现
package com.yjp.recyclerviewtest;import android.content.Context;import android.support.v7.widget.RecyclerView;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.TextView;public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ItemHolder> { private LayoutInflater mLayoutInflater; private int mItemsCount; private OnItemClickListener mOnItemClickListener; public interface OnItemClickListener { void onItemClicked(ItemHolder holder, int position); } public ItemAdapter(Context context) { mLayoutInflater = LayoutInflater.from(context); } @Override public ItemAdapter.ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = mLayoutInflater.inflate(R.layout.item_layout, parent, false); return new ItemHolder(itemView, this); } @Override public void onBindViewHolder(ItemAdapter.ItemHolder holder, int position) { holder.setContent("条目" + position); } @Override public int getItemCount() { return mItemsCount; } public void insertItem(int position) { mItemsCount++; notifyItemInserted(position); } public void removeItem(int position) { mItemsCount--; notifyItemRemoved(position); } public void setOnItemClickListener(OnItemClickListener listener) { mOnItemClickListener = listener; } public OnItemClickListener getOnItemClickListener() { return mOnItemClickListener; } public static class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener { private TextView mContentView; private ItemAdapter mItemAdapter; public ItemHolder(View itemView, ItemAdapter adapter) { super(itemView); mItemAdapter = adapter; itemView.setOnClickListener(this); mContentView = (TextView) itemView.findViewById(R.id.content); } public void setContent(String content) { mContentView.setText(content); } public String getContent() { return mContentView.getText().toString(); } @Override public void onClick(View v) { OnItemClickListener listener = mItemAdapter.getOnItemClickListener(); if (listener != null) { listener.onItemClicked(this, getAdapterPosition()); } } }}重写了三个主要的方法:public ItemAdapter.ItemHolder onCreateViewHolder(ViewGroup parent, int viewType)
在创建ViewHolder时调用,主要创建我们自定义的ViewHolder并在其构造函数调用findViewById,拿到对应视图的引用。
public void onBindViewHolder(ItemAdapter.ItemHolder holder, int position)
在绑定数据时调用,如果使用回收的视图进行显示,不会调用上面的方法,只会调用这个方法,这样就可以减少findViewById的调用,同时可以复用资源。
public int getItemCount()
返回当前条目数。
这个示例中的Adapter主要依靠调用insertItem和removeItem来动态添加和移除条目数据,另外我们还设置了一个点击监听器,来处理点击事件,最后,可以注意看看ViewHolder,它是Adapter访问操作视图的媒介。下面是我们item的布局,很简单,只有一个TextView
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/holo_blue_bright"> <TextView android:id="@+id/content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:textAppearance="?android:textAppearanceLarge" android:textStyle="bold"/></FrameLayout>现在我们就准备好了decoration和adapter,就可以使用RecyclerView了,首先是布局<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.yjp.recyclerviewtest.MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /></FrameLayout>我们还使用了一个菜单,资源文件如下:<?xml version="1.0" encoding="utf-8"?><menu xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"> <item android:title="垂直" android:id="@+id/vertical" app:showAsAction="never" /> <item android:title="水平" android:id="@+id/horizontal" app:showAsAction="never" /> <item android:title="网格" android:id="@+id/grid" app:showAsAction="never" /> <item android:title="添加" android:id="@+id/add" app:showAsAction="never" /> <item android:title="删除" android:id="@+id/remove" app:showAsAction="never" /></menu>最后是我们的类文件:package com.yjp.recyclerviewtest;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.support.v7.widget.GridLayoutManager;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.RecyclerView;import android.view.Menu;import android.view.MenuItem;import android.widget.Toast;public class MainActivity extends AppCompatActivity implements ItemAdapter.OnItemClickListener { private RecyclerView mRecyclerView; private ItemAdapter mItemAdapter; private MarginDecoration mMarginDecoration; private LineDecoration mLineDecoration; private LinearLayoutManager mHorizontalLayoutManager; private LinearLayoutManager mVerticalLayoutManager; private GridLayoutManager mGridLayoutManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //装饰 mMarginDecoration = new MarginDecoration(this); mLineDecoration = new LineDecoration(this); //创建LayoutManager mHorizontalLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); mVerticalLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); mGridLayoutManager = new GridLayoutManager(this, 2, LinearLayoutManager.VERTICAL, false); mGridLayoutManager.setSpanSizeLookup(new CustomGridSpanSizeLookup()); mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); //设置默认布局管理器 mRecyclerView.setLayoutManager(mVerticalLayoutManager); //设置装饰 mRecyclerView.addItemDecoration(mMarginDecoration); //设置Adapter mItemAdapter = new ItemAdapter(this); mRecyclerView.setAdapter(mItemAdapter); //设置监听器 mItemAdapter.setOnItemClickListener(this); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.options, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); switch (id) { case R.id.horizontal: mRecyclerView.setLayoutManager(mHorizontalLayoutManager); mRecyclerView.removeItemDecoration(mLineDecoration); return true; case R.id.vertical: mRecyclerView.setLayoutManager(mVerticalLayoutManager); mRecyclerView.removeItemDecoration(mLineDecoration); return true; case R.id.grid: mRecyclerView.setLayoutManager(mGridLayoutManager); mRecyclerView.addItemDecoration(mLineDecoration); return true; case R.id.add: mItemAdapter.insertItem(mItemAdapter.getItemCount()); return true; case R.id.remove: mItemAdapter.removeItem(mItemAdapter.getItemCount() - 1); return true; default: return false; } } @Override public void onItemClicked(ItemAdapter.ItemHolder holder, int position) { Toast.makeText(this, holder.getContent() + "被点击", Toast.LENGTH_SHORT).show(); }}注释已经写的很清楚了,唯一需要特别说明的一个东西,我们前面没有准备,就是为GridLayoutManager设置的CustomGridSpanSizeLookup,代码如下:package com.yjp.recyclerviewtest;import android.support.v7.widget.GridLayoutManager;public class CustomGridSpanSizeLookup extends GridLayoutManager.SpanSizeLookup { @Override public int getSpanSize(int position) { return (position % 3 == 0 ? 2: 1); }}它派生自GridLayoutManager.SpanSizeLookup,一个Item占用的span宽度,当position能被3整除时,一个item占用一行,也就是2个span,无法整除的,一个占用1个span,就产生了我们上面网格布局的效果。可以看到,使用RecyclerView并不是很复杂,但是要理解各个类的作用以及相互之间的关系,在合适的类中做合适的工作,最后将它们放在一起就能产生很好的效果。

新闻热点
疑难解答