前言:
RecyclerView
小组件比 ListView 更高级且更具灵活性。 此小组件是一个用于显示庞大数据集的容器,可通过保持有限数量的视图进行非常有效的滚动操作。 如果您有数据集合,其中的元素将因用户操作或网络事件而发生改变,请使用RecyclerView
小组件。
RecyclerView
类别将通过提供下列功能简化庞大数据集的显示与处理:
用于项目定位的布局管理器
用于通用项目操作(例如删除或添加项目)的默认动画
您也可灵活选择如何为 RecyclerView
小组件定义定制布局管理器与动画。
如果要使用 RecyclerView
小组件,您必须指定一个适配器和一个布局管理器。 如果要创建一个适配器,请扩展 RecyclerView.Adapter
类别。
实现的详情将取决于数据集的具体信息以及视图的类型。 如果要了解更多信息,请参阅下列示例。
布局管理器将确定RecyclerView
内各项目视图的位置并决定何时重新使用用户已不可见的项目视图。 如果要重新使用(或重复使用)一个视图,布局管理器可能会要求适配器以数据集中的另一个元素替换视图的内容。 以此方式重复使用视图将可避免创建不必要的视图或执行成本高昂的 findViewById() 查找,从而改善性能。
RecyclerView
提供这些内置布局管理器:
LinearLayoutManager
以垂直或水平滚动列表方式显示项目。
GridLayoutManager
在网格中显示项目。
StaggeredGridLayoutManager 在分散对齐网格中显示项目。
如果要创建一个定制布局管理器,请扩展 RecyclerView.LayoutManager
类别。
动画
RecyclerView
在默认情况下启用增添与删除项目的动画。如果要定制这些动画,请扩展RecyclerView.ItemAnimator类别
并使用 RecyclerView.setItemAnimator()
方法。
添加依赖项
RecyclerView
小组件为 v7 支持内容库的一部分。 如果要在您的项目中使用这些小组件,请将 Gradle 依赖项添加至您的应用模块:
dependencies { ... compile 'com.android.support:recyclerview-v7:21.0.+' }
效果图:
RecyclerView解析
解析一:RecyclerView的基本使用(数据正常显示)
步骤一:编写布局(可以和listView对比来用)
<android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="8" android:id="@+id/rv" > </android.support.v7.widget.RecyclerView>
步骤二:编写适配器
1>适配器需要继承RecyclerView.Adapter<MyAdapter.ViewHolder>
2>重写RecyclerView.Adapter<MyAdapter.ViewHolder>里边的三个方法:
onCreateViewHolder(ViewGroup parent, int viewType)
public void onBindViewHolder(final ViewHolder holder, int position)
public int getItemCount()
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { /*** *所要展示的数据 */ private List<String> mDataset; /** * 构造函数 * @param myDataset */ public MyAdapter(List<String> myDataset) { mDataset = myDataset; } /** * 用于将布局载入ViewHolder里,实例化ViewHolder,并返回ViewHolder实例 * @param parent * @param viewType * @return */ @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()) .inflate(R.layout.recyclerview_item_layout,parent,false); ViewHolder mViewHolder = new ViewHolder(v); return mViewHolder; } /** * 初始化子条目里边的控件 * @param holder * @param position */ @Override public void onBindViewHolder(final ViewHolder holder, int position) { holder.mTextView.setText(mDataset.get(position)); if(listener != null){ holder.mTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { listener.onItemClick(v); } }); } } /*** * getItemCount()与listView里边的getItemCount()方法所起作用一致, * 都是得到Adapter中所有条目的总数 * @return */ @Override public int getItemCount() { return mDataset.size(); } /** * ViewHolder继承于RecyclerView.ViewHolder, * 然后在ViewHolder里边保存子条目的控件 */ class ViewHolder extends RecyclerView.ViewHolder{ public TextView mTextView; public ViewHolder(View v) { super(v); mTextView = (TextView) v.findViewById(R.id.tv); } } }
步骤三:初始化数据和控件
主要是设置RecyclerView的适配器以及布局管理器
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); initView(); }
/** * 构建数据 */ private void initData() { mCityList.add("广州"); mCityList.add("北京"); mCityList.add("深圳"); mCityList.add("上海"); mCityList.add("杭州"); mCityList.add("武汉"); mCityList.add("天津"); mCityList.add("重庆"); mCityList.add("厦门"); mCityList.add("香港"); mCityList.add("福州"); mCityList.add("南京"); mCityList.add("苏州"); mCityList.add("黄石"); mCityList.add("大连"); mCityList.add("丽江"); mCityList.add("青岛"); mCityList.add("重庆"); mCityList.add("扬州"); mCityList.add("澳门"); }
/** * 初始化控件 */ public void initView(){ mRecyclerView = (RecyclerView) findViewById(R.id.rv); /** * 指定一个布局管理器,在这里指定为LinearLayoutManager, * 若没有设置其排放的位置,则默认为竖直排放 */ mLayoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(mLayoutManager); /** * 获取适配器 */ mAdapter = new MyAdapter(mCityList); /** * 设置RecyclerView的适配器 */ mRecyclerView.setAdapter(mAdapter); }
解析二:布局管理器和动画
通过设置布局管理器,可以实现不同的展示效果,例如实现ListView的效果,GridView的效果,还有另外一种效果(自行体会)。
在代码的初始部分,布局管理器是需要进行设置的,否则,数据是不会显示出来的。例如:
/** * 指定一个布局管理器,在这里指定为LinearLayoutManager, * 若没有设置其排放的位置,则默认为竖直排放 */ mLayoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(mLayoutManager);
动画,主要是用在在删除条目或增加条目时,产生动画的效果,在代码中,不设置,则会使用系统默认的一种效果显示出来。
当然这个动画也可以自行去定义一个。但是在增删时,若想要看到动画效果,那么在添加数据时,需要用mAdapter.notifyItemInserted(index)
来替代mAdapter.notifyDataSetChanged()方法,在删除数据时,需要用mAdapter.notifyItemRemoved(index);来替代mAdapter.notifyDataSetChanged()
方法。例如:
/** * 设置动画,这里采用的是默认的一个动画, * 若不设置,也会使用这个默认的动画,当然也可以自行去定义动画。 */ mRecyclerView.setItemAnimator(new DefaultItemAnimator());
解析三:分割线解析
分割线的实现,如下代码所示:
/** * 设置分割线 */ mItemDecoration = new DividerItemDecoration(this, mCurrentOrientation); mRecyclerView.addItemDecoration(mItemDecoration);
在这里,DividerItemDecoration类是需要我们自己去实现,它继承于RecyclerView.ItemDecoration,所以需要重写
其里边的方法,如下(参考官网Sample):
onDraw()主要用来实现分割线的绘制
@Override public void onDraw(Canvas c, RecyclerView parent) { if (mOrientation == VERTICAL_LIST) { drawVertical(c, parent); } else { drawHorizontal(c, parent); } }
getItemOffsets()主要是用来给绘制的分割线留出空间,
当然,也可以实现让 Item 与 其余 Item之间产生一定的间隔
@Override public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { int margin = (int) dimens*1; if (mOrientation == VERTICAL_LIST) { /** * Set the rectangle's coordinates to the specified values. Note: no range * checking is performed, so it is up to the caller to ensure that * left <= right and top <= bottom. * * @param left The X coordinate of the left side of the rectangle * @param top The Y coordinate of the top of the rectangle * @param right The X coordinate of the right side of the rectangle * @param bottom The Y coordinate of the bottom of the rectangle */ /** * 其实这一部分和设置Item的margin效果一样, * 源码里边会把这一部分空间当做是系统已占用的空间,所以在绘制Item时, * 这一部分的是不会再被Item所利用的,而是空出来,由于分割线的绘制早于 * Item的绘制,所以我们可以通过空出这么一部分信息,从而让分割线绘制的 * 部分不被其余Item所占用。 * * 在本示例中,我在布局文件中,把Item的margin设置了 1dp , * 而分割线是在Item的margin下边绘制的,所以在本示例中, * 分割线会距离上边的Item 1dp 的间隔,与此同时,又由于分割线的高度为 4dp, * 所以在outRect.set(0, 0, 0, mDivider.getIntrinsicHeight() + margin)函数中, * 我通过也空出了分割线的高度 4dp ,然后还空出一个 1dp (int margin = (int) dimens*1;), * 这样大家就可以观察到 在分割线的上方和下方,都会有一个 1dp 的空白间隔,这也就是 * outRect.set(0, 0, 0, mDivider.getIntrinsicHeight() + margin)函数所起的作用, * 若将这个函数的参数全部设置为0,那么,Item是会覆盖掉分割线的,因为 Item的绘制晚于分割线的绘制; */ outRect.set(0, 0, 0, mDivider.getIntrinsicHeight() + margin); } else { outRect.set(0, 0, mDivider.getIntrinsicHeight() + margin, 0); } }
解析四:条目的侦听
public interface OnItemClickListen{ void onItemClick(View v); }
public void setListener(OnItemClickListen listener){ this.listener = listener; }
public void onBindViewHolder(final ViewHolder holder, int position) { holder.mTextView.setText(mDataset.get(position)); if(listener != null){ holder.mTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { listener.onItemClick(v); } }); } }
/** * 自定义条目的侦听,因为默认情况下, * RecyclerView是没有对条目侦听的API的, * 需要自己去予以实现... */ mAdapter.setListener(new MyAdapter.OnItemClickListen(){ @Override public void onItemClick(View v) { Toast.makeText(MainActivity.this,((TextView) v).getText(),Toast.LENGTH_SHORT).show(); } });