Android基础控件—RecycleView

本文介绍了Android的RecyclerView控件,它是ListView的升级版,提供了更强大的功能和更高的灵活性。RecyclerView拥有标准化的ViewHolder,封装了viewholder的回收复用,使得编写Adapter更简单。此外,它还支持线性、网格和瀑布流等多种布局,可以通过LayoutManager轻松切换。文中详细讲解了如何使用RecyclerView,包括设置Adapter、添加Item动画、实现点击事件以及添加Item分隔线等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.RecycleView是什么?

       自Android 5.0之后,谷歌公司推出了RecyclerView控件,RecyclerView是support-v7包中的新组件,这是一个比ListView更为强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,这一点从它的名字recyclerview即回收view也可以看出,RecyclerView只关心回收与复用View,其它的可以自己设置。从这点看出其高度解耦的特点,给予你充分的定制自由(所以你才可以轻松的通过这个控件实现ListView,GirdView,瀑布流等效果)。相对于经典的ListView控件RecyclerView自然拥有ListView所不具备的优点。

2.RecyclerView的优点

       根据谷歌官方的介绍RecylerView是ListView的升级版,不仅可以轻松实现和ListView同样的效果,还优化了ListView中存在的各种不足之处。目前,Android官方更加推荐使用RecyclerView,既然如此RecyclerView在保留了ListView的种种优点之外肯定还会有其特有的一些优点。接下来就比较RecyclerView和ListView:

①RecyclerView封装了viewholder的回收复用,也就是说RecyclerView标准化了ViewHolder,编写Adapter面向的是       ViewHolder而不再是View了,复用的 逻辑被封装了,写起来更加简单。

②提供了一种插拔式的体验,高度的解耦,异常的灵活,针对一个Item的显示RecyclerView专门抽取出了相应的类,     来控制Item的显示,使其的扩展性非常强。例如:你想控制横向或者纵向滑动列表效果可以通过                                 LinearLayoutManager这个类来进行控制(与GridView效果对应的是GridLayoutManager,与瀑布流对应的还有               StaggeredGridLayoutManager等),也就是说RecyclerView不再拘泥于ListView的线性展示方式,它也可以实现           GridView的效果等多种效果。你想控制Item的分隔线,可以通过继承RecyclerView的ItemDecoration这个类,然后     针对自己的业务需求去抒写代码。

③可以控制Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecyclerView有其自己     默认的实现。

3.RecyclerView的用法

(1)线性布局

       在使用RecyclerView之前必须要导入support-v7包,在Android Studio中需要在build.gradle文件中添加相应的依赖库才行,因此需要在build.gradle文件中的dependencies{}闭包中添加compile 'com.android.support:recyclerview-v7:23.2.1',添加完成以后记得要点击一下Sync Now来进行同步,如下面的代码所示。

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:23.4.0'
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:recyclerview-v7:23.2.1'
}

       在布局文件中,入下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="5dp"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.administrator.recyclerview.MainActivity">

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

</LinearLayout>

       在布局中加入RecyclerView也是非常简单的,先给RecyclerView指定一个id,然后将宽度和高度都设置为match_parent,这样RecyclerView就占满整个布局的空间,这里需要注意的是,由于RecyclerView并不是内置在系统的SDK当中的,所以需要把完整的路径写出来。

       在开始使用RecyclerView之前跟使用ListView差不多,都需要自定义一个适配器,与ListView的适配器继承自BaseAdapter不同的是,RecyclerView的适配器需要继承自RecyclerView.Adapter,并将泛型指定为RecyclerViewAdapter.RecyclerViewHolder,其中,RecyclerViewHolder是我们在RecyclerViewAdapter中定义的一个内部类,除此之外还需要实现onCreateViewHolder(),onBindViewHolder()和getItemCount()这三个方法。

package com.example.administrator.recyclerview;

import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;

/**
 * Created by ChuPeng on 2017/3/31.
 */

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolder>
{
    private List<User> userList;
    private ItemClickListener itemClickListener;

    public RecyclerViewAdapter(List<User> userList)
    {
        super();
        this.userList = userList;
    }

    public int getItemCount()
    {
        return userList.size();
    }

    //初始化一个ViewHolder
    public RecyclerViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType)
    {
        View view = View.inflate(viewGroup.getContext(), R.layout.recyclerview_item, null);
           //创建一个ViewHolder
        RecyclerViewHolder viewHolder = new RecyclerViewHolder(view)
        {
            public String toString()
            {
                return super.toString();
            }
        };
        return viewHolder;
    }

    public void onBindViewHolder(RecyclerViewHolder holder, int position)
    {
        User user = userList.get(position);
        holder.userNmae.setText(user.getUserName());
        holder.imageName.setBackgroundResource(user.getImageView());
    }

    //自定义一个ViewHolder
    public class RecyclerViewHolder extends RecyclerView.ViewHolder
    {
        private TextView userNmae;
        private ImageView imageName;

        public RecyclerViewHolder(View itemView)
        {
            super(itemView);
            this.imageName = (ImageView) itemView.findViewById(R.id.image);
            this.userNmae = (TextView) itemView.findViewById(R.id.name);
        }
    }
}
①onCreateViewHolder()
       这个方法主要生成为每个Item inflater出一个View,但是该方法返回的是一个ViewHolder。该方法把View直接封装在ViewHolder中,然后我们面向的是ViewHolder这个实例,当然这个ViewHolder需要我们自己去编写。直接省去了当初的convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。
②onBindViewHolder()

       这个方法主要用于适配渲染数据到View中。方法提供给你了一个ViewHolder,而不是原来的convertView。

       这个方法主要用于对RecyclerView的子项数据进行赋值,会在每个子项被滚动到屏幕内的时候执行,在这个方法中会提供两个参数,一个参数是ViewHolder,这里的ViewHolder来自于onCreateViewHolder()的返回值,另一个参数是position,这里我们通过position参数得到当前项User的实例,然后再将数据设置到ViewHolder的ImageView和TextView当中即可。

③getItemCount()
       这个方法就类似于BaseAdapter的getCount方法了,它用于告诉RecyclerView一共有多少子项,直接返回数据源的长度就可以了。
       可以看到RecyclerView标准化了ViewHolder,编写 Adapter面向的是ViewHoder而不在是View了,复用的逻辑被封装了,写起来更加简单。其实它的写法与BaseAdapter的写法是差不多的,大家可以对比下它与getView方法写法的区别,在onCreateViewHolder方法中初始化了一个View,然后返回一个ViewHolder,这个返回的ViewHolder类似于之前在getView中的convertView.getTag(),然后在onBindViewHolder方法中去给这个ViewHolder中的控件填充值。其实它的原理跟getView是差不多的,只是做了封装,我们写起来比较简洁。
       在创建完RecyclerView的Adapter以后,还需要将Adapter设置到RecyclerView中去。

package com.example.administrator.recyclerview;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.LinearLayout;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity
{
    private RecyclerView recyclerView;
    private List<User> userList;
    private RecyclerViewAdapter adapter;
    private LinearLayoutManager linearLayoutManager;
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        userList = new ArrayList<User>();
        for(int i = 0; i <= 20; i++)
        {
            User user = new User();
            user.setImageView(R.mipmap.ic_launcher);
            user.setUserName("我是Item" + i);
            userList.add(user);
        }
        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);       
        //创建一个线性布局管理器
        linearLayoutManager = new LinearLayoutManager(this);
        //设置为垂直布局,这是默认的
        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        //设置布局管理器
        recyclerView.setLayoutManager(linearLayoutManager);
        //新建一个适配器实例
        adapter = new RecyclerViewAdapter(userList);
        //设置Adapter
        recyclerView.setAdapter(adapter);
    }
}

       通过上面的代码可以看到对RecylerView设置Adapter的过程,上面的的过程比ListView要复杂一些,这也是因为RecyclerView高度解耦的表现,虽然在代码的书写上增加复杂度,但是它拥有极高的扩展性。

       现在运行一下程序可以看到:

       

       通过上面的例子可以看出,我们使用RecyclerView实现了和ListView几乎一模一样的效果,虽然说在代码量方面并没有减少,但是逻辑变得更加清晰了,但是目前的RecyclerView还没有item之间的分割线,接下来我们还需要通过RecyclerView.addItemDecoration()给RecyclerView中添加item之间的分割线。

       RecyclerView.addItemDecoration()需要传入一个参数,该参数为RecyclerView.ItemDecoration,它是一个抽象类。也就是说我们要加入分隔线必须自定义一个类并且实现这个抽象类。RecyclerView.ItemDecoration的源代码为:

public static abstract class ItemDecoration 
{
    public void onDraw(Canvas c, RecyclerView parent, State state) 
    {
        onDraw(c, parent);
    }

    public void onDrawOver(Canvas c, RecyclerView parent, State state) 
    {
        onDrawOver(c, parent);
    }

    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
    {
        getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),parent);
    }

    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)
    {
        outRect.set(0, 0, 0, 0);
    }
}

       从上面的抽象类中可以看出有onDraw()、onDrawOver()和getItemOffsets()这三种方法,RecyclerView在绘制的过程中,会先调用该抽象类中的onDraw()和onDrawOver()方法绘制Decorator,onDraw()方法优先于drawChildren,而onDrawOver()方法在drawChildren之后调用,因此一般情况下这两个方法我们只用重写一个即可,getItemOffsets()方法可以通过outRect.set()为每个Item设置一定的偏移量,主要用于绘制Decorator。

       接下来看一个RecyclerView.ItemDecoration的实现类,当使用的LayoutManager为LinearLayoutManager时,该类可以很好的为RecyclerView添加分隔线。

package com.example.administrator.recyclerview;

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;

public class DividerItemDecoration extends RecyclerView.ItemDecoration
{
    //使用系统主题中的分割线
    private static final int[] ATTRS = new int[]{
            android.R.attr.listDivider
    };

    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;

    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;

    private Drawable mDivider;

    private int mOrientation;

    public DividerItemDecoration(Context context, int orientation)
    {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
        setOrientation(orientation);
    }

    //判断方向参数是否合法
    public void setOrientation(int orientation)
    {
        if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST)
        {
            throw new IllegalArgumentException("invalid orientation");
        }
        mOrientation = orientation;
    }

    public void onDraw(Canvas c, RecyclerView parent)
    {
        if (mOrientation == VERTICAL_LIST)
        {
            //竖直
            drawVertical(c, parent);
        }
        else
        {
            //水平
            drawHorizontal(c, parent);
        }
    }

    //竖直方向
    public void drawVertical(Canvas c, RecyclerView parent)
    {
        //得到RecyclerView距界面左边的距离,其实就是item左上角的横坐标
        final int left = parent.getPaddingLeft();
        //得到RecyclerView距界面右边的距离,其实就是item右下角的横坐标
        final int right = parent.getWidth() - parent.getPaddingRight();
        //得到Item的数量
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++)
        {
            //得到Item的对象
            final View child = parent.getChildAt(i);
            //得到Item参数
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            //计算item左上角的纵坐标
            final int top = child.getBottom() + params.bottomMargin;
            //计算item右下角的纵坐标
            final int bottom = top + mDivider.getIntrinsicHeight();
            //根据Item的左上角和右下角坐标绘制矩形
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    //水平方向
    public void drawHorizontal(Canvas c, RecyclerView parent)
    {
        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);
        }
    }

    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)
    {
        if (mOrientation == VERTICAL_LIST)
        {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        }
        else
        {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }
}

       从上面的代码中可以看出,DividerItemDecoration直接使用了系统主题中的android.R.attr.listDivider做为Item之间的分隔线,获取到listDivider之后在getItemOffsets中,outRect去设置了绘制的范围,onDraw中实现了真正的绘制。在写好DividerItemDecoration之后只需要在代码中添加

//设置Item的分割线
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
        就可以给Item设置分隔线效果,设置好的效果如下所示:

       

       如上面所示,所使用的分割线效果是系统自带的属性,如果想要自定义Item分隔线效果可以在theme.xml中找到该属性并且进行修改。

       除了使用上面的方法绘制出item分隔线之外,还可以通过设置Item距离父控件的边距来实现Item分隔线效果,使用这种方式比较简单,也更容易理解。只需要在Item的布局文件中加入android:layout_margin属性即可,入下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        android:layout_margin="5dp"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center"
            android:src="@mipmap/ic_launcher"/>

        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="5"
            android:textSize="25sp"
            android:textColor="#000000"
            android:layout_gravity="center"
            android:text="我是Item1"/>

    </LinearLayout>
       顺便通过设置的方向,将RecyclerView变为水平滑动

//设置滚动方向为水平方向,如果不设置默认为竖直方向
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
       通过以上的设置,效果如下:

       
       上面的设置实现了类似于ListView样子的Demo,但是还是缺少了点击效果,令人郁闷的是系统也没有为我们提供可以直接使用的ClickListener和LongClickListener,这就需要我们自己去添加点击效果,在这里我们选择通过在adapter中添加点击的回调方法。

       首先需要在接口中定义几种点击类型

package com.example.administrator.recyclerview;

import android.view.View;

/**
 * Created by ChuPeng on 2017/3/31.
 */

public interface ItemClickListener
{
    //Item 普通点击
    public void onItemClick(View view, int position);

    //Item 长按
    public void onItemLongClick(View view, int position);

    //Item 内部View点击
    public void onItemSubViewClick(View view, int position);
}
       然后需要在adap ter中添加点击的回调

package com.example.administrator.recyclerview;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;

/**
 * Created by ChuPeng on 2017/3/31.
 */

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolder>
{
    private List<User> userList;
    private String orientation;
    private ItemClickListener itemClickListener;

    public RecyclerViewAdapter(List<User> userList, String orientation)
    {
        super();
        this.userList = userList;
        this.orientation = orientation;
    }

    //设置回调
    public void setItemClickListener(ItemClickListener itemClickListener)
    {
        this.itemClickListener = itemClickListener;
    }

    public int getItemCount()
    {
        return userList.size();
    }

    public RecyclerViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType)
    {
        View view;
        if("vertical".equals(orientation))
        {
            view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recyclerview_item_vertical, null);
        }
        else
        {
            view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recyclerview_item_horizontal, null);
        }
        //创建一个ViewHolder
        RecyclerViewHolder viewHolder = new RecyclerViewHolder(view);
        return viewHolder;
    }

    public void onBindViewHolder(final RecyclerViewHolder holder, final int position)
    {
        User user = userList.get(position);
        holder.userNmae.setText(user.getUserName());
        holder.imageName.setBackgroundResource(user.getImageView());
        //为image添加监听回调
        holder.imageName.setOnClickListener(new View.OnClickListener()
        {
            public void onClick(View v)
            {
                //如果设置了回调,则设置点击事件
                if(itemClickListener != null)
                {
                    itemClickListener.onItemSubViewClick(holder.imageName, position);
                }
            }
        });
    }

    public class RecyclerViewHolder extends RecyclerView.ViewHolder
    {
        private TextView userNmae;
        private ImageView imageName;

        public RecyclerViewHolder(final View itemView)
        {
            super(itemView);
            this.imageName = (ImageView) itemView.findViewById(R.id.image);
            this.userNmae = (TextView) itemView.findViewById(R.id.name);
            //为item添加普通点击回调
            itemView.setOnClickListener(new View.OnClickListener()
            {
                public void onClick(View v)
                {
                    //如果设置了回调,则设置点击事件
                    if(itemClickListener != null)
                    {
                        itemClickListener.onItemClick(itemView, getPosition());
                    }
                }
            });
            //为item添加长按回调
            itemView.setOnLongClickListener(new View.OnLongClickListener()
            {
                public boolean onLongClick(View v)
                {
                    //如果设置了回调,则设置点击事件
                    if(itemClickListener != null)
                    {
                        itemClickListener.onItemLongClick(itemView, getPosition());
                    }
                    return true;
                }
            });
        }
    }
}
       通过上面的代码可以看出,相比较普通的adapter来说,加入点击事件就是在adapter的onBindViewHolder()方法或者RecyclerViewHolder()方法中加入某一项或者是某个View点击事件的自定义回调接口。最后还需要在Activity中设置adapter的点击监听的实现。

adapter.setItemClickListener(new ItemClickListener()
{
     public void onItemClick(View view, int position)
     {
         Toast.makeText(linearLayoutVerticalActivity.this, "你点击了Item", Toast.LENGTH_SHORT).show();
     }

     public void onItemLongClick(View view, int position)
     {
         Toast.makeText(linearLayoutVerticalActivity.this, "你长按了Item", Toast.LENGTH_SHORT).show();
     }

     public void onItemSubViewClick(View view, int position)
     {
         Toast.makeText(linearLayoutVerticalActivity.this, "你点击了Image", Toast.LENGTH_SHORT).show();
     }
});
       通过设置以后的效果如下:

          
       通常在ListView中实现Item的增加或删除操作,都是对数据源进行操作,并且通过notifyDataSetChanged()方法更新数据达到ListView刷新的效果。在RecyclerView中想要对Item实现增加或删除操作并且有动画效果也是对数据源进行操作,不同的是需要通过notifyItemInserted(position)或notifyItemRemoved(position)刷新数据,否则将不会有动画效果。 在Activity中做出如下修改。

//设置Item增加、移出动画
recyclerView.setItemAnimator(new DefaultItemAnimator());

addButton.setOnClickListener(new View.OnClickListener()
{
	public void onClick(View v)
	{
		User user = new User();
		user.setUserName("Insert One");
		user.setImageView(R.mipmap.ic_launcher);
		if(userList.size() > 2)
		{
			userList.add(2, user);
			adapter.notifyItemInserted(2);
		}
		else
		{
			userList.add(0, user);
			adapter.notifyItemInserted(0);
		}
	}
});

removeButton.setOnClickListener(new View.OnClickListener()
{
	public void onClick(View v)
	{
		if(userList.size() > 3)
		{
			userList.remove(2);
			adapter.notifyItemRemoved(2);
		}
		else
		{
			if(userList.size() > 0)
			{
				userList.remove(0);
				adapter.notifyItemRemoved(0);
			}
			else
			{
				Toast.makeText(linearLayoutVerticalActivity.this, "列表中无数据", Toast.LENGTH_SHORT).show();
			}
		}
	}
});

       在上面的代码中增加/删除都是从列表中的第三个Item开始,直到列表中不足三个Item时增加/删除的操作就变为从第一个Item开始,通过添加增加/删除后的效果入下所示:

                           
       可以看到通过这样的方法实现了在不同方向上带有动画效果的增加/删除操作。

(2)网格布局       

       至此,RecyclerView的用法就已经介绍的差不多了,但是你肯定会发现像上面的那种使用RecyclerView去模仿ListView真的好麻烦,还不如直接用ListView来的简单快速,那么请继续往下看。在Activity的代码中,可以看到这样的代码

//创建一个线性布局管理器
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
//设置滚动方向为竖直方向,如果不设置默认为竖直方向
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
//设置布局管理器
recyclerView.setLayoutManager(layoutManager);
       在上面的代码中是通过系统默认的LinearLayoutManager,设置好滚动的方向以后再将这个布局管理器设置给RecyclerView即可。RecyclerView.LayoutManager是一个抽象类,系统提供了三个实现类,LinearLayoutManager就是其中一个实现类,使用这三个实现类可以快速的通过RecyclerView分别实现线性布局、网格布局、瀑布式布局。

  • LinearLayoutManager   线性布局管理器,支持横向、纵向
  • GridLayoutManager   网格布局管理器,支持横向、纵向
  • StaggeredGridLayoutManager   瀑布布局管理器,支持横向、纵向
       在上面的的过程中已经体验了,线性布局的使用方法,把上面展示的RecyclerView线性改造成网格布局(类似于GridView效果)其实就是分分钟钟的事情。
GridLayoutManager gridLayoutManager = new GridLayoutManager(gridLayoutVerticalActivity.this, 3, GridLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(gridLayoutManager);
       只需要修改布局管理器为GridLayoutManager即可,从GridLayoutManager的构造函数中可以看出总共接收4个参数,第一个参数是context,第二个参数是按布局方向上有几个Item,第三个参数是布局方向,第四个参数设置是否反转。把布局管理器改为GridLayoutManager后前面使用的DividerItemDecoration绘制分隔线在这里明显已经不适用了,因为在LinearLayoutManager中每一行只有一个Item,而在GridLayoutManager中每一行有多个Item。
package com.example.administrator.recyclerview;

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.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;
import android.support.v7.widget.RecyclerView.State;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;

public class DividerGridItemDecoration extends RecyclerView.ItemDecoration
{
    private static final int[] ATTRS = new int[]{
            android.R.attr.listDivider };
    private Drawable mDivider;

    public DividerGridItemDecoration(Context context)
    {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, State state)
    {

        drawHorizontal(c, parent);
        drawVertical(c, parent);

    }

    private int getSpanCount(RecyclerView parent)
    {
        // 列数
        int spanCount = -1;
        LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager)
        {

            spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
        } else if (layoutManager instanceof StaggeredGridLayoutManager)
        {
            spanCount = ((StaggeredGridLayoutManager) layoutManager)
                    .getSpanCount();
        }
        return spanCount;
    }

    public void drawHorizontal(Canvas c, RecyclerView parent)
    {
        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.getLeft() - params.leftMargin;
            final int right = child.getRight() + params.rightMargin
                    + mDivider.getIntrinsicWidth();
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    public void drawVertical(Canvas c, RecyclerView parent)
    {
        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.getTop() - params.topMargin;
            final int bottom = child.getBottom() + params.bottomMargin;
            final int left = child.getRight() + params.rightMargin;
            final int right = left + mDivider.getIntrinsicWidth();

            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    private boolean isLastColum(RecyclerView parent, int pos, int spanCount,
                                int childCount)
    {
        LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager)
        {
            if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边
            {
                return true;
            }
        } else if (layoutManager instanceof StaggeredGridLayoutManager)
        {
            int orientation = ((StaggeredGridLayoutManager) layoutManager)
                    .getOrientation();
            if (orientation == StaggeredGridLayoutManager.VERTICAL)
            {
                if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边
                {
                    return true;
                }
            } else
            {
                childCount = childCount - childCount % spanCount;
                if (pos >= childCount)// 如果是最后一列,则不需要绘制右边
                    return true;
            }
        }
        return false;
    }

    private boolean isLastRaw(RecyclerView parent, int pos, int spanCount,
                              int childCount)
    {
        LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager)
        {
            childCount = childCount - childCount % spanCount;
            if (pos >= childCount)// 如果是最后一行,则不需要绘制底部
                return true;
        } else if (layoutManager instanceof StaggeredGridLayoutManager)
        {
            int orientation = ((StaggeredGridLayoutManager) layoutManager)
                    .getOrientation();
            // StaggeredGridLayoutManager 且纵向滚动
            if (orientation == StaggeredGridLayoutManager.VERTICAL)
            {
                childCount = childCount - childCount % spanCount;
                // 如果是最后一行,则不需要绘制底部
                if (pos >= childCount)
                    return true;
            } else
            // StaggeredGridLayoutManager 且横向滚动
            {
                // 如果是最后一行,则不需要绘制底部
                if ((pos + 1) % spanCount == 0)
                {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void getItemOffsets(Rect outRect, int itemPosition,
                               RecyclerView parent)
    {
        int spanCount = getSpanCount(parent);
        int childCount = parent.getAdapter().getItemCount();
        if (isLastRaw(parent, itemPosition, spanCount, childCount))// 如果是最后一行,则不需要绘制底部
        {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        } else if (isLastColum(parent, itemPosition, spanCount, childCount))// 如果是最后一列,则不需要绘制右边
        {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else
        {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(),
                    mDivider.getIntrinsicHeight());
        }
    }
}
       在上面的代码中是在每行每列都绘制分隔线,在getItemOffsets()方法中去判断当前将要绘制的是否是最后一行,如果是则不需要绘制底部,判断是否是最后一列,如果是则不需要绘制右边。一般情况下如果仅仅希望通过Item之间的空隙来代替分隔线,那么设置Item布局的android:layout_margin属性倒是一种更好的方式,最后的效果为:
                           
       在其中加入增加/删除操作Item的动画效果,跟上面的实现方法是相同的,只需要在相应的Activity中加入增加/删除操作的逻辑。
addButton.setOnClickListener(new View.OnClickListener()
{
	public void onClick(View v)
	{
		User user = new User();
		user.setUserName("Insert One");
		user.setImageView(R.mipmap.ic_launcher);
		if(userList.size() > 2)
		{
			userList.add(2, user);
			adapter.notifyItemInserted(2);
		}
		else
		{
			userList.add(0, user);
			adapter.notifyItemInserted(0);
		}
	}
});

removeButton.setOnClickListener(new View.OnClickListener()
{
	public void onClick(View v)
	{
		if(userList.size() > 3)
		{
			userList.remove(2);
			adapter.notifyItemRemoved(2);
		}
		else
		{
			if(userList.size() > 0)
			{
				userList.remove(0);
				adapter.notifyItemRemoved(0);
			}
			else
			{
				Toast.makeText(gridLayoutVerticalActivity.this, "列表中无数据", Toast.LENGTH_SHORT).show();
			}
		}
	}
});
       实现后的效果为:
                           

(3)瀑布布局       
       想要实现瀑布布局,仅仅是按照下列代码对布局管理器进行替换,如下列代码所示:
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
       这里的StaggeredGridLayoutManager构造函数总共接收两个参数,第一个参数表示有多少列或者多少行,第二个参数表示瀑布式布局滑动的方向,如果传入的是StaggeredGridLayoutManager.VERTICAL代表有多少列;那么传入的如果是StaggeredGridLayoutManager.HORIZONTAL就代表有多少行,实现后的效果为:
                           
       在其中加入增加/删除操作Item的动画效果,跟上面的实现方法是相同的,只需要在相应的Activity中加入增加/删除操作的逻辑。
addButton.setOnClickListener(new View.OnClickListener()
{
	public void onClick(View v)
	{
		User user = new User();
		user.setUserName("Insert One");
		user.setImageView(R.mipmap.ic_launcher);
		if(userList.size() > 2)
		{
			userList.add(2, user);
			adapter.notifyItemInserted(2);
		}
		else
		{
			userList.add(0, user);
			adapter.notifyItemInserted(0);
		}
	}
});

removeButton.setOnClickListener(new View.OnClickListener()
{
	public void onClick(View v)
	{
		if(userList.size() > 3)
		{
			userList.remove(2);
			adapter.notifyItemRemoved(2);
		}
		else
		{
			if(userList.size() > 0)
			{
				userList.remove(0);
				adapter.notifyItemRemoved(0);
			}
			else
			{
				Toast.makeText(gridLayoutVerticalActivity.this, "列表中无数据", Toast.LENGTH_SHORT).show();
			}
		}
	}
});
       实现后的效果为:
                           
4.总结
       RecyclerView通过简单的改变布局管理器就可以产生不同的效果,那么我们就可以根据RecyclerView的这种特性动态的设置不同的布局管理器,屏幕宽度一般的情况下将RecyclerView的布局设置为线性布局,宽度大的情况下可以显示两列的网格布局或者瀑布布局,或者在屏幕横向纵向切换变化是改变RecyclerView的布局管理器,这里可以做出更为友好的界面。






                   以上Demo的源代码地址:点击打开链接














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值