RecyclerView和ListView一样,都是做列表显示View子项的控件,它比ListView更高效和自由。
解析RecycleView,Recycle View,意思就是该控件只管回收和显示View子项,而对于如何显示,显示什么,它是不关心的,这给开发过程带来了极大的便利,比如ListView只能作为单列的列表显示,GridView将一个界面表格化,通常情况下GridView通过强制View子项的宽度来显示,在横竖屏切换时的效果很差。
而RecycleView可以实现:
ListView的功能
GridView的功能
横向ListView的功能
横向ScrollView的功能
瀑布流的功能
添加和删除View子项
这些功能,非常强大,可以看出,它几乎可以替代所有的动态布局控件。
这么强大的动态布局控件,得益于它的高度解耦,同样,众所周知,高度解耦,就意味着复杂度提升,相较于ListView、GridView等控件,RecycleView才实现过程是相对较复杂的。
RecyclerView的适配器需要继承自RecyclerView.Adapter,在该适配器将要面向ViewHolder,也就是说,它内部已经实现了缓存复用。
实现LIstView功能
注意,RecyclerView是谷歌在support-v7包中添加的新组件,在开发的时候,请添加support.v7包以及recyclerview-v7包,在module的Project Structure中的Dependencies部分添加如下图所示:
首先创建RecyclerViewDemo1,活动:RecyclerViewDemo.java,layout:activiy_main.xml。
简单通过RecyclerView实现ListView的功能,那么首先就要理解ListView的实现过程。ListView是通过列表的的形式依次显示一行行的数据,从编程的角度出发,就是一个LinnerLayout布局,方向为纵向,依次添加进数据,后通过Scrolling实现滚动屏幕。
再来对比RecyclerView,屏幕滚动部分,在RecyclerView中已经被实现好了,不需要关心,需要关心的就是布局,以及方向。
Java Code
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
//设置RecyclerView的布局管理器,为了模仿ListView,
//这里设置为Vertical的LinnerLayout
LinearLayoutManager manager = new LinearLayoutManager(this);
manager.setOrientation(OrientationHelper.VERTICAL);
recyclerView.setLayoutManager(manager);
上述代码第5行,定义了一个LinearLayoutManager,传入上下文为活动的this,第6行将布局管理器的方向设置为VERTICAL,第7行,将这个布局管理器传递给RecyclerView。
对比ListView,这个时候我们只是拥有了一个ListView实例,数据源,适配器,以及View子项都还没有说明。
接下来,创建一个View子项,这个View子项可以显示一张图片,以及一段文字,可以使用一个TextView,当然为了自由度,将声明两个控件,一个TextView、一个ImageView,分别用来显示文字和图片。
子View还是通过xml文件来定义。
XML Code
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="//schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg">
<ImageView
android:id="@+id/img_item"
android:layout_width="50dp"
android:layout_height="50dp" />
<TextView
android:id="@+id/txv_item"
android:gravity="center_vertical|center_horizontal"
android:textSize="21sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
上述代码第7行和第11行,分别定义了一个ImageView和一个TextView,第4行,将打下限定在了wrap_content,View子项的大小可控。
四大组成部分中,已经实现了两部分,接下来是数据源,因为是一个Demo,这里数据源我们使用一个包含一个String和一个图片Id的HashMap来实现。
Java Code
public HashMap
{
HashMap
imgHash.put("0000", R.drawable.zero);
imgHash.put("1111", R.drawable.one);
imgHash.put("2222", R.drawable.two);
imgHash.put("3333", R.drawable.three);
imgHash.put("4444", R.drawable.four);
imgHash.put("5555", R.drawable.five);
imgHash.put("6666", R.drawable.six);
imgHash.put("7777", R.drawable.seven);
imgHash.put("8888", R.drawable.eight);
imgHash.put("9999", R.drawable.nine);
imgHash.put("aaaa", R.drawable.a);
imgHash.put("bbbb", R.drawable.b);
imgHash.put("cccc", R.drawable.c);
imgHash.put("dddd", R.drawable.d);
imgHash.put("eeee", R.drawable.e);
imgHash.put("ffff", R.drawable.f);
return imgHash;
}
RecyclerView实例有了,数据源有了,View子项有了,只剩下适配器将这些东西串联起来了。
RecyclerView的Adapter,通常使用的是自定义的,这是由RecyclerView高度解耦,高度自由决定的,高度解耦意味着编码复杂度上升。
在创建Adapter的时候,继承自RecyclerView.Adapter,不过在这之前,请先建立一个自定义的ViewHolder类,这是因为,在RecyclerView的编码过程中,缓存部分的工作已经交由RecyclerView做好了,那么缓存什么,View子项是什么,需要程序员告知它。
Java Code
//自定义的ViewHolder
class MyViewHolder extends RecyclerView.ViewHolder{
public ImageView img;
public TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
img = (ImageView) itemView.findViewById(R.id.img_item);
textView = (TextView) itemView.findViewById(R.id.txv_item);
}
}
编写自定义适配器。
Java Code
public class MyAdapter extends RecyclerView.Adapter
private Context context;
private List
private List
private LayoutInflater inflater;
private OnItemClickListener onItemClickListener;
//自定义构造器,传入上下文和数据源
public MyAdapter(Context context, HashMap
this.context = context;
imgList = new ArrayList<>();
nameList = new ArrayList<>();
for (Object o : imgHash.entrySet()){
Map.Entry entry = (Map.Entry) o;
String name = (String) entry.getKey();
int img = (int) entry.getValue();
nameList.add(name);
imgList.add(nameList.indexOf(name), img);
}
inflater = LayoutInflater.from(context);
}
//面向ViewHolder编程
@Override
public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
//View子项
View view = inflater.inflate(R.layout.item_recycler, viewGroup, false);
return new MyViewHolder(view);
}
//面向ViewHolder编程
@Override
public void onBindViewHolder(MyViewHolder myViewHolder,
@SuppressLint("RecyclerView") final int i) {
//对View子项赋值
myViewHolder.img.setImageResource(imgList.get(i));
myViewHolder.textView.setText(nameList.get(i));
}
@Override
public int getItemCount() {
return nameList.size();
}
//自定义的ViewHolder
class MyViewHolder extends RecyclerView.ViewHolder{
public ImageView img;
public TextView textView;
//和View子项真正建立连接
public MyViewHolder(View itemView) {
super(itemView);
img = (ImageView) itemView.findViewById(R.id.img_item);
textView = (TextView) itemView.findViewById(R.id.txv_item);
}
}
}
四大组成部分已经编写完成,接下来,
myAdapter = new MyAdapter(this, imgHash);
recyclerView.setAdapter(myAdapter);
完成适配器配置。
来看一下现象如下图所示:
和ListView的效果很像,已经将数据完整的显示在活动中,但是仔细看,会发现,View子项和View子项之间没有明显的分隔。并没有ListView中的分隔的效果。
对于RecyclerView来说,添加分隔线也是耦合事件,所以,当想要分隔线的时候,需要自己添加。需要添加recyclerView.addItemDecoration()方法添加分隔线。该方法的参数需要一个RecycleView.ItemDecoration实例,该类是一个abstract的虚类,要实现该类需要做自定义操作,谷歌并没有提供默认的实现。
创建MyItemDecoration继承自RecyclerView.ItemDecoration,观察该类,需要重写其onDraw方法,getItemOffsets方法。
Java Code
package com.hqyj.dev.recyclerviewdemo1;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* Created by jiyangkang on 2016/7/15 0015.
*/
public class MyItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
private static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
private static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
//构造器,从参数离别中拿出要绘制的Drawable
public MyItemDecoration(Context context, int orientation) {
//参数列表
final TypedArray a = context.obtainStyledAttributes(ATTRS);
//参数列表0项
mDivider = a.getDrawable(0);
//使用完参数列表之后一定要做recycle()
a.recycle();
try {
setmOrientation(orientation);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
//选择绘制什么样的分隔线
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if (mOrientation == VERTICAL_LIST) {
drawVertiacl(c, parent, state);
} else {
drawHorizontal(c, parent, state);
}
}
public void setmOrientation(int orientation) throws IllegalAccessException {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalAccessException("invalid orientation");
}
mOrientation = orientation;
}
//纵向排列的View子项
public void drawVertiacl(Canvas c, RecyclerView parent, RecyclerView.State state) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
//横向排列的View子项
public void drawHorizontal(Canvas c, RecyclerView parent, RecyclerView.State state) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@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, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
自定义ItemDecoration写好厚,就可以将其添加到RecyclerView中了。
添加recyclerView.addItemDecoration(new MyItemDecoration(this, LinearLayoutManager.VERTICAL));带活动的onCreate方法中。
再看展示的效果图,如下图:
可以看到,已经有分隔线了,这个分隔线是我们在ATTRS中放入的第0个Drawable的id,我们知道它是来自android.R的,即系统集成的,使用该分隔线的一个好处就是,我们可以对其进行自定义。
在res下的drawable目录下创建一个shap,来设置一个渐变drawable.
XML Code
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="//schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:centerColor="#ff00ff00"
android:startColor="#ff0000ff"
android:endColor="#ffff0000"/>
<size android:height="2dp"/>
</shape>
然后在styles.xml中为工程的AppTheme中添加一个android:listDivider子项,为listDivider赋值为定义好的drawable。
效果图如下:
这样的效果已经和ListView很像了,但是,在点击对应的View子项的时候,会发现,是没有效果的,这是因为RecyclerView中,并没有和ListView一样的OnItemClickLisener接口。
还是因为RecyclerView高度解耦的缘故,而自定义监听将更加自由。在Adapter中添加一个接口,用以监听按钮事件。
Java Code
public interface OnItemClickListener
{
void onClick(int position, String name, int imgSource);
void onLongClick(int position, String name, int imgSource);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener)
{
this.onItemClickListener = onItemClickListener;
}
@Override
public void onBindViewHolder(MyViewHolder myViewHolder, @SuppressLint("RecyclerView") final int i)
{
myViewHolder.img.setImageResource(imgList.get(i));
myViewHolder.textView.setText(nameList.get(i));
if (onItemClickListener != null)
{
myViewHolder.itemView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
onItemClickListener.onClick(i, nameList.get(i), imgList.get(i));
}
});
myViewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener()
{
@Override
public boolean onLongClick(View view)
{
//onItemClickListener.onLongClick(i, nameList.get(i), imgList.get(i));
//removeData(i);
return false;
}
});
}
}
在调用Adapter的部分添加
Java Code
myAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener()
{
@SuppressLint("DefaultLocale")
@Override
public void onClick(int position, String name, int imgSource)
{
textView.setText(String.format("%d is clicked, %s is contained", position, name));
}
@SuppressLint("DefaultLocale")
@Override
public void onLongClick(int position, String name, int imgSource)
{
textView.setText(String.format("%d is Long clicked, %s is contained", position, name));
}
});
到此,点击事件也已经注册到活动中,点击每个View子项都已经有相应。
但是,还有一个问题,点击事件发生的时候,View子项没有任何反应,位View子项添加一个background,在Drawable里边顶一个selector。就可以实现点击事件发生时改变背景颜色的功能。
XML Code
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="//schemas.android.com/apk/res/android" >
<item android:state_pressed="true"
android:drawable="@color/colorBg_press"/>
<item android:drawable="@color/colorBg_default"/>
</selector>
至此,一个仿照的ListView就被实现出来了,而且更具灵活性,也更生动。
在灵活性上,一个很重要的体现就是,可以任意的增加和删除View子项。在上述的代码中其实已经可以看见一部分了,在长按的监听事件中,长按发生时,会将对应的View子项从列表中删除。
Java Code
public void removeData(int position){
nameList.remove(position);
imgList.remove(position);
notifyItemRemoved(position);
notifyDataSetChanged();//必须添加
}
notifyItemRemoved方法即为删除方法。同样的,还有一个为notifyItemInser(int position)的方法,用以插入一个View子项。View子项也是可以移动的,方法为notifyItemMove(int fromPosition, int toPosition)。