说到RecyclerView,相信你们都不陌生,它是咱们经典级ListView的升级版,升级后的RecyclerView展示了极大的灵活性。同时内部直接封装了ViewHolder,不用咱们本身定义ViewHolder就能实现item的回收和复用功能。固然它确定不止这些好处,好比咱们能够自定义分割线,能够更加方便的实现列表的布局方式等等。虽然说咱们本身在第一次使用时,会比使用listView和gridView稍微的复杂一些,须要自定义的也多了一点,可是它却更好的体现了灵活性,能够随本身的喜爱来随便的定义,固然最主要的是能更好的复用,只需一次的定义,却可随处的复用。python
下面,咱们来好好的学习下它的使用。android
首先,咱们要是用RecyclerView必须引入support-V7包,拿android studio来举例:微信
先打开File->选择Project Structure,以后在左边Modules选择你的项目,而后在点击右边的Dependencies,而后点击绿色的+号选择添加Library,而后找到recyclerview-v7双击加入到依赖库中。而后能够在build.gradle中查看:app
dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.3.0' compile 'com.android.support:recyclerview-v7:23.3.0' }
能够找dependencies 中找到对recyclerview的支持,则说明添加成功,若是尚未的,能够clean下本身的工程。ide
接下来,咱们就进入Recyclerview的学习。RecyclerView的学习目标就是如下四个方法,把如下四个方法彻底的掌握了,也就真正的掌握了RecyclerView。布局
RecyclerView.setAdapter:用来设置adapter,显示数据
RecyclerView.setLayoutManager :用来设置显示布局的,目前系统给出三种布局,分别是垂直,水平和瀑布流式布局
RecyclerView.setItemAnimator :用来设置显示动画的
RecyclerView.addItemDecoration :用来设置列表分割线的学习
接下来咱们就学习怎么使用以上四个方法来真正掌握Recyclerview的使用。要使用Recyclerview,咱们必须先定义一个类(CustomRecyclerAdapter)并继承Recyclerview.Adapter,且实现它里面的方法,代码以下:gradle
public class CustomRecyclerAdapter extends RecyclerView.Adapter<CustomRecyclerAdapter.ViewHolderHelper>{ @Override public ViewHolderHelper onCreateViewHolder(ViewGroup parent, int viewType) { return null; } @Override public void onBindViewHolder(ViewHolderHelper holder, int position) { } @Override public int getItemCount() { return 0; } public class ViewHolderHelper extends RecyclerView.ViewHolder{ public ViewHolderHelper(View itemView) { super(itemView); } } }
在咱们还没正式开始使用以前,先大致上了解下上面三个方法是作什么的:动画
A. onCreateViewHolder()方法:该方法就是将布局文件转化为View并传递给RecyclerView封装好的ViewHolder。ui
B. onBindViewHolder()方法:该方法将会在固定的位置上把ViewHolder里的itemView数据映射在item中。
C. getItemCount()方法:该方法和listView中的getCount()同样,都是返回Item的个数。
了解了这三个方法,咱们来先实现最简单的应用,把咱们的数据显示在app中。
首先,咱们建立一个布局文件recycler_view.xml,以下
<?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="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.v7.widget.RecyclerView> </LinearLayout>
建立RecycerActivity用来加载布局文件:
public class RecycerActivity extends Activity { private RecyclerView mRecyclerView; private List<String> mData; private CustomRecyclerAdapter mCustomRecyclerAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.recycer_view); mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); initData(); //线性布局管理器 recyclerViewLayoutManager = new LinearLayoutManager(this); //设置布局管理器 mRecyclerView.setLayoutManager(recyclerViewLayoutManager); //设置显示动画 mRecyclerView.setItemAnimator(new DefaultItemAnimator()); //设置adapter mCustomRecyclerAdapter = new CustomRecyclerAdapter(mData); mRecyclerView.setAdapter(mCustomRecyclerAdapter); } private void initData() { mData = new ArrayList<String>(); for(int i = 0; i < 10; i++){ mData.add("第"+i+"item"); } } }
通过修改的CustomRecyclerAdapter 以下,
public class CustomRecyclerAdapter extends RecyclerView.Adapter<CustomRecyclerAdapter.ViewHolderHelper>{ private List<String> mData; public CustomRecyclerAdapter(List<String> data) { mData = data; } @Override public ViewHolderHelper onCreateViewHolder(ViewGroup parent, int viewType) { //onCreateViewHolder方法就是将布局文件转化为View并传递给RecyclerView封装好的ViewHolder View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view, parent, false); return new ViewHolderHelper(view); } @Override public void onBindViewHolder(ViewHolderHelper holder, int position) { holder.textView.setText(mData.get(position)); } @Override public int getItemCount() { return mData.size(); } }
item_view.xml布局文件
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_item" android:layout_toRightOf="@+id/iv_item" android:layout_width="match_parent" android:layout_height="30dp" android:text="I am item view"/> </RelativeLayout>
好,基础工做已作足,咱们先来看看效果吧
已经显示出来了,不过,你们看着是否是很别扭呢,连个分割线也没有,还不如listView呢,别急,咱们在上面也提到过,RecyclerView给了咱们最大的发挥自由度,它自己并无给定列表的分割线,这是须要咱们本身定义的。由此咱们来定义本身的分割线。自定义分割线是须要咱们继承RecyclerView.ItemDecoration类,并实现它的onDraw()方法。请看代码:
public class DividerItemDecoration extends RecyclerView.ItemDecoration{ public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; private int mOrientation; private Context mContext; private TextPaint mTextPaint; private float listDividerSize = 2; private int listDividerColor; public DividerItemDecoration(Context context,int orientation){ mContext = context; mTextPaint = new TextPaint(); mTextPaint.setColor(Color.RED); setOrientation(orientation); } public void setOrientation(int orientation) { if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { throw new IllegalArgumentException("invalid orientation"); } mOrientation = orientation; } @Override public void onDraw(Canvas c, RecyclerView parent) { super.onDraw(c, parent); if(mOrientation == HORIZONTAL_LIST){ drawHorizontal(c, parent); }else{ drawVertical(c, parent); } } /** * 绘制垂直分割线 * @param c * @param parent */ private void drawVertical(Canvas c, RecyclerView parent) { //分割线的左边界 = 子View的左padding值 int rectLeft = parent.getPaddingLeft(); //分割线的右边界 = 子View的宽度 - 子View的右padding值 int rectRight = parent.getWidth() - parent.getPaddingRight(); int childCount = parent.getChildCount(); for(int i = 0; i < childCount; i ++){ View child = parent.getChildAt(i); RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); // 分割线的top = 子View的底部 + 子View的margin值 int rectTop = child.getBottom() + layoutParams.bottomMargin; // 分割线的bottom = 分割线的top + 分割线的高度 float rectBottom = rectTop + listDividerSize; c.drawRect(rectLeft,rectTop,rectRight,rectBottom,mTextPaint); } } /** * 绘制水平分割线 * @param c * @param parent */ private void drawHorizontal(Canvas c, RecyclerView parent) { //分割线的上边界 = 子View的上padding值 int rectTop = parent.getPaddingTop(); //分割线的下边界 = 子View的高度 - 子View的底部padding值 int rectBottom = 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(); //分割线的Left = 子View的右边界 + 子View的左margin值 int rectLeft = child.getRight() + layoutParams.rightMargin; //分割线的right = 分割线的Left + 分割线的宽度 float rectRight = rectLeft + listDividerSize; c.drawRect(rectLeft,rectTop,rectRight,rectBottom,mTextPaint); } } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); if(mOrientation == VERTICAL_LIST){ outRect.set(0,0,0,(int)listDividerSize); } else{ outRect.set(0,0,(int)listDividerSize,0); } } }
代码很好理解,这里考虑了两个状况,分别是垂直和水平的布局,而后再ondraw()里面计算出四角边值,最后直接绘制一个矩形便可。
在RecycerActivity 中的onCreate中添加上一句
mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL_LIST));
如今再来看看效果。
如今已显示出来了,分割线也出来了,在这里只列出了垂直方向的布局,就再也不列出其余样式的布局代码了,伙伴们可自行写写看。
或许有经验的小伙伴们已经知道咱们的RecyclerView本身是没有实现点击事件的,这里须要咱们来根据业务的需求本身来实现。这里咱们利用事件回调机制来完成事件的触发。
首先咱们须要在CustomRecyclerAdapter中定义一个接口,并在其中定义两个可用的事件方法,以下:
public interface OnItemClickListener{ void onItemClickListener(); void onLongItemClickListener(); }
这里提供了用于点击和长按的事件方法,接下来咱们须要对外暴露该接口用于被调用
public void setOnClickItemListener(OnItemClickListener onItemClickListener){ mOnItemClickListener = onItemClickListener; }
而后咱们能够在ViewHolderHelper 作以下的修改:
public class ViewHolderHelper extends RecyclerView.ViewHolder{ private TextView textView; public ViewHolderHelper(View itemView) { super(itemView); textView = (TextView)itemView.findViewById(R.id.tv_item); textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mOnItemClickListener.onItemClickListener(); } }); textView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { mOnItemClickListener.onLongItemClickListener(); return false; } }); } }
首先获得咱们itemView中的textView对应的Id,而后为textView添加事件,可是只是添加却并不实现,它的用意是谁调用谁实现。
最后在咱们的RecycerActivity中添加以下代码,请看:
mCustomRecyclerAdapter.setOnClickItemListener(new CustomRecyclerAdapter.OnItemClickListener() { @Override public void onItemClickListener() { Toast.makeText(getContext(),"单击",Toast.LENGTH_SHORT).show(); } @Override public void onLongItemClickListener() { Toast.makeText(getContext(),"长单击",Toast.LENGTH_SHORT).show(); } });
下面在来看看是否能够实现事件的触发了呢?
ok,学到这里你们至少对RecylerView有了一个初步的认识,可是咱们一想,这样写的话确定达不到咱们标题所说的无限复用,甚至连复用也高不可攀,是的,这样写是不可能完成复用的,接下来咱们一步一步的慢慢调整,让它能够支持一次编写N次复用,达到极大多数的重复使用,即便不符合需求,咱们也只须要修改丁点便可知足需求,这是咱们的目标,接下来一步一步的实现。
首先,咱们先整理下,看看能够调整哪些目标能逐步的实现重复使用的目标:
1. 数据类型:咱们在使用List集合时,是没法固定类型的,有多是String,int等等类型,因此咱们不该该固定为哪种类型。
2. 在onCreateViewHolder方法中,它须要映射一个布局文件并转化为View或是一个自定义View传递给RecyclerView封装好的ViewHolder,为了能够达到复用,因此咱们就不能够在此直接映射布局文件。
3. 在onBindViewHolder方法中也不该该直接为itemView设置属性,如上面的:holder.textView.setText(mData.get(position));
4. 咱们不该该在ViewHolder的构造方法中直接获取咱们的itemView,并给它添加触发事件
以上几个是咱们能很直观的获得的能重构的问题所在,至于其余的不容易想到的咱们再重构的时候慢慢讲解。如今咱们逐一的解决上面的问题,使咱们能更达到重复使用的目的。
1,针对数据类型的不一致,咱们能够根据具体的使用场景利用泛型进行传递到Adapter中,好比:咱们再定义CustomRecyclerAdapter时使用泛型,让调用者传递过来它所拥有的类型,这样咱们就能够不用考虑类型的不一致了。请看下面片断代码
public class CustomRecyclerAdapter<T> extends RecyclerView.Adapter<CustomViewHolderHelper>{ private Context mContext; private List<T> mData; private CustomOnItemClickListener mOnItemClickListener; public CustomRecyclerAdapter(Context context, List<T> data) { mContext = context; mData = data; } } ...
RecycerActivity中在调用时能够这样使用:
mCustomRecyclerAdapter = new CustomRecyclerAdapter
这样就能够把类型给肯定下来了,同时也解决了问题1的复用。
2 , 在onCreateViewHolder方法中,和问题一的解决方案是同样的,咱们把须要的itemView给传递过去而不是固定写死在方法中
public class CustomRecyclerAdapter<T> extends RecyclerView.Adapter<CustomViewHolderHelper>{ private Context mContext; private List<T> mData; protected int mLayoutResId; private CustomOnItemClickListener mOnItemClickListener; public CustomRecyclerAdapter(Context context, int layoutResId, List<T> data) { mContext = context; mLayoutResId = layoutResId; mData = data; } /** * Called when RecyclerView needs a new ViewHolder of the given type to represent * an item. * 当 RecyclerView 依据给出的类型须要一个新的 ViewHolder 去展现一个 item 时,该方法将会被调用 * * 这个给出的类型是在 getItemViewType返回的,默认返回 0 。 * * @param parent * @param viewType * @return */ @Override public CustomViewHolderHelper onCreateViewHolder(ViewGroup parent, int viewType) { //onCreateViewHolder方法就是将布局文件转化为View并传递给RecyclerView封装好的ViewHolder View view = LayoutInflater.from(parent.getContext()).inflate(mLayoutResId, parent, false); return new CustomViewHolderHelper(view); } }
RecycerActivity中在调用时能够这样使用:
mCustomRecyclerAdapter = new CustomRecyclerAdapter
3 , 在onBindViewHolder方法中也不该该直接为itemView设置属性,咱们能够在这里记录itemView的position,并给它设置监听事件,更重要的咱们这这里能够建立一个抽象方法,让调用者本身去实现业务逻辑。请看代码:
public abstract class CustomRecyclerAdapter<T> extends RecyclerView.Adapter<CustomViewHolderHelper> implements View.OnClickListener,View.OnLongClickListener{ private Context mContext; private List<T> mData; protected int mLayoutResId; private CustomOnItemClickListener mOnItemClickListener; public CustomRecyclerAdapter(Context context, int layoutResId, List<T> data) { mContext = context; mLayoutResId = layoutResId; mData = data; } /** * Called when RecyclerView needs a new ViewHolder of the given type to represent * an item. * 当 RecyclerView 依据给出的类型须要一个新的 ViewHolder 去展现一个 item 时,该方法将会被调用 * * 这个给出的类型是在 getItemViewType返回的,默认返回 0 。 * * @param parent * @param viewType * @return */ @Override public CustomViewHolderHelper onCreateViewHolder(ViewGroup parent, int viewType) { //onCreateViewHolder方法就是将布局文件转化为View并传递给RecyclerView封装好的ViewHolder View view = LayoutInflater.from(parent.getContext()).inflate(mLayoutResId, parent, false); return new CustomViewHolderHelper(view); } /** * Called by RecyclerView to display the data at the specified position. This method should * update the contents of the ViewHolder#itemView to reflect the item at the given position. * * RecyclerView 将要在特殊的位置上显示数据时,该方法将被调用。该方法将会在固定的位置上 * 把ViewHolder里的itemView数据映射在item中。 * @param holder * @param position */ @Override public void onBindViewHolder(CustomViewHolderHelper holder, int position) { //把每个itemView设置一个标签,方便之后根据标签获取到该itemView以便作其余事项,好比点击事件 holder.itemView.setTag(position); holder.itemView.setOnClickListener(this); holder.itemView.setOnLongClickListener(this); T itemData = mData.get(position); displayContents(holder,itemData); } /** *用来在holder中设置每一个ItemView的显示数据 * 设定为抽象方法,是为:本身自己并不实现,谁使用谁设置 * @param holder * @param itemData */ protected abstract void displayContents(CustomViewHolderHelper holder, T itemData); }
上面注解也写的很清楚,相信你们一看就明白,至于为何能够直接使用holder.itemView来获取每一个itemView是由于在onCreateViewHolder()方法中,咱们返回了一个新的对象引用,这个对象的构造方法中使用super(itemView);把咱们的itemView传递到了ViewHolder中,请看它的源码构造方法:
public ViewHolder(View itemView) { if (itemView == null) { throw new IllegalArgumentException("itemView may not be null"); } this.itemView = itemView; }
所以咱们能够在onBindViewHolder()方法中,直接使用holder.itemView来获取itemView。那么接下来,在RecycerActivity中,咱们这样使用:
mCustomRecyclerAdapter = new CustomRecyclerAdapter<String>(this, R.layout.item_view, mData) { @Override protected void displayContents(CustomViewHolderHelper holder, String itemData) { holder.setText(R.id.tv_item,itemData); } };
固然你必须也得在ViewHolder中定义相应的方法,如:
public class CustomViewHolderHelper extends RecyclerView.ViewHolder{ private SparseArray<View> views; public CustomViewHolderHelper(View itemView) { super(itemView); views = new SparseArray<View>(); } private <T extends View> T converToViewFromId(int resId) { View view = views.get(resId); if(view == null){ view = itemView.findViewById(resId); } views.put(resId,view); return (T)view; } public CustomViewHolderHelper setText(int resId, String value){ TextView itemView = converToViewFromId(resId); if (TextUtils.isEmpty(value)) { itemView.setText(""); } else { itemView.setText(value); } return this; } }
ok,这样咱们连第四个问题也一并解决了,看下效果吧,彻底的同样,这样咱们就实现的重复使用,可是有人会有疑问,这里也就只能使用TextView啊,其实已经在ViewHolder中给出了答案,你们只须要在添加相对应的方法便可,好比
public CustomViewHolderHelper setImageResource(int viewId, int imageResId) { ImageView view = converToViewFromId(viewId); view.setImageResource(imageResId); return this; } public CustomViewHolderHelper setOnClickListener(int viewId, View.OnClickListener listener) { View view = converToViewFromId(viewId); view.setOnClickListener(listener); return this; }
上面我又添加了两个方法,用于点击事件和加载图片的ImageView,下面咱们再把itemView布局文件修改下:
item_view.xml布局文件:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_item" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_item" android:layout_toRightOf="@+id/iv_item" android:layout_width="match_parent" android:layout_height="30dp" android:text="I am item view"/> <Button android:id="@+id/btn_item" android:layout_toRightOf="@+id/iv_item" android:layout_below="@+id/tv_item" android:layout_width="wrap_content" android:text="点我" android:layout_height="wrap_content" /> </RelativeLayout>
RecycerActivity中在调用时能够这样使用:
mCustomRecyclerAdapter = new CustomRecyclerAdapter<String>(this, R.layout.item_view, mData) { @Override protected void displayContents(CustomViewHolderHelper holder, String itemData) { holder.setText(R.id.tv_item,itemData) .setImageResource(R.id.iv_item,R.mipmap.ic_launcher) .setOnClickListener(R.id.btn_item, new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(RecycerActivity.this,"您单击了按钮",Toast.LENGTH_SHORT).show(); } }); } };
ok,来看下效果吧
是否是能够了呢,这样咱们就能够彻底的一次定义N次复用了,每次使用只须要更换不一样的布局文件便可而不须要再次编写代码,学会了吧。
其实咱们这节课主要讲解的RecyclerView.setAdapter的内容,其余的三个咱们并无详细的介入,咱们会再之后的博文中陆续的讲解。
好了,今天就讲到这里吧,祝你们学习愉快。
更多资讯请关注微信平台,有博客更新会及时通知。爱学习爱技术。