那年的RecyclerView我们从头撸一下

    xiaoxiao2021-08-20  287

    1. RecyclerView是什么?

    根据Google官方给出的说明:A flexible view for providing a limited window into a large data set。能够在有限的窗口中展示大数据集合的灵活视图。

    所以我们能够理解为,RecyclerView的一个恰当的使用场景是:由于尺寸限制,用户的设备不能一次性展现所有条目,用户需要上下滚动以查看更多条目。滚出可见区域的条目将被回收,并在下一个条目可见的时候被复用。

    对于减少内存开销和CPU的计算,缓存条目是一个非常有用的方法,因为这意味着我们不必每次都创建新的条目,从而减小内存开销和CPU的计算,而且还能够有效降低屏幕的卡顿,保证滑动的顺滑。

    RecyclerView不关心视觉效果(visuals)

    但是和ListView有什么区别呀?我们已经使用ListView很长一段时间了呀,它一样可以做到呀。从它的类名上看,RecyclerView代表的意义是,我只管Recycler View,也就是说RecyclerView只管回收与复用View,其他的开发者可以自己去设置。你想要另一个布局?插入另一个LayoutManager。你想要不同的动画吗?插入一个ItemAnimator,等等。可以看出其高度的解耦,给予你充分的定制自由(所以你才可以轻松的通过这个控件实现ListView,GirdView,瀑布流等效果)。

    2. 引入RecyclerView

    RecyclerView 是Support Library的一部分。所以只需要在app/build.gradle中添加以下依赖,便能立即使用:

    dependencies { compile 'com.android.support:recyclerview-v7:25.3.1'

    在布局文件中加入:

    <android.support.v7.widget.RecyclerView  android:id="@+id/recycler_view"  android:layout_width="match_parent"   android:layout_height="match_parent" />  

    然后在页面中引入RecyclerView即可:

    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); 

    OK,从现在开始,让我们一步一步,开始了解它。

    3. 使用RecyclerView

    下面的表格中就是使用RecyclerView来显示数据中用到的几个最重要的类,这些类都是RecyclerView的内部类,如果你想使用RecyclerView,需要做以下操作:

    Class功能Adapter处理数据集合并负责绑定视图ViewHolder持有所有的用于绑定数据或者需要操作的ViewLayoutManager负责摆放视图等相关操作ItemDecoration负责绘制Item附近的分割线ItemAnimator为Item的一般操作添加动画效果,如,增删条目等

    我们可以从下图更直观的了解到RecyclerView的基本结构:

    接下来,我将要描述每个类或接口的内容以及如何使用它。

    3.1 RecyclerView.ViewHolder

    ViewHolder的基本用法是用来存放View对象。Android团队很早之前就推荐使用“ViewHolder设计模式”,但是没有要求开发者在Adapter中必须使用ViewHolder模式。那么现在对于这种新型的RecyclerView.Adapter,我们必须实现并使用这种模式。

    Google官方等了这么长时间才强制使用ViewHolder模式,这有点奇怪,但迟做总比不做好。如果您不了解ViewHolder模式,请查看一下Android training Hold View Objects in a View Holder。另外网上有大量关于ListView优化的文章。面试重点。

    有一件事是专门针对RecyclerView的。ViewHolder子类可以通过访问公共成员itemView来访问ViewHolder的根视图。所以不需要在ViewHolder子类中存储。

    下面是示例的ViewHolder的代码,ViewHolder是示例Adapter的内部类:

    public static class MyViewHolder extends RecyclerView.ViewHolder{                TextView tv;        public MyViewHolder(View itemView) {                        super(itemView);            tv = (TextView) itemView.findViewById(R.id.tv);        }     }  

    3.2 Adapter

    Adapter扮演着两个角色。一是,根据不同ViewType创建与之相应的的Item-Layout,二是,访问数据集合并将数据绑定到正确的View上。这就需要我们重写以下3个方法:

    public VH onCreateViewHolder(ViewGroup parent, int viewType) 创建Item视图,并返回相应的ViewHolderpublic void onBindViewHolder(VH holder, int position) 绑定数据到正确的Item视图上。public int getItemCount() 返回该Adapter所持有的item数量

    示例代码如下所示:

    @Override     public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {         MyViewHolder holder = new MyViewHolder(LayoutInflater.from(context)                 .inflate(R.layout.item_recycler_view,parent,false));                         return holder;     }         @Override     public void onBindViewHolder(MyViewHolder holder, int position) {         holder.tv.setText(mDatas.get(position));     }         @Override     public int getItemCount() {                 return mDatas.size();      }  

    因此,一个基本的RecyclerView.Adapter如下:

    public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHolder> {         private Context context;         private List<String> mDatas;      public RecyclerAdapter(Context context, List<String> mDatas) {                 this.context = context;                 this.mDatas = mDatas;     }         @Override     public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {                 MyViewHolder holder = new MyViewHolder(LayoutInflater.from(context)                 .inflate(R.layout.item_recycler_view, parent, false));                 return holder;     }         @Override     public void onBindViewHolder(MyViewHolder holder, int position) {         holder.tv.setText(mDatas.get(position));     }         @Override     public int getItemCount() {                 return mDatas == null ? 0 : mDatas.size();     }      public static class MyViewHolder extends RecyclerView.ViewHolder {                 TextView tv;          public MyViewHolder(View itemView) {                         super(itemView);             tv = (TextView) itemView.findViewById(R.id.tv);         }     }   

    3.3 RecyclerView.LayoutManager

    LayoutManager的职责是摆放Item的位置,并且负责决定何时回收和重用Item。它有一个默认的实现:LinearLayoutManager,它可以用于垂直和水平列表。

    RecyclerView.LayoutManager是一个抽象类,RecyclerView为我们提供3个实现类:

    LinearLayoutManager 现行管理器,支持横向、纵向。GridLayoutManager 网格布局管理器StaggeredGridLayoutManager 瀑布流式布局管理器

    3.3.1 LinearlayoutManager

    LinearlayoutManager是LayoutManager的默认实现。你可以使用这个类来创建垂直或水平列表。

    // 设置RecyclerView布局方式为纵向布局 LinearLayoutManager layoutManager = new LinearLayoutManager(context); layoutManager.setOrientation(LinearLayoutManager.VERTICAL);  recyclerView.setLayoutManager(layoutManager);   // 设置RecyclerView布局方式为横向布局LinearLayoutManager layoutManager= new LinearLayoutManager(this);layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);recyclerView.setLayoutManager(layoutManager); 

    运行程序,我们看到如下效果: 

    我们发现和ListView有一些不同,没有分割线让这个列表看起来很不美观,给RecyclerView设置分割线这个我们在下一小节说明。

    当然LinearlayoutManager中还有一些很实用的API:

    findFirstVisibleItemPosition() 返回当前第一个可见Item的positionfindFirstCompletelyVisibleItemPosition() 返回当前第一个完全可见Item的positionfindLastVisibleItemPosition() 返回当前最后一个可见Item的positionfindLastCompletelyVisibleItemPosition() 返回当前最后一个完全可见Item的position

    3.3.2 GridLayoutManager

    有两个构造方法:

    /** * Creates a vertical GridLayoutManager * @param context Current context, will be used to access resources. * @param spanCount 设置行数,因为默认是纵向的 */public GridLayoutManager(Context context, int spanCount) {         super(context);     setSpanCount(spanCount); }/** * @param context Current context, will be used to access resources. * @param spanCount 设置行数或者列数,根据方向 * @param orientation 布局方向 HORIZONTAL or VERTICAL. * @param reverseLayout 是否反向显示 When set to true, layouts from end to start. */public GridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {         super(context, orientation, reverseLayout);     setSpanCount(spanCount);   

    我们按如下代码设置:

    // 设置纵向的4列的表格布局GridLayoutManager layoutManager = new GridLayoutManager(this,4,GridLayoutManager.VERTICAL,false); recyclerView.setLayoutManager(layoutManager); // 设置横向的3行的表格布局GridLayoutManager layoutManager = new GridLayoutManager(this,3,GridLayoutManager.HORIZONTAL,false);  recyclerView.setLayoutManager(layoutManager);   

    3.3.3 StaggeredGridLayoutManager

    我们来看StaggeredGridLayoutManager的构造方法,只有一个方法

    /** * Creates a StaggeredGridLayoutManager with given parameters. * @param spanCount   设置行数或者列数,根据方向,纵向就是列数,横向就是行数 * @param orientation 方向 */   public StaggeredGridLayoutManager(int spanCount, int orientation) {}  

    因此我们这样设置:

    // 设置纵向的瀑布流布局 StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL);  recyclerView.setLayoutManager(layoutManager);  

    我们看效果和GridLayoutManager没有什么区别呢,那我们来小小的修改一下,你就可以看到它的强大。

    我们给每个item的布局加入margin:

    <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:orientation="vertical" android:layout_width="match_parent"     android:layout_height="wrap_content"     android:layout_margin="5dp"     android:background="@color/colorAccent"     <TextView         android:id="@+id/tv"         android:layout_width="100dp"         android:layout_height="100dp"         android:layout_gravity="center"         android:gravity="center"         android:textColor="#fff"          /></LinearLayout>  

    然后我们在适配器的onBindViewHolder方法中为我们的item设置个随机的高度:

    //贴上部分代码,其余的请看附件      private List<Integer> mHeights;         public RecyclerAdapter(Context context, List<String> mDatas) {             this.context = context;             this.mDatas = mDatas;         mHeights = new ArrayList<Integer>();                     for (int i = 0; i < mDatas.size(); i++) {             mHeights.add((int) (100 + Math.random() * 300));         }     }      @Override         public void onBindViewHolder(MyViewHolder holder,          final int position) {         ViewGroup.LayoutParams lp = holder.tv.getLayoutParams();         lp.height = mHeights.get(position);         holder.tv.setLayoutParams(lp);      }  

    运行,我们可以看到效果,是不是很炫!

    3.4 RecyclerView.ItemDecoration

    通过设置

    recyclerView.addItemDecoration(new DividerDecoration(Context context, int orientation)); 

    来改变Item之间的偏移量或者对Item进行装饰。

    例如,在上面的设置LinearlayoutManager之后加入以下代码,那么列表的效果就会发生改变:

    // 给纵向显示RecyclerView设置分割线 recyclerView.addItemDecoration(new DividerItemDecoration(activity,DividerItemDecoration.VERTICAL)); // 给横向显示RecyclerView设置分割线  recyclerView.addItemDecoration(new DividerItemDecoration(activity,DividerItemDecoration.HORIZONTAL));  

    当然,你也可以对RecyclerView设置多个ItemDecoration,列表展示的时候会遍历所有的ItemDecoration并调用里面的绘制方法,对Item进行装饰。

    RecyclerView.ItemDecoration是一个抽象类,可以通过重写以下三个方法,来实现Item之间的偏移量或者装饰效果:

    public void onDraw(Canvas c, RecyclerView parent) 装饰的绘制在Item条目绘制之前调用,所以这有可能被Item的内容所遮挡public void onDrawOver(Canvas c, RecyclerView parent) 装饰的绘制在Item条目绘制之后调用,因此装饰将浮于Item之上public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) 与padding或margin类似,LayoutManager在测量阶段会调用该方法,计算出每一个Item的正确尺寸并设置偏移量。

    当然了,如果我们使用GridLayoutManager后,对于分割线,前面的DividerItemDecoration就不适用了,主要是因为它在绘制的时候,比如水平线,针对每个child的取值为:

    final int left = parent.getPaddingLeft(); final int right = parent.getWidth() - parent.getPaddingRight(); 

    因为每个Item一行,这样是没问题的。而GridLayoutManager时,一行有多个childItem,这样就多次绘制了,并且GridLayoutManager时,Item如果为最后一列(则右边无间隔线)或者为最后一行(底部无分割线)。

    按照上述的方法,我们可以自定义一个DividerGridItemDecoration。由于代码篇幅过长,我们在附件中给出。我们给GridLayoutManager设置DividerGridItemDecoration,运行效果如下:

    3.5 RecyclerView.ItemAnimator

    ItemAnimator能够帮助Item实现独立的动画。

    ItemAnimator作触发于以下三种事件:

    某条数据被插入到数据集合中从数据集合中移除某条数据更改数据集合中的某条数据

    幸运的是,在Android中默认实现了一个DefaultItemAnimator,我们可以通过以下代码为Item增加动画效果:

    recyclerView.setItemAnimator(new DefaultItemAnimator()); 

    在之前的版本中,当时据集合发生改变时,我们通过调用.notifyDataSetChanged(),来刷新列表,因为这样做会触发列表的重绘,所以并不会出现任何动画效果,因此需要调用一些以notifyItem*()作为前缀的特殊方法,比如:

    public final void notifyItemInserted(int position) 向指定位置插入Itempublic final void notifyItemRemoved(int position) 移除指定位置Itempublic final void notifyItemChanged(int position) 更新指定位置Item

    下面我们使用DefaultItemAnimator来展示一下动画效果,修改代码如下:

    在Activity中添加两个按钮add和remove负责动态插入和移除item

    btnAdd.setOnClickListener(new View.OnClickListener() {                         @Override             public void onClick(View v) {                 adapter.addData(1);             }         });  btnRemove.setOnClickListener(new View.OnClickListener() {                         @Override             public void onClick(View v) {                 adapter.removeData(2);             }          });  

    在Adapter中添加两个方法:

    public void addData(int position) {         mDatas.add(position, "Insert");         mHeights.add( (int) (100 + Math.random() * 300));         notifyItemInserted(position); }public void removeData(int position) {         mDatas.remove(position);         notifyItemRemoved(position);   

    运行结果如下: 

    3.6 Listeners

    很遗憾,RecyclerView并没有像ListView那样提供以下两个Item的点击监听事件

    public void setOnItemClickListener(@Nullable OnItemClickListener listener) Item点击事件监听public void setOnItemLongClickListener(OnItemLongClickListener listener) Item长按事件监听

    但是这并不能阻拦我们的脚步,我可以定义这样的两个方法。

    public interface OnItemClickLitener {         void onItemClick(View viewint position);         void onItemLongClick(View viewint position);     private OnItemClickLitener litener;     public void setLitener(OnItemClickLitener litener) {             this.litener = litener;   

    在onBindViewHolder方法中给需要响应点击事件的控件设置监听器:

    // 如果设置了回调,则响应点击事件holder.itemView.setOnClickListener(new View.OnClickListener() {         @Override     public void onClick(View v) {                 if (litener != null) {             litener.onItemClick(v, position);         }     } });  holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {         @Override     public boolean onLongClick(View v) {                 if (litener != null) {             litener.onItemLongClick(v, position);         }        return false    }  });  

    在Activity中设置这个方法:

    adapter.setLitener(new RecyclerAdapter.OnItemClickLitener() {     @Override         public void onItemClick(View viewint position) {         Toast.makeText(activity,"第"+position+"项被点击了",Toast.LENGTH_SHORT).show();     }      @Override         public void onItemLongClick(View viewint position) {         Toast.makeText(activity,"第"+position+"项被长按了",Toast.LENGTH_SHORT).show();     }  });  

    效果如图:

    作者:shijiacheng 来源:51CTO

    最新回复(0)