RecyclerView 使用指南
最近看了很多 RecyclerView 的使用文章,一直晕乎乎的,完全不知道套路是啥。很多人都是直接上代码,但是却没有详细说明代码的使用,于是打算自己写写,理理思路。顺便帮助那些正在学习 Android 的新人。
本文源码参见 https://github.com/huanshen/Learn-Android/tree/master/recycleTest
1、单个 item 的 recyclerView
首先 recycleView 需要我们引入,所以在 build.gradle ( model ) 中引入:
compile 'com.android.support:recyclerview-v7:26.0.+'
下面我们开始写布局文件:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/my_recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="vertical"/> </RelativeLayout>
这相当于在屏幕上占了个位置给 recyclerView 用,但是我们还得为其添加 item 项,item 也得有自己的布局呢,于是我们继续编写下面这个布局。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="wenzi " android:id="@+id/text"/> </LinearLayout>
布局写好了,接下去就是怎么把数据在 view 上进行显示。这个当然得用到 适配器啦, recycleView 有自己的适配器,我们只需要继承就好,然后编写具体的处理代码,具体如下,后面有详细的分析的。
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { public String[] datas = null; public MyAdapter(String[] data) { datas = data; } //创建新View,被LayoutManager所调用 @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item,viewGroup,false); ViewHolder vh = new ViewHolder(view); return vh; } //将数据与界面进行绑定的操作 @Override public void onBindViewHolder(ViewHolder viewHolder, int position) { viewHolder.mTextView.setText(datas[position]); } //获取数据的数量 @Override public int getItemCount() { return datas.length; } //自定义的ViewHolder,持有每个Item的的所有界面元素 public class ViewHolder extends RecyclerView.ViewHolder { public TextView mTextView; public ViewHolder(View view){ super(view); mTextView = (TextView) view.findViewById(R.id.text); } } }
这里我们再来细细分析下继承之后要写的东西,首先是构造函数 MyAdapter,用来传入所需要的数据。一般都是数组或者链表之类的,这样才能形成列表啊。
其次是 onCreateViewHolder, 我把它翻译成 “创建视图容器”,就是用来装 item 视图的。先获取 item 的视图,然后再把它放进容器即可。
接下去就是 onBindViewHolder,就是对容器里的 item 的每一个项进行绑定,这样我们才能将数据映射到 view 上进行显示啊。
然后就是 getItemCount 了,它其实就是返回一个数量,就是最后到底创建了几个。
最后呢,我们自定义了一个 ViewHolder ,继承于RecyclerView.ViewHolder。 这个就更好理解啦,就是我们要把 item 中的每一项都先装进ViewHolder 这个大容器里面,这样我们才能进行前面 绑定啊。这里定义了一个 mTextView, 注意它也出现在了 onBindViewHolder 中噢。
把代码好好看一遍,然后再阅读一遍上面的分析,你应该就知道怎么用了呢。
下一步,我们就要开始用它们啦,具体见代码如下:
private RecyclerView mRecyclerView; private RecyclerView.LayoutManager mLayoutManager; private RecyclerView.Adapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRecyclerView = (RecyclerView)findViewById(R.id.my_recycler_view); //创建默认的线性LayoutManager mLayoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(mLayoutManager); //如果可以确定每个item的高度是固定的,设置这个选项可以提高性能 mRecyclerView.setHasFixedSize(true); //创建并设置Adapter mAdapter = new MyAdapter(new String[]{"1231","43252345","2342342"}); mRecyclerView.setAdapter(mAdapter); }
首先我们得找到原来的 RecyclerView 这个大容器,然后我们创建一个默认的线性 Layoutmanger,并设置 RecyclerView 为线性得得。 然后我们在将数据传入到 MyAdapter 中,并创建一个它的实例,最后调用 setAdapter 即可。
2、多个 item 的 recycleView 实现
我们前面展现的是 只有一个 item,并且样式都是一样的,那我们能不能有多种不同的样式呢?答案是可以。
如上图所示,我们让它奇偶采用不同的样式。当然,我们完全还可以自由发挥实现有图和无图的。
那这个具体怎么实现的呢,还是在上一个代码基础上进行修改。
首先,我们添加一个新的样式,叫 item1.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:id="@+id/root" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="56dp" android:text="wenzi " android:background="@color/colorAccent" android:layout_margin="10dp" android:id="@+id/text"/> </LinearLayout>
然后我们开始添加实现脸多个item 的代码,主要添加在 我们自己写的适配器里面,activity 的代码不用动。
private final int ITEM = 1; private final int ITEM1 = 2;
首先我们定义了两个常量来表示 两种不同的类型。因为有两种类型,自然要定义两种不同的 viewhold 了。具体代码如下:
//自定义的ViewHolder,持有每个Item的的所有界面元素 public class ImageHolder extends RecyclerView.ViewHolder { public TextView mTextView; public ImageHolder(View view){ super(view); mTextView = (TextView) view.findViewById(R.id.text); } } public class ColorHolder extends RecyclerView.ViewHolder{ public TextView mTextView; public ColorHolder(View view){ super(view); mTextView = (TextView) view.findViewById(R.id.text); } }
同样的我们也要创建两种不同的 viewHold 容器,那我们怎样才能知道我们需要创建哪一种呢?
这时候,getItemViewType 就可以派上用场了,这个函数就是根据不同的位置,为我们确定不同的类型的。
由于我们的数据比较简单,我是用奇偶来划分的。那如果真的操作的时候,我们必须在提供数据的同时,还要提供数据的类别,这样我们才知道那种数据采用哪种 view 进行展示。
public int getItemViewType(int position) { if (position % 2 == 0){ return ITEM; } return ITEM1; }
当我们把数据类型划定好了,就可以来创建 viewHold 了。
//创建新View,被LayoutManager所调用 @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { if (viewType == ITEM) { View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false); ImageHolder vh = new ImageHolder (view); return vh; }else{ View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item1, viewGroup, false); ColorHolder vh = new ColorHolder(view); return vh; } }
上面的代码,我们根据不同的类型,来引用不同的布局。
创建好之后,就是对数据进行绑定啦。具体代码如下:
//将数据与界面进行绑定的操作 @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { if (viewHolder instanceof ImageHolder) { //Toast.makeText(MainActivity.this, datas[position], Toast.LENGTH_SHORT).show(); ((ImageHolder)viewHolder).mTextView.setText(datas[position]); viewHolder.itemView.setTag(position); }else { ((ColorHolder)viewHolder).mTextView.setText(datas[position]); viewHolder.itemView.setTag(position); } }
绑定的时候,我们要对 viewHold 的类型进行判定,只有这样我们才能正确的将数据绑定到 view 上。
好了,到这里我们就完成了呢,相信你应该能够掌握 recycleView 了。
3、点击事件的产生
我们让每个 item 点击的时候,弹出相对应的 position 。
首先,我们要实现 View.OnClickListener 接口,让 item 可以点击。
public class ImageHolder extends RecyclerView.ViewHolder implements View.OnClickListener { public TextView mTextView; public ImageHolder(View view){ super(view); mTextView = (TextView) view.findViewById(R.id.text); view.setOnClickListener(this); } @Override public void onClick(View view) { if (mOnRvItemClick != null) mOnRvItemClick.onItemClick(view, getAdapterPosition()); } }
这里,我们在 ImageHolder 实现该接口,重写了 onClick 方法。方法里面的内容后面再说。该方法的参数并没有 position ,因此需要我们在做一些处理。position 位置的获取主要是通过 getAdapterPosition 来实现的。
我们定义了一个 item 的点击接口
public interface onRecyclerViewItemClick { void onItemClick(View v, int position); }
这里我们定义了一个 onRecyclerViewItemClick 接口,其内部方法则是 onItemClick,可以看到我们有 position 参数了。
public MyAdapter(String[] data, onRecyclerViewItemClick onRvItemClick) { datas = data; mOnRvItemClick = onRvItemClick; }
然后我们在初始化的时候,将 onRecyclerViewItemClick 的实例 onRvItemClick 传给私有参数 mOnRvItemClick;然后再 onClick 中进行调用,具体见前面的所说的。
最后,我们在调用即可。
mAdapter = new MyAdapter(data, new MyAdapter.onRecyclerViewItemClick() { @Override public void onItemClick(View v, int position) { Toast.makeText(MainActivity.this, "第" + position + "行", Toast.LENGTH_SHORT).show(); } }); mRecyclerView.setAdapter(mAdapter);
4、添加分割线
recyclerView 有一个默认的分割线方法:DividerItemDecoration。
只需这样调用就可以:
mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
分割线也是分水平还是竖直的。如果我们需要自定义自己的分割线也是可以的。我们只需要 extends RecyclerView.ItemDecoration 重写其中的一些方法就可以。
1 package com.example.shenjiaqi.httpshiyong; 2 3 import android.annotation.SuppressLint; 4 import android.content.Context; 5 import android.content.res.TypedArray; 6 import android.graphics.Canvas; 7 import android.graphics.Rect; 8 import android.graphics.drawable.Drawable; 9 import android.support.annotation.NonNull; 10 import android.support.v7.widget.LinearLayoutManager; 11 import android.support.v7.widget.RecyclerView; 12 import android.view.View; 13 import android.widget.LinearLayout; 14 15 /** 16 * Created by shenjiaqi on 2017/10/22. 17 */ 18 19 public class MyDecoration extends RecyclerView.ItemDecoration { 20 /** 21 * 22 * @param outRect 边界 23 * @param view recyclerView ItemView 24 * @param parent recyclerView 25 * @param state recycler 内部数据管理 26 *//* 27 @Override 28 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 29 //设定底部边距为1px 30 *//*outRect.set(0, 0, 0, 30);*//* 31 }*/ 32 33 public static final int HORIZONTAL = LinearLayout.HORIZONTAL; 34 public static final int VERTICAL = LinearLayout.VERTICAL; 35 36 private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; 37 38 private Drawable mDivider; 39 40 /** 41 * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}. 42 */ 43 private int mOrientation; 44 45 private final Rect mBounds = new Rect(); 46 47 /** 48 * Creates a divider {@link RecyclerView.ItemDecoration} that can be used with a 49 * {@link LinearLayoutManager}. 50 * 51 * @param context Current context, it will be used to access resources. 52 * @param orientation Divider orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}. 53 */ 54 public MyDecoration(Context context, int orientation) { 55 final TypedArray a = context.obtainStyledAttributes(ATTRS); 56 mDivider = a.getDrawable(0); 57 a.recycle(); 58 setOrientation(orientation); 59 } 60 61 /** 62 * Sets the orientation for this divider. This should be called if 63 * {@link RecyclerView.LayoutManager} changes orientation. 64 * 65 * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL} 66 */ 67 public void setOrientation(int orientation) { 68 if (orientation != HORIZONTAL && orientation != VERTICAL) { 69 throw new IllegalArgumentException( 70 "Invalid orientation. It should be either HORIZONTAL or VERTICAL"); 71 } 72 mOrientation = orientation; 73 } 74 75 /** 76 * Sets the {@link Drawable} for this divider. 77 * 78 * @param drawable Drawable that should be used as a divider. 79 */ 80 public void setDrawable(@NonNull Drawable drawable) { 81 if (drawable == null) { 82 throw new IllegalArgumentException("Drawable cannot be null."); 83 } 84 mDivider = drawable; 85 } 86 87 @Override 88 public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 89 if (parent.getLayoutManager() == null) { 90 return; 91 } 92 if (mOrientation == VERTICAL) { 93 drawVertical(c, parent); 94 } else { 95 drawHorizontal(c, parent); 96 } 97 } 98 99 @SuppressLint("NewApi") 100 private void drawVertical(Canvas canvas, RecyclerView parent) { 101 canvas.save(); 102 final int left; 103 final int right; 104 if (parent.getClipToPadding()) { 105 left = parent.getPaddingLeft(); 106 right = parent.getWidth() - parent.getPaddingRight(); 107 canvas.clipRect(left, parent.getPaddingTop(), right, 108 parent.getHeight() - parent.getPaddingBottom()); 109 } else { 110 left = 0; 111 right = parent.getWidth(); 112 } 113 114 final int childCount = parent.getChildCount(); 115 for (int i = 0; i < childCount; i++) { 116 final View child = parent.getChildAt(i); 117 parent.getDecoratedBoundsWithMargins(child, mBounds); 118 final int bottom = mBounds.bottom + Math.round(child.getTranslationY()); 119 final int top = bottom - mDivider.getIntrinsicHeight(); 120 mDivider.setBounds(left, top, right, bottom); 121 mDivider.draw(canvas); 122 } 123 canvas.restore(); 124 } 125 126 @SuppressLint("NewApi") 127 private void drawHorizontal(Canvas canvas, RecyclerView parent) { 128 canvas.save(); 129 final int top; 130 final int bottom; 131 if (parent.getClipToPadding()) { 132 top = parent.getPaddingTop(); 133 bottom = parent.getHeight() - parent.getPaddingBottom(); 134 canvas.clipRect(parent.getPaddingLeft(), top, 135 parent.getWidth() - parent.getPaddingRight(), bottom); 136 } else { 137 top = 0; 138 bottom = parent.getHeight(); 139 } 140 141 final int childCount = parent.getChildCount(); 142 for (int i = 0; i < childCount; i++) { 143 final View child = parent.getChildAt(i); 144 parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds); 145 final int right = mBounds.right + Math.round(child.getTranslationX()); 146 final int left = right - mDivider.getIntrinsicWidth(); 147 mDivider.setBounds(left, top, right, bottom); 148 mDivider.draw(canvas); 149 } 150 canvas.restore(); 151 } 152 153 @Override 154 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 155 RecyclerView.State state) { 156 int childAdapterPosition = parent.getChildAdapterPosition(view); 157 158 int lastCount = parent.getAdapter().getItemCount() - 1; 159 //如果当前条目与是最后一个条目,就不设置divider padding 160 if (childAdapterPosition == lastCount) { 161 outRect.set(0, 0, 0, 0); 162 return; 163 } 164 if (mOrientation == VERTICAL) { 165 outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); 166 } else { 167 outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); 168 } 169 } 170 }
为了用我们自己的分割线
在 drawable 中建一个 divider.xml 的文件
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <gradient android:centerColor="#ff00ff00" android:endColor="#ff0000ff" android:startColor="#ffff0000" android:type="linear" /> <size android:height="4dp"/> </shape>
然后我们再在 style 文件中放一个 item ,name 就叫 android:listDivider;引入上面的文件。
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <item name="android:listDivider">@drawable/divider</item> </style> </resources>