RecyclerView实现瀑布流

RecyclerView is a flexible view for providing a limited window into a large data set.

RecyclerView可以说是ListView和GridView的升级版本,提供了ListView和GridView的基础功能,并且有良好的扩展性,比如可以控制列表的布局和动画。该组件定制型太强,所以相对于ListView提出了很多新概念,刚接触可能会觉得比较复杂。

先来看下效果图:

瀑布流

与RecyclerView相关的重要基础类:

RecyclerView是android-support-library-v7里面的类,需要在gradle里面导入:

compile 'com.android.support:recyclerview-v7:23.3.0'
//或者 compile 'com.android.support:cardview-v7:21.0.+'

RecyclerView实现瀑布流的实现步骤如下,其实也就是通常使用RecyclerView的步骤:

1.在布局文件里面声明RecyclerView,findViewById获取实例,或者通过代码的方式将RecyclerView添加到布局里面。

这两种方式还是有区别的,xml的方式可以声明RecyclerView的ScrollBar,代码方式由于初始化的时候系统未初始化ScrollBar的相关属性,所以这种方式初始化的Recycler是没有ScrollBar的。具体参考

    <!-- 声明scrollbars -->
    <android.support.v7.widget.RecyclerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

2.初始化Recycler的数据适配器

在我们使用ListView的时候,我们会在Adapter#getView的时候使用ViewHolder来缓存我们的Sub View对象避免重复的findViewById,已到达优化ListView的目的,但是我们可以选择用与不用。但是在RecyclerView.Adapter中就必须使用,因为在创建View对象的使用明确地要求我们返回ViewHolder对象,并且在给View加载数据也是使用ViewHolder。

在实现RecyclerView.Adapter必须要实现的三个方法,我们的列表项只有一个ImageView所以比较好理解。并且当我们的数据源发生改变是可以通过notifyItemInserted(int index),notifyItemRemoved(int index),notifyItemRangeChanged(int start,int end)通知数据适配器更新数据,如果我们在给RecyclerView设置了ItemAnimator还会显示相应的动画效果。

 public class MyViewHolder extends RecyclerView.ViewHolder {

      ImageView imageView;

      public MyViewHolder(View itemView) {
          super(itemView);
          imageView = (ImageView) itemView.findViewById(R.id.iv);
      }

  }


public class MyAdapter extends RecyclerView.Adapter<MyViewHolder>{

    private List<Integer> resIds;

    public MyAdapter(List<Integer> resIds){
        this.resIds = resIds;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View item = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_view_item,parent,false);
        return new MyViewHolder(item);
    }

    @Override
    public void onBindViewHolder(final MyViewHolder holder, final int position) {
         holder.imageView.setImageResource(resIds.get(position));
    }

    @Override
    public int getItemCount() {
        return resIds.size();
    }
}

3.默认我们的列表项与项之间是没有间隙的,可以通过添加自定义的ItemDecorations来实现列表项之间的间隙。

我们可以通过重写ItemDecorations的getItemOffsets来控制项与项之间的偏移量来实现列表之间的间隙。

/**
 * 代码来自 http://stackoverflow.com/a/30701422
 */
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {

  private int spanCount;
  private int spacing;
  private boolean includeEdge;

  public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
    this.spanCount = spanCount;
    this.spacing = spacing;
    this.includeEdge = includeEdge;
  }

  @Override
  public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    int position = parent.getChildAdapterPosition(view); // item position
    int column = position % spanCount; // item column

    if (includeEdge) {
      outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
      outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)

      if (position < spanCount) { // top edge
        outRect.top = spacing;
      }
      outRect.bottom = spacing; // item bottom
    } else {
      outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
      outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f /    spanCount) * spacing)
      if (position >= spanCount) {
        outRect.top = spacing; // item top
      }
    }
  }
}

4.定义列表项增加,删除,重排序的动画,提供了默认的实现DefaultItemAnimator,效果为平移动画。

5.给列表项增加点击事件与长按事件,RecyclerView默认是没有提供的,可以通过在RecyclerView.Adapter中绑定数据的时候加上我们需要的事件,比如:

public class MyAdapter extends RecyclerView.Adapter<MyViewHolder>{

    private List<Integer> resIds;

    public MyAdapter(List<Integer> resIds){
        this.resIds = resIds;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View item = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_view_item,parent,false);
        return new MyViewHolder(item);
    }

    @Override
    public void onBindViewHolder(final MyViewHolder holder, final int position) {
         holder.imageView.setImageResource(resIds.get(position));

         holder.imageView.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 if(mOnItemClickListener != null) {
                     mOnItemClickListener.onClick(holder,position);
                 }
             }
         });

         holder.imageView.setOnLongClickListener(new View.OnLongClickListener() {
             @Override
             public boolean onLongClick(View v) {
                 if(mOnItemLongClickListener != null) {
                     mOnItemLongClickListener.onLongClick(holder,position);
                     return true;
                 }
                 return false;
             }
         });
    }

    @Override
    public int getItemCount() {
        return resIds.size();
    }

    private OnItemClickListener mOnItemClickListener;
    private OnItemLongClickListener mOnItemLongClickListener;

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        mOnItemClickListener = onItemClickListener;
    }

    public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
        mOnItemLongClickListener = onItemLongClickListener;
    }


    interface OnItemClickListener {
        void onClick(RecyclerView.ViewHolder VH ,int position);
    }

    interface OnItemLongClickListener {
        void onLongClick(RecyclerView.ViewHolder VH,int position);
    }

}

这样,就可以监听列表项的点击事件和长按事件了。

综合一下,实现瀑布流的RecyclerView的代码就如下:

    RecyclerView recyclerView = new RecyclerView(this);
    RecyclerView.LayoutManager layoutManager = new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL);
    recyclerView.setLayoutManager(layoutManager);
    adapter = new MyAdapter(getData());
    recyclerView.setAdapter(adapter);
    recyclerView.addItemDecoration(new GridSpacingItemDecoration(2,10,true));
    recyclerView.setItemAnimator(new DefaultItemAnimator());
    // use this setting to improve performance if you know that changes
    // in content do not change the layout size of the RecyclerView
    recyclerView.setHasFixedSize(true);
    adapter.setOnItemClickListener(this);

完整的示例代码查看Github上面的源码:)

参考资料:
http://www.grokkingandroid.com/first-glance-androids-recyclerview/

https://developer.android.com/training/material/lists-cards.html