RecyclerView完全解析之打造新版类Gallery效

(一).前言:

话说RecyclerView已经面市很久,也在很多应用中得到广泛的使用,在整个开发者圈子里面也拥有很不错的口碑,那说明RecyclerView拥有比ListView,GridView之类控件有很多的优点,例如:数据绑定,Item View创建,View的回收以及重用等机制。本系列文章会包括到以下三个部分:

  1. RecyclerView控件的基本使用,包括基础,进阶,高级部分,动画之类(点击进入)
  2. RecyclerView控件的实战实例
  3. RecyclerView控件集合AA(Android Annotations)注入框架实例

今天使我们本系列文章的第二讲主要是我们通过RecyclerView来打造一个新版类似Gallery控件的效果。本次讲解所有用的Demo例子已经全部更新到下面的项目中了,欢迎大家star和fork。

FastDev4Android框架项目地址:https://github.com/jiangqqlmj/FastDev4Android

(二).基本实现

上一讲我们已经对于RecyclerView的基本使用和进阶部分做了讲解(点击进入),下面我们一步步的来打造一个新版Gallery效果控件。先来看一下和RecyclerView相关类:

类名说明
RecyclerView.Adapter可以托管数据集合,为每一项Item创建视图并且绑定数据
RecyclerView.ViewHolder承载Item视图的子布局
RecyclerView.LayoutManager负责Item视图的布局的显示管理
RecyclerView.ItemDecoration给每一项Item视图添加子View,可以进行画分隔线之类的东西
RecyclerView.ItemAnimator负责处理数据添加或者删除时候的动画效果

那如果要实现Gallery的效果,里面的Item是横向滑动的,也就是说我们的RecyclerView可以支持横向滑动,这边我们直接采用了LinearLayoutManager布局管理器,同时设置方向为:HORIZONTAL(水平)

下面来具体看代码:

1.作为RecyclerView控件,我们需要设置每一项Item的布局:

<?xmlversion="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
   android:layout_height="wrap_content"
   android:layout_width="wrap_content"
    android:gravity="center"
    android:padding="8.0dip">
    <ImageView
        android:id="@+id/item_img"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:scaleType="fitXY"
       android:adjustViewBounds="true"
       android:src="@drawable/ic_item_gallery"/>
    <TextView
        android:id="@+id/item_tv"
        android:text="标题1"
       android:layout_marginTop="5dp"
        android:textSize="15sp"
       android:layout_gravity="center_horizontal"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
        />
</LinearLayout>

这个布局中我们比较简单,定义了一个图片和一个标题,垂直方向布局。

2.间接着,和ListView写法差不多,需要自定义适配器,来创建每一项布局视图以及把数据和视图绑定起来,所以这边继承RecyclerView.Adapter类创建一个自定义适配器GalleryRecyclerAdapter.java。那么需要实现基类中的三个方法:

  • onCreateViewHolder(ViewGroup parent,int viewType) 创建Item View然后通过ViewHolder来承载
  • onBindViewHolder(ViewHolder holder,int position)进行视图和数据绑定
  • getItemCount()获取列表中视图Item的数量

具体GallerRecyclerAdapter实现代码如下:

public class GalleryRecyclerAdapter extends RecyclerView.Adapter<GalleryRecyclerAdapter.ViewHolder> {
 
    private List<GalleryModel> models;
    private LayoutInflater mInflater;
 
    public GalleryRecyclerAdapter(Context context){
        models=new ArrayList<GalleryModel>();
        for (int i=0;i<20;i++){
            int index=i+1;
            models.add(new GalleryModel(R.drawable.ic_item_gallery,"Item"+index));
        }
        mInflater=LayoutInflater.from(context);
    }
 
    /**
     * 创建Item View  然后使用ViewHolder来进行承载
     * @param parent
     * @param viewType
     * @return
     */
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view=mInflater.inflate(R.layout.item_gallery_recycler,parent,false);
        ViewHolder viewHolder=new ViewHolder(view);
        return viewHolder;
    }
 
    /**
     * 进行绑定数据
     * @param holder
     * @param position
     */
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
       holder.item_img.setImageResource(models.get(position).getImgurl());
       holder.item_tv.setText(models.get(position).getTitle());
    }
 
    @Override
    public int getItemCount() {
        return models.size();
    }
 
 
    //自定义的ViewHolder,持有每个Item的的所有界面元素
    public static class ViewHolder extends RecyclerView.ViewHolder {
        private ImageView item_img;
        private TextView item_tv;
        public ViewHolder(View view){
            super(view);
           item_img=(ImageView)view.findViewById(R.id.item_img);
           item_tv=(TextView)view.findViewById(R.id.item_tv);
        }
    }
 
}

3.注意看上面的代码,我们继承了RecyclerView.ViewHolder实现一个自定义类ViewHolder,这个用来承载我们的子Item视图,现在Google已经要求开发者必须要使用ViewHolder了。在ViewHolder中我们进行控件的初始化工作,然后保存View视图。

//自定义的ViewHolder,持有每个Item的的所有界面元素
    public static class ViewHolder extends RecyclerView.ViewHolder {
        private ImageView item_img;
        private TextView item_tv;
        public ViewHolder(View view){
            super(view);
           item_img=(ImageView)view.findViewById(R.id.item_img);
           item_tv=(TextView)view.findViewById(R.id.item_tv);
        }
    }

4.最后在Activity中控件设置,例如布局管理器,Adapter绑定即可,完整代码如下:

public class RecyclerGalleryActivity extends BaseActivity {
    private RecyclerView gallery_recycler;
    private LinearLayout top_bar_linear_back;
    private TextView top_bar_title;
    @Override
    protected void onCreate(BundlesavedInstanceState) {
        super.onCreate(savedInstanceState);
       setContentView(R.layout.recycler_gallery_layout);
       top_bar_linear_back=(LinearLayout)this.findViewById(R.id.top_bar_linear_back);
       top_bar_linear_back.setOnClickListener(new CustomOnClickListener());
       top_bar_title=(TextView)this.findViewById(R.id.top_bar_title);
       top_bar_title.setText("RecyclerView打造Gallery效果");
        //初始化RecyclerView控件
       gallery_recycler=(RecyclerView)this.findViewById(R.id.gallery_recycler);
        //固定高度
        gallery_recycler.setHasFixedSize(true);
        //创建布局管理器
        LinearLayoutManager linearLayoutManager=new LinearLayoutManager(this);
        //设置横向
       linearLayoutManager.setOrientation(OrientationHelper.HORIZONTAL);
        //设置布局管理器
       gallery_recycler.setLayoutManager(linearLayoutManager);
        //创建适配器
        GalleryRecyclerAdapter adapter=new GalleryRecyclerAdapter(this);
        //绑定适配器
        gallery_recycler.setAdapter(adapter);
    }
    class CustomOnClickListener implements View.OnClickListener{
        @Override
        public void onClick(View v) {
           RecyclerGalleryActivity.this.finish();
        }
    }
}

5.在看运行效果之前,我们先来看下上面的代码,上面的代码基本注释已经全部加了,相应大家可以看的懂,不过我们需要来讲一下上面的LayoutManager(布局管理器)。

在上一讲中我们也讲到了,LayoutManger(布局管理器)该类负责将每一个Item视图在RecyclerView中的布局。目前RecyclerView已经给我们提供三个内置管理器:LinearLayoutManger,GridLayoutManger以及StaggeredGridLayoutManager。这边的例子中我们是采用LinearLayoutManger而且设置了横向水平布局了。当然LinearLayoutManger还给我们提供了以下几个方法来让开发者方便的获取到屏幕上面的顶部item和顶部item相关的信息:

  • findFirstVisibleItemPosition()
  • findFirstCompletlyVisibleItemPosition()
  • findLastVisibleItemPosition()
  • findLastCompletlyVisibleItemPosition()

这边的具体设置代码如下:

        //创建布局管理器
        LinearLayoutManagerlinearLayoutManager=new LinearLayoutManager(this);
        //设置横向
       linearLayoutManager.setOrientation(OrientationHelper.HORIZONTAL);
        //设置布局管理器
       gallery_recycler.setLayoutManager(linearLayoutManager);

6.初步运行效果如下:

 

(三).升级加入点击事件

通过上面的方式我们显示了一个类似于Gallery的效果,但是还远远不如实际Gallery的效果,现在只是可以有多项Item以及可以左右滑动,但是没有点击事件,下面我们来加入点击事件操作。

对于ListView来讲,我们可以为ListView加入setOnItemClickListener监听事件,但是对于RecyclerView控件来讲,RecyclerView已经不再负载Item视图的布局和显示,这些工作已经交给了LayoutManger来做了。所以RecyclerView也没有给我们提供类似onItemClick事件,这样如果非得要实现类似的功能,我们开发者也可以自定义模拟实现。来,我们继续往下看….

1.我们最终要实现点击列表上面每一项Item来回调点击方法,那么我们可以在Adapter中的每一项View上面做文章,首先我们来看一下Adapter中的onCreateViewHolder()方法:

 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Viewview=mInflater.inflate(R.layout.item_gallery_recycler,parent,false);
        ViewHolder viewHolder=new ViewHolder(view);
        return viewHolder;
    }

2.该方法创建出了Item 视图,然后通过ViewHolder来进行承载了,既然这样那我们可以在View加载出来之后给它设置一些属性例如:颜色,大小,当然也可以是点击事件等等。那这边我们给View添加onClick事件,然后在onClick方法把View点击触发的事件回调出去,同时可以回调一些参数内容出去。OK,那么我们这边就需要一个自定义的接口了,我们创建一个GallerRecyclerAdapter的内部类接口:

/**
     * 类似ListView的 onItemClickListener接口
     */
    public interface OnRecyclerViewItemClickListener{
        /**
         * Item View发生点击回调的方法
         * @param view   点击的View
         * @paramposition  具体Item View的索引
         */
        void onItemClick(View view,intposition);
    }

3.然后定义接口,同时提供set和get方法,来让外部传入该接口,初始化:

private OnRecyclerViewItemClickListener onRecyclerViewItemClickListener;
 
    public OnRecyclerViewItemClickListener getOnRecyclerViewItemClickListener() {
        return onRecyclerViewItemClickListener;
    }
    public void setOnRecyclerViewItemClickListener(OnRecyclerViewItemClickListener onRecyclerViewItemClickListener) {
        this.onRecyclerViewItemClickListener =onRecyclerViewItemClickListener;
    }

4.现在开始在onCreateViewHolder()方法中给View添加一个onClick事件,然后相应处理,判断onRecyclerViewItemClickListener是否存在,把事件回调出去:

 view.setOnClickListener(newView.OnClickListener() {
            @Override
            public void onClick(View v) {
               if(onRecyclerViewItemClickListener!=null){
                   onRecyclerViewItemClickListener.onItemClick(view,(int)view.getTag());
                }
            }
        });

5.上面的代码中大家可能注意到onItemClick()方法中的第二个参数,获取了tag,因为这边的position索引值是在onBindViewHolder()方法中设置的:

public voidonBindViewHolder(ViewHolder holder, int position) {
       holder.item_img.setImageResource(models.get(position).getImgurl());
       holder.item_tv.setText(models.get(position).getTitle());
        holder.itemView.setTag(position);
    }

6.OK这边我们搞定了一个Item点击监听方法,接下去就是使用了,

adapter.setOnRecyclerViewItemClickListener(new GalleryRecyclerAdapter.OnRecyclerViewItemClickListener() {
            @Override
            public void onItemClick(View view,int position) {
               Toast.makeText(RecyclerGalleryActivity.this,"您点击的Item的索引为:"+position,Toast.LENGTH_SHORT).show();
            }
        });

7.现在该功能代码整完了,运行效果如下:

(四).升级之加入分割线

上面我们已经给每一项Item加入了点击回调事件,但是总感觉还缺少点什么东西,例如分隔线。很遗憾的是,RecyclerView没有提供ListView控件这样设置分割线的方法,不过它给我们提供了ItemDecoration类。这个ItemDecoration可以使得每一个Item在视觉上面进行分隔开来。RecyclerView没有要求ItemDecoration必须要设置,同样作为开发者可以选择不设置或者设置多个Decoration。然后RecyclerView会进行相应的绘制。

我们这边定义了一个TestDecoration类,该类继承自RecyclerView.Decoration。只需要实现一下的两个方法即可:

  • onDraw(Canvas c,RecyclerView parent,RecyclerView.State state)
  • getItemOffset(Rect outRect,int itemPosition,RecyclerView parent)

具体实现代码如下:

public class TestDecoration extends RecyclerView.ItemDecoration {
    //采用系统内置的风格的分割线
    private static final int[] attrs=newint[]{android.R.attr.listDivider};
    private Drawable mDivider;
 
    public TestDecoration(Context context) {
        TypedArray typedArray=context.obtainStyledAttributes(attrs);
        mDivider=typedArray.getDrawable(0);
        typedArray.recycle();
    }
 
    /**
     * 进行自定义绘制
     * @param c
     * @param parent
     * @param state
     */
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        int top=parent.getPaddingTop();
        intbottom=parent.getHeight()-parent.getPaddingBottom();
        int childCount=parent.getChildCount();
        for(int i=0;i<childCount;i++){
            View child=parent.getChildAt(i);
            RecyclerView.LayoutParams layoutParams=(RecyclerView.LayoutParams)child.getLayoutParams();
            intleft=child.getRight()+layoutParams.rightMargin;
            intright=left+mDivider.getIntrinsicWidth();
           mDivider.setBounds(left,top,right,bottom);
            mDivider.draw(c);
        }
    }
 
    @Override
    public void getItemOffsets(Rect outRect,View view, RecyclerView parent, RecyclerView.State state) {
       outRect.set(0,0,mDivider.getIntrinsicWidth(),0);
    }
}

最后给RecyclerView添加该分隔线即可:

//设置分割线
gallery_recycler.addItemDecoration(new TestDecoration(this));

运行效果如下:

(五).升级之分割线改造

仔细看上面的运行效果,我们会发现一个问题,那就是分割线垂直分布,但是没有自适应控件的高度,直接延伸到界面的底部了。重新检查了有关的所有布局文件发现,高度都设置成了warp_content,但是实际的效果还是没有自适应。原来在哪里呢?

真正的原因是因为RecyclerView控件已经不负责每一项VIew的显示了,那我们来看LayoutManger(布局管理器)该进行负责Item的布局显示了,所以我们需要进行实现一个LayoutManger,然后重写里边的onMeasure()方法。计算高度即可,具体代码如下:

public class CustomLinearLayoutManager extends LinearLayoutManager {
    public CustomLinearLayoutManager(Contextcontext) {
        super(context);
    }
 
    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
        Viewview=recycler.getViewForPosition(0);
        if(view!=null){
           measureChild(view,widthSpec,heightSpec);
            int mWidth=View.MeasureSpec.getSize(widthSpec);
            intmHeight=view.getMeasuredHeight();
           setMeasuredDimension(mWidth,mHeight);
        }
    }
}

然后RecyclerView使用CustomLinearLayoutManger即可,运行效果如下:

(六).升级之添加删除Item动画

RecyclerView控件的一个优美之处就是当里边Item发生变化的时候可以加入相应的动画效果,涉及的类为RecyclerView.ItemAnimatior。一般当存在以下三种操作的时候可以加入动画效果:

  • Item删除
  • Item添加
  • Item移动

当我们的数据,或者移动的时候,去掉用Adapter给我们提供的以下两个方法即可:

  •  notifyItemInserted(int position)
  • notifyItemRemoved(int position)

那我们可以在Adapter中加入两个方法,分别为添加Item和删除Item的方法:

  //添加数据
    public void addItem(GalleryModel model, intposition) {
        models.add(position, model);
        notifyItemInserted(position);
    }
    //删除数据
    public void removeItem(int position) {
        models.remove(position);
        notifyItemRemoved(position);
    }

然后在外部进行调用这两个方法:

    adapter.addItem(newGalleryModel(R.drawable.ic_item_gallery,"Item Add"),3);
   adapter.removeItem(2);

最后千万不要忘记给RecyclerView设置动画效果,我这边就直接采用默认动画了。

 //设置动画
 gallery_recycler.setItemAnimator(newDefaultItemAnimator());

 最终运行效果如下:

(七).最后总结

今天通过实例带大家又重新把RecyclerView的相关使用讲解了一遍,实现类似Gallery效果,当然实例中还有很多缺点,需要进一步优化,后面的文章中也会继续更新的~

本次具体实例注释过的全部代码已经上传到FastDev4Android项目中了。同时欢迎大家去Github站点进行clone或者下载浏览:

https://github.com/jiangqqlmj/FastDev4Android 同时欢迎大家star和fork整个开源快速开发框架项目~下一讲我们会进行RecyclerView集合AA(Android Annotations)注入框架来实现实例,敬请期待!

文章导航