当我们需要对大量的数据进行展示的时候,通常会用到ListView、GridView等。而Recycler则可以看做是能够完成ListView、GridView、StaggeredGridView的一个强大容器。
先来看看RecyclerView的基本使用。
通常在使用ListView等控件的时候都需要一个数据适配器,在RecyclerView中也继承了这个优良的传统。
为了使RecyclerView将数据的样式展示成不同的样式,它为我们提供了LayoutManager用来控制数据展示的布局。通过设置ItemDecoration可以控制分分割线的样式,通过ItemAnimator可以控制增删数据时的动画效果。
一 Recycler基础使用
下面,先来一个ListView布局的列子。
public class MainActivity extends Activity {
RecyclerView recyclerView;
ArrayList<Product> productList;
private StaggeredGridLayoutManager manager;
MyNormalRecyclerAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView= (RecyclerView) findViewById(R.id.recycler);
//layoutManager
recyclerView.setLayoutManager(new LinearLayoutManager(this));
initData();
//adapter
adapter=new MyNormalRecyclerAdapter(productList);
recyclerView.setAdapter(adapter);
}
private void initData() {
productList = new ArrayList<Product>();
Product product;
for (int i = 0; i < 30; i++) {
int asd = R.drawable.ic_launcher;
product = new Product(asd, "" + i);
productList.add(product);
}
}
}
在上面代码中,先初始化了一个数据源,之后通过 recyclerView.setLayoutManager(new LinearLayoutManager(this));将recyclerview的设置成ListView的显示样式。
这里使用了一个自定义的adapter。
public class MyRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>/*<RecyclerView.ViewHolder>*//*RecyclerView.Adapter*/{
public static final int TYPE_HEADER = 0;
public static final int TYPE_NORMAL = 1;
public static final int TYPE_FOOTER = 2;
private View mHeaderView;
private View mFooterView;
ArrayList<Product> products;
public int getItemViewType(int position) {
Log.d("HK", "getItemViewType called "+position);
if(mHeaderView == null) return TYPE_NORMAL;
if(position == 0) return TYPE_HEADER;
return TYPE_NORMAL;
}
public MyRecyclerAdapter(ArrayList<Product> list) {
products = list;
}
@Override
public int getItemCount() {
return 0;
}
public int getRealPosition(RecyclerView.ViewHolder holder) {
int position = holder.getLayoutPosition();
Log.d("HK", "getRealPosition called "+position);
return mHeaderView == null ? position : position - 1;
}
@Override
public void onBindViewHolder(ViewHolder arg0, final int position) {
Log.d("HK", "onBindViewHolder called "+position);
if(getItemViewType(position) == TYPE_HEADER) return;
final int pos = getRealPosition(arg0);
final Product data = products.get(pos);
if(arg0 instanceof MasonryView) {
((MasonryView) arg0).textView.setText(products.get(position).getTitle());
((MasonryView) arg0).imageView.setImageResource(products.get(position).getImg());
}
ImageView image = ((MasonryView)arg0).imageView;
TextView text = ((MasonryView)arg0).textView;
image.setImageResource(products.get(position).getImg());
text.setText(products.get(position).getTitle());
View view = ((MasonryView)arg0).view;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Log.d("HK", "onCreateViewHolder called "+viewType);
if(mHeaderView != null && viewType == TYPE_HEADER) return new MasonryView(mHeaderView);
View layout = LayoutInflater.from(parent.getContext()).inflate(R.layout.receycler_item, parent, false);
return new MasonryView(layout);
}
class MasonryView extends RecyclerView.ViewHolder{
ImageView imageView;
TextView textView;
View view;
public MasonryView(View itemView){
super(itemView);
view = itemView;
imageView= (ImageView) itemView.findViewById(R.id.item_img );
textView= (TextView) itemView.findViewById(R.id.item_title);
}
}
}
在这个适配其中,通过onBindViewHolder来进行数据的绑定,而通过onCreateViewHolder来返回视图。代码中出现的mHeaderView等将在后文进行介绍。
有了这个适配器后,通过上一段的代码,就可以呈现出ListView的显示效果了。当然,如果需要将显示效果更改为gridview,只需要将
recyclerView.setLayoutManager(new LinearLayoutManager(this));更改为 mRecyclerView.setLayoutManager(new GridLayoutManager(this,4))就行。
当然如果要加上分割线,只需要添加
SpacesItemDecoration spaces = new SpacesItemDecoration(16);
recyclerView.addItemDecoration(spaces);
SpacesItemDecoration 是自定义的一个类,可以设置分割线的样式等。
public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
private int space;
public SpacesItemDecoration(int space) {
this.space=space;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.left=space;
outRect.right=space;
outRect.bottom=space;
// outRect.top=space;
if(parent.getChildAdapterPosition(view)==0){
// outRect.top=space;
}
}
如果需要对recyclerview中添加点击事件,则需要自己在适配器中添加接口,添加方式也比较简单。代码如下:
现在适配器中设置一个回调。
private OnItemClickLitener mOnItemClickLitener;
public interface OnItemClickLitener
{
void onItemClick(View view, int position);
void onItemLongClick(View view , int position);
}
public void onBindViewHolder(final ViewHolder viewholder, final int position) {
......
if (mOnItemClickLitener != null)
{
viewholder.itemView.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
int pos = viewholder.getLayoutPosition();
mOnItemClickLitener.onItemClick(viewholder.itemView, pos);
}
});
viewholder.itemView.setOnLongClickListener(new OnLongClickListener()
{
@Override
public boolean onLongClick(View v)
{
int pos = viewholder.getLayoutPosition();
mOnItemClickLitener.onItemLongClick(viewholder.itemView, pos);
return false;
}
});
}
}
}
接下来在需要点击的地方实现回调
adapter.setOnItemClickLitener(new OnItemClickLitener() {
@Override
public void onItemClick(View view, int position) {
// TODO Auto-generated method stub
Toast.makeText(MainActivity.this, position + " click",
Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
// TODO Auto-generated method stub
Toast.makeText(MainActivity.this, position + " long click",
Toast.LENGTH_SHORT).show();
//mAdapter.removeData(position);
}
});
(二)为Recycler添加头部和底部
RecyclerView支持在容器中添加头部和底部来打造优美的视觉效果。由于头部和底部都是作为recyvlerview容易的一个view。为了将其与普通的ItemView进行区分,则需要使用到public int getItemViewType(int position)。先来看看在适配器中代码。
private View mHeaderView;
private View mFooterView;
public static final int TYPE_HEADER = 0;
public static final int TYPE_NORMAL = 1;
public static final int TYPE_FOOTER = 2;
public void setHeaderView(View headerView) {
mHeaderView = headerView;
notifyItemInserted(0);
Log.d("HK", "setHeaderView called" );
}
public void setFooterView(View footerView) {
mFooterView = footerView;
int footIndex = getItemCount()-1;
notifyItemInserted(footIndex);
Log.d("HK", "setFooterView called and index = "+footIndex);
}
public void onBindViewHolder(final ViewHolder viewholder, final int position) {
// TODO Auto-generated method stub
if(getItemViewType(position) == TYPE_HEADER) return;
if(getItemViewType(position) == TYPE_FOOTER) return;
final int pos = getRealPosition(viewholder);
Log.d("HK", "onBindViewHolder called position pos "+position+" "+pos);
}
public int getItemViewType(int position) {
Log.d("HK", "getItemViewType called "+position);
if((mHeaderView == null) && (mFooterView == null)) return TYPE_NORMAL;
if(position == 0) return TYPE_HEADER;
if(position == getItemCount()-1) return TYPE_FOOTER;
return TYPE_NORMAL;
}
上面的代码中在适配器中添加了增加头部的函数,增加底部的函数以及对VIEW类型的返回。因为加入了头部和底部,那么普通item的位置就和为添加之前有了一定的差别,因此为了获取位置,我们使用了getRealPosition(viewholder);
public int getRealPosition(RecyclerView.ViewHolder holder) {
int position = holder.getLayoutPosition();
Log.d("HK", "getRealPosition called "+position);
return mHeaderView == null ? position : position - 1;
}
将适配器做完改造后,只需要在使用中进行调用就行。
setHeader(recyclerView);
setFooter(recyclerView);
private void setFooter(RecyclerView view) {
View footerView = LayoutInflater.from(this).inflate(R.layout.footerlayout, view, false);
adapter.setFooterView(footerView);
}
private void setHeader(RecyclerView view) {
View header = LayoutInflater.from(this).inflate(R.layout.titlelayout, view, false);
adapter.setHeaderView(header);
}
但是,按照上述做法我们会发现并不能实现想要的效果。这是应为作为gridview 头部索引为0,如果gridview 分为了多列,那么这个0只会在第一个位置,而不会占用一整行。为了解决这个问题,需要在适配器中添加如下代码。
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if(manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (getItemViewType(position) == TYPE_HEADER||getItemViewType(position) == TYPE_FOOTER)
? gridManager.getSpanCount() : 1;
}
});
}
}
当然 ,如果我们将样式设置为瀑布流类型就需要添加如下代码:
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
super.onViewAttachedToWindow(holder);
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if(lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
if(holder.getLayoutPosition() == 0 ||(holder.getLayoutPosition() == getItemCount()-1)) {
p.setFullSpan(true);
}
}
}
以上代码可在GITHUB下载。
GIT:https://github.com/everyhappy/RecyclerViewDemo