RecyclerView

        在Android开发中,我们经常与ListView、GridView打交道,它们为数据提供了列表和视图的展示方式,方便用户的操作。随着Android的不断发展,单一的listview逐渐满足不了需求多变的项目。谷歌在support v7中加入了新的控件——RecyclerView,该控件整合了ListView、GridView的特点,而且最大的优点是可以很方便实现瀑布流效果。

        RecyclerView有几个它常用的内部类,很重要,贯穿整个使用过程:

1)、RecyclerView.Adapter:抽象类,为RecyclerView提供数据,一般根据不同的业务需求来编写具体的实现类。

2)、RecyclerView.LayoutManager:抽象类,主要用于测量RecyclerView的子Item,以及根据不同的布局方式来实现Item的布局效果,v包自带的实现类有:LinearLayoutManager、、GridLayoutManager StaggeredGridLayoutManager。

3)、RecyclerView.ItemDecoration:抽象类,这个主要用于不同的Item之间添加分割线(可选)。我们可以自定义抽象类实现自己的分割线效果。

4)、RecyclerView.ItemAnimator:抽象类,这个主要用于当一个item添加或者删除的时候出现的动画效果,官方提供一个默认的实现类。如果想要使我们的RecyclerView在添加、删除数据的时候有炫酷的动画,可以实现这个抽象类。

        1、使用方法

        1)、在build.gradle中添加依赖

compile 'com.android.support:recyclerview-v7:25+'

        2)、布局文件

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

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

</LinearLayout>

       item布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="item1"
        android:gravity="center"
        android:background="@color/colorAccent"/>

</LinearLayout>

        3)、创建Adpater

        通过继承RecyclerView.Adapter实现自己的适配器。RecyclerView.Adapter如下:

    public static abstract class Adapter<VH extends ViewHolder> {
        public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
        public abstract void onBindViewHolder(VH holder, int position);
        public abstract int getItemCount();
    }

        继承该类的时候,必须重写这三个方法,我们分别解释一下这三个方法是什么作用:

        a、onCreateViewHolder:创建ViewHolder,该方法会在RecyclerView需要展示一个item的时候回调。重写该方法时,应该使ViewHolder加载item view的布局。这个可以避免了不必要的findViewById操作,提高了性能。和ListView的ViewHolder操作类似。

        b、onBindeViewHolder:该方法在RecyclerView在特定位置展示数据时候回调,把数据绑定填充到相应的item view中。

        c、getItemCount:返回数据的数量。

        Adapter如下:

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
    private List<String> datas;

    public RecyclerViewAdapter(List<String> datas) {
        this.datas = datas;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // 使用 inflate(R.layout.item_recyler_view, null); RecyclerView子View宽度不能全屏,是wrap_content效果
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recyler_view, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.textView.setText(datas.get(position));
    }

    @Override
    public int getItemCount() {
        return datas.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        TextView textView;

        public ViewHolder(View itemView) {
            super(itemView);
            textView = (TextView) itemView.findViewById(R.id.tv_content);
        }
    }
}

        4)、Activity中代码处理,需要完全以下3点才可以正常显示

        a、获取RecyclerView实例,通过findViewById()方法。

        b、为RecyclerView设置布局管理器

        c、为RecyclerView设置适配器Adapter。

        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new RecyclerViewAdapter(datas);
        recyclerView.setAdapter(adapter);


            2、Item之间的分割线

        上图中RecyclerView每个item之间没有分割线。RecycleView没有像ListView那样可以直接在xml属性中添加android:divider。 我们可以实现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);
        }
    }

        onDraw和onDrawOver这两个方法是用于绘制的,onDraw是在item view绘制之前调用,而onDrawOver是在item view绘制之后调用。因此我们一般选择重写其中一个方法即可。getItemOffset告诉RecyclerView在绘制完一个item view的时候,应该留下多少空位用于绘制分割线。

        下面是实现类,和RecyclerView.ItemDecoration是一样,这里只是为了方便打印日志才重写的。设置RecyclerView的divider时我们可以不用自定义,直接使用系统自带的RecyclerView.ItemDecoration。

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;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent) {
        Log.v("recyclerview - itemdecoration", "onDraw()");

        if (mOrientation == VERTICAL_LIST) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }

    }


    public void drawVertical(Canvas c, RecyclerView parent) {
        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);
            android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
            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);
        }
    }

    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);
        }
    }

    @Override
    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);
        }
    }
}

        该实现类可以看到通过读取系统主题中的 android.R.attr.listDivider作为Item间的分割线,并且支持横向和纵向。获取到listDivider以后,该属性的值是个Drawable,在getItemOffsets中,outRect去设置了绘制的范围。onDraw中实现了真正的绘制。

        在Activity中添加如下代码:

recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new RecyclerViewAdapter(datas);
recyclerView.setAdapter(adapter);
recyclerView.addItemDecoration(new DividerItemDecoration(this,  DividerItemDecoration.VERTICAL_LIST));

        如何更改分割线的样式呢?该分割线是系统默认的android.R.attr.listDivider,你可以在theme.xml中找到该属性的使用情况。可以在style.xml中指定我们的divider,如下:

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="colorControlNormal">@color/grey</item>
        <item name="colorControlActivated">@color/blue_black</item>
        <item name="android:listDivider">@drawable/shape_divider</item>          // 定义RecyclerView的divider
    </style>

        在res->drawable中新建shape:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <size
        android:height="5dp"/>

    <solid
        android:color="@color/blue_dark"/>
</shape>

效果如下:


        3、更改布局管理器

    官方提供了三种布局方式,LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager。LinearLayoutManager是线性布局管理器,使得item呈竖直排列或者水平排列。GridLayoutManager是表格形式的布局,类似于GridView。StaggeredGridLayoutManager是瀑布流表格布局。

        1)、LinearLayoutManager

recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));

第2个参数表示是水平滑动或者是竖直方向滑动,第3个参数表示是否从数据的尾部开始显示。

效果就是上面图片。

        2)、GridLayoutManager

recyclerView.setLayoutManager(new GridLayoutManager(this, 4, GridLayoutManager.VERTICAL, false));

第2个参数表示表示表格的行数或者列数,第3个参数表示是水平滑动或者是竖直方向滑动,第4个参数表示是否从数据的尾部开始显示。

效果如下:

        3)、StaggeredGridLayoutManager

瀑布流效果

recyclerView.setLayoutManager(new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.VERTICAL));

第一个参数表示列数或者行数,第二个参数表示滑动方向。这些改动是不足够的,达不到瀑布流的效果。因为每个Item view的高度都是一样的。因此要在Adapter中修改每一个Item View的Height。

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.textView.setText(datas.get(position));

        ViewGroup.LayoutParams layoutParams =  holder.itemView.getLayoutParams();
        layoutParams.height = (int)(Math.random() * 300 + 100);
        holder.itemView.setLayoutParams(layoutParams);
    }

效果如下:


        4、监听Item的点击事件

系统没有提供ClickListener和LongClickListener,需要我们自己去实现点击事件。实现点击事件有两种方式:

方法一:利用View.onClickListener及onLongClickListener,

方法二:利用RecyclerView.OnItemTouchListener。

方法一比较简单,我们暂时实现方法一。

        1)定义点击事件监听接口(Adapter中)

 public interface OnItemClickListener{

        void onClick(View view, int position);
        void onLongClick(View view, int position);
    }

        2)将ItemView的点击事件传递到OnItemClickListener(Adapter中)

    public void onBindViewHolder(ViewHolder holder, final int position) {
        holder.textView.setText(datas.get(position));

        ViewGroup.LayoutParams layoutParams =  holder.itemView.getLayoutParams();
        layoutParams.height = (int)(Math.random() * 300 + 100);
        holder.itemView.setLayoutParams(layoutParams);

        /**
         * 为item设置点击监听
         */
        if(onItemClickListener != null) {
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onItemClickListener.onClick(v, position);
                }
            });

            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    onItemClickListener.onLongClick(v, position);
                    return true;
                }
            });
        }
    }

        注意上面回调函数返回的position是不正确的。如果使用onBindViewHolder()参数中的position,删除了数据position还是按照删除前的顺序返回的。应该使用如下方法获取position:

int realPosition = holder.getAdapterPosition();
onItemClickListener.onClick(v, realPosition);

        3)回调点击监听(Activity中)

        adapter = new RecyclerViewAdapter(datas);
        recyclerView.setAdapter(adapter);

        adapter.setOnItemClickListener(new RecyclerViewAdapter.OnItemClickListener() {
            @Override
            public void onClick(View view, int position) {
                Toast.makeText(RecyclerViewTestActivity.this, "onClick--view:" + view.toString() + ", position:" + position, Toast.LENGTH_LONG).show();
            }

            @Override
            public void onLongClick(View view, int position) {
                Toast.makeText(RecyclerViewTestActivity.this, "onLongClick--view:" + view.toString() + ", position:" + position, Toast.LENGTH_LONG).show();
            }
        });


        5、Adapter中数据的增加、删除、更改

        RecyclerView.Adapter中提供的四个操作数据的方法,如下:

final void notifyItemInserted(int position)该方法用于增加一个数据的时候,position表示新增数据显示的位置

final void notifyItemRemoved(int position) 该方法用于删除一个数据的时候,position表示数据删除的位置

final void notifyItemChanged(int position) 该方法表示所在position对应的item位置不会改变,但是该item内容发生变化

final void notifyDataSetChanged() 该方法一般用于:适配器之前装载的数据大部分已经过时了,需要重新更新数据。调用该方法的时候,recyclerView会重新计算子item及所有子item重新布局,出于效率考虑,官方建议用更加精确的方法(比如上面三个方法)来取代这个方法

1)、在Adapter中封装操作数据的方法

    public void addData(int position, String data){
        datas.add(position, data);
        notifyItemInserted(position);
    }

    public void removeData(int position){
        datas.remove(position);
        notifyItemRemoved(position);
    }

    public void updateData(int position, String data){
        datas.set(position, data);
        notifyItemChanged(position);
    }

        2)在Activity中调用上面的方法


        6、添加、删除、更改数据时动画

RecyclerView默认添加了动画DefaultItemAnimator。我们也可以通过下面代码设置动画:

recyclerView.setItemAnimator(new DefaultItemAnimator()); 

也可以自定义RecyclerView.ItemAnimator实现动画效果。


        7、打造通用Adapter

        通用Adapter的3个问题。数据怎么办?布局怎么办?绑定怎么办?数据采用泛型通过构造函数传递,布局使用构造函数传递,绑定数据使用抽象类子类实现。

public abstract class CommonRecyclerViewAdapter<T> extends RecyclerView.Adapter<CommonRecyclerViewAdapter.CommonViewHolder> {
    private static final String TAG = CommonRecyclerViewAdapter.class.getSimpleName();
    private Context mContext;
    private int mLayoutId;
    private List<T> mDatas; // ###数据使用泛型
    private OnItemClickListener mOnItemClickListener;

    public CommonRecyclerViewAdapter(Context context, int layoutId, List<T> datas){
        mContext = context;
        mLayoutId = layoutId; // ###布局怎么办?布局使用构造函数传递
        mDatas = datas; // ###数据怎么办?数据使用构造函数传递
    }

    /**
     * 子类实现bind数据
     * @param holder 点击item对应的ViewHolder
     * @param position 点击item对应的position
     */
    public abstract void bind(CommonViewHolder holder, int position); // ### bind怎么办?使用抽象类并由子类实现

    @Override
    public CommonViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(mLayoutId, parent,false);
        CommonViewHolder commonViewHolder = new CommonViewHolder(view);
        Log.i(TAG, "onCreateViewHolder--holder:" + commonViewHolder.toString());
        return commonViewHolder;
    }

    @Override
    public void onBindViewHolder(final CommonRecyclerViewAdapter.CommonViewHolder holder, int position) {
        Log.i(TAG, "onBindViewHolder--holder:" + holder.toString());
        bind(holder, position);

        // 设置点击监听
        if(mOnItemClickListener != null){
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mOnItemClickListener.onClick(v, holder.getLayoutPosition());
                }
            });

            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    mOnItemClickListener.onLongClick(v, holder.getLayoutPosition());
                    return true;
                }
            });
        }
    }

 public class CommonViewHolder extends RecyclerView.ViewHolder{
    // 用来存放子View减少findViewById的次数
    private SparseArray<View> mViews;

    public CommonViewHolder(View itemView) {
        super(itemView);
        mViews = new SparseArray<>();
    }

    public View getView(int id){
        View view  = mViews.get(id);
        if(view == null) {
            Log.i(TAG, "getView2:" + id);
            view = itemView.findViewById(id);
            mViews.put(id, view);
        }else{
            Log.i(TAG, "getView1:" + id);
        }

        return view;
    }
}









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值