Android RecyclerView 使用

来源:互联网 发布:鲶鱼墨水 知乎 编辑:程序博客网 时间:2024/06/06 05:46

RecyclerView已经出来很久了,工作当中用android原生控件比较少,而且由于项目基于android的版本的也是比较旧的,所以android新出的各种技术在工作中都用不到,平时也只有工作之余写demo的时候去学习学习,这里就对RecyclerView的使用做一下笔记。

在RecyclerView之前,我们用ListView或者GridView来显示庞大数据集,屏幕之外的内容可以滚动该视图变得可见。其中ListView是垂直显示,GridView是网格显示,而且我们还知道虽然ListView和GridView虽然显示很多数据,但是不会出现内存溢出,这是因为它们里面只会存在有限个子视图。关于ListView和GridView这里就不细说了,这两个控件很重要,用的也比较多。

既然官方推出了RecyclerView ,相信RecyclerView一定有特别之处。

下面就跟着一个小demo,一边理解RecyclerView的使用,一边感受RecyclerView的特别之处。

首先记得添加依赖库

compile 'com.android.support:recyclerview-v7:24.2.1'

布局文件:

<?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:id="@+id/activity_recycler_view"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="cj.com.recyclerviewdemo.RecyclerViewActivity">    <android.support.v7.widget.RecyclerView        android:id="@+id/my_recycler_view"        android:layout_width="match_parent"        android:layout_height="wrap_content"/></RelativeLayout>

看主要代码:

  mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);        // use this setting to improve performance if you know that changes        // in content do not change the layout size of the RecyclerView        mRecyclerView.setHasFixedSize(true);        // use a linear layout manager        mLayoutManager = new LinearLayoutManager(this);        mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);        mLayoutManager.setReverseLayout(false);//true 从底部开始填充子视图        mRecyclerView.setLayoutManager(mLayoutManager);        mRecyclerView.addItemDecoration(new DividerItemDecoration(this,LinearLayoutManager.VERTICAL));        mAdapter = new MyAdapter(datas);        mRecyclerView.setAdapter(mAdapter);

跟ListView它们相比,RecyclerView使用稍微复杂一些,因为多了一些新对象比如上面的LinearLayoutManager, DividerItemDecoration,那么首先来了解一下。

RecyclerView有一个函数:

setLayoutManager(RecyclerView.LayoutManager layout)

来看一下RecyclerView.LayoutManager的类结构:


有三个子类,这里先说这一下这类的作用,效果后面看

LayoutManager就理解为布局管理器吧,像ListView或者GridView它们的子视图在它们当中布局已经定死了,ListView就垂直显示,GridView就是网格显示,这样如果要需要显示两种不同的效果,则就需要用到这两个控件,而RecyclerView就灵活点了,子视图在它里面怎么布局都由LayoutManager去控制,只需要通过setLayoutManager()函数给

RecyclerView设置一个布局管理器即可,比如上面android已有布局管理器:

LinearLayoutManager(线性布局管理器),添加这个布局管理器的话,可以设置方向,通过setOrientation()函数设置,参数可以是水平或者垂直,如果是垂直的话,那就跟ListView一模一样了。等下看demo演示效果。

GridLayoutManager(网格布局管理器)它是LinearLayoutManager的子类,也可以设置方向,水平或垂直,垂直方向的话就跟GridView一模一样了。等下看demo演示效果。

StaggeredGridLayoutManager(瀑布流布局管理器),这个之前用ListView或者GridView没法实现,需要自定义,现在通过RecyclerView可以很简单实现,等下看效果。

怎么样,看到上面这些介绍,是不是觉得RecyclerView功能很强大了。

接着往下看DividerItemDecoration:

RecyclerView下面这个函数

addItemDecoration (RecyclerView.ItemDecoration decor)
是来装饰它的子视图的。这里我是用来给子视图添加一条分割线,因为RecyclerView默认没有分割线了,我们在使用使用ListView的给ListView添加分割线很简单,例如:

 <ListView        android:dividerHeight="2dp"        android:divider="@color/colorPrimaryDark"        android:id="@+id/listview"        android:layout_width="match_parent"        android:layout_height="match_parent">    </ListView>

现在是不是觉得RecyclerView添加分割线好复杂的,其实不然,RecyclerView这样更灵活了,因为RecyclerView.ItemDecoration给子视图装饰,不仅仅可以添加分割线,还可以随意添加其他装饰,后面看ItemDecoration的内部函数就知道。所以说RecyclerView功能更丰富了

跟ListView它们一样,RecyclerView也需要关联一个Adapter,这个Adapter负责提供数据集。

只不过RecyclerView关联的Adapter需要继承RecyclerView.Adapter而且该Adapter还需要加一个泛型继承android.support.v7.widget.RecyclerView.ViewHolder

先看代码:

 public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {        private ArrayList<Contact> mDataset;        // Provide a reference to the views for each data item        // Complex data items may need more than one view per item, and        // you provide access to all the views for a data item in a view holder        public  class ViewHolder extends RecyclerView.ViewHolder {            // each data item is just a string in this case            public TextView name;            public TextView number;            public View View;            public ViewHolder(View v) {                super(v);                View = v;                name = (TextView) v.findViewById(R.id.name);                number = (TextView) v.findViewById(R.id.number);            }        }        // Provide a suitable constructor (depends on the kind of dataset)        public MyAdapter(ArrayList<Contact> myDataset) {            mDataset = myDataset;        }        // Create new views (invoked by the layout manager)        @Override        public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,                                                       int viewType) {            Log.d("test","onCreateViewHolder  viewType="+viewType);            // create a new view            View v = LayoutInflater.from(parent.getContext())                    .inflate(R.layout.list_item_layout, parent, false);            // set the view's size, margins, paddings and layout parameters           // ...            ViewHolder vh = new ViewHolder(v);            Log.d("test","onCreateViewHolder  ViewHolder="+vh);            return vh;        }        // Replace the contents of a view (invoked by the layout manager)        @Override        public void onBindViewHolder(ViewHolder holder, int position) {            // - get element from your dataset at this position            // - replace the contents of the view with that element            Log.d("test","onBindViewHolder  holder="+holder+ " position="+position);            holder.name.setText(mDataset.get(position).getName());            holder.number.setText(mDataset.get(position).getNumber());        }        // Return the size of your dataset (invoked by the layout manager)        @Override        public int getItemCount() {            Log.d("test","getItemCount");            return mDataset.size();        }        @Override        public int getItemViewType(int position) {            Log.d("test","getItemViewType   position="+position);            return super.getItemViewType(position);        }        @Override        public long getItemId(int position) {            Log.d("test","getItemId   position="+position);            return super.getItemId(position);        }    }

RecyclerView.Adapter跟我们使用ListView用的BaseAdapter区挺大的

BaseAdapter主要重写下面两个函数:

public int getCount()public View getView(int position, View convertView, ViewGroup parent)

RecyclerView.Adapter主要重写下面三个函数:

public int getItemCount() public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)  public void onBindViewHolder(ViewHolder holder, int position)

上面提到了RecyclerView.ViewHolder,关于ViewHolder,我们也在ListView的中用到过,它的作用是来优化ListView的性能,减少去查找控件次数,使用一般如下:

 class MyAdapter extends BaseAdapter{        private Context context;        public MyAdapter(Context context){            this.context = context;        }        @Override        public int getCount() {            return datas.size();        }        @Override        public Object getItem(int position) {            return datas.get(position);        }        @Override        public long getItemId(int position) {            return position;        }        @Override        public View getView(int position, View convertView, ViewGroup parent) {            ViewHolder  viewHolder;            Log.d("test","  position="+position+"convertView="+convertView);            if(convertView == null){                convertView = LayoutInflater.from(context).inflate(R.layout.list_item_layout, null);                Log.d("test","==null  position="+position+"convertView="+convertView);                TextView name = (TextView) convertView.findViewById(R.id.name);                TextView number = (TextView) convertView.findViewById(R.id.number);                viewHolder = new ViewHolder();                viewHolder.name = name;                viewHolder.number = number;                convertView.setTag(viewHolder);            }else{                viewHolder = (ViewHolder) convertView.getTag();            }            viewHolder.name.setText(datas.get(position).getName());            viewHolder.number.setText(datas.get(position).getNumber());            return convertView;        }        class ViewHolder{            private TextView name;            private TextView number;        }    }

可以看到ViewHolder是我们自己定义的。

而在RecyclerView中已经提供RecyclerView.ViewHolder,我们只需继承该类即可。

然后我们说ListView的它们内部只会有有限个子View,因为新出现的View会复用移出屏幕的View,主要通过下面这个函数来体现的:

 public View getView(int position, View convertView, ViewGroup parent) {            ViewHolder  viewHolder;            Log.d("test","  position="+position+"convertView="+convertView);            if(convertView == null){                convertView = LayoutInflater.from(context).inflate(R.layout.list_item_layout, null);                Log.d("test","==null  position="+position+"convertView="+convertView);                TextView name = (TextView) convertView.findViewById(R.id.name);                TextView number = (TextView) convertView.findViewById(R.id.number);                viewHolder = new ViewHolder();                viewHolder.name = name;                viewHolder.number = number;                convertView.setTag(viewHolder);            }else{                viewHolder = (ViewHolder) convertView.getTag();            }            viewHolder.name.setText(datas.get(position).getName());            viewHolder.number.setText(datas.get(position).getNumber());            return convertView;        }

如果convertView不为null,就表示有废弃的View可以复用,这个时候只需要修改相应的数据即可,无需创建新的视图。

在RecyclerView.Adapter中看一下下面两个函数:

 // Create new views (invoked by the layout manager)        @Override        public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,                                                       int viewType) {            Log.d("test","onCreateViewHolder  viewType="+viewType);            // create a new view            View v = LayoutInflater.from(parent.getContext())                    .inflate(R.layout.list_item_layout, parent, false);            // set the view's size, margins, paddings and layout parameters           // ...            ViewHolder vh = new ViewHolder(v);            Log.d("test","onCreateViewHolder  ViewHolder="+vh);            return vh;        }        // Replace the contents of a view (invoked by the layout manager)        @Override        public void onBindViewHolder(ViewHolder holder, int position) {            // - get element from your dataset at this position            // - replace the contents of the view with that element            Log.d("test","onBindViewHolder  holder="+holder+ " position="+position);            holder.name.setText(mDataset.get(position).getName());            holder.number.setText(mDataset.get(position).getNumber());        }

这里可以理解RecyclerView内部复用机制是复用ViewHolder。

onCreateViewHolder()这个函数在滚动RecyclerView时候不一定会调用,只要有子View被移出屏幕,滚进来的View显示,就不会调用onCreateViewHolder()该函数,只会调用onBindViewHolder来重新设置数据。后面通过log可以发现。当然这里讲ListView和RecyclerView都只考虑它们的子View都属于一个Type,也就是他们布局都一样。即使布局一样,有多个类型,它们也会复用,这里就没再讨论了。

好了,主要代码就讲这么多,看一下效果:


这里用的RecyclerView.LayoutManager是LinearLayoutManager,方向设置是垂直的,所以效果跟ListView一样。

看一下log

D/test: getItemCountD/test: getItemCountD/test: getItemCountD/test: getItemViewType   position=0D/test: onCreateViewHolder  viewType=0D/test: onCreateViewHolder  ViewHolder=ViewHolder{584e7bc position=-1 id=-1, oldPos=-1, pLpos:-1 unbound no parent}D/test: onBindViewHolder  holder=ViewHolder{584e7bc position=0 id=-1, oldPos=-1, pLpos:-1 no parent} position=0D/test: getItemCountD/test: getItemViewType   position=1D/test: onCreateViewHolder  viewType=0D/test: onCreateViewHolder  ViewHolder=ViewHolder{f9a2345 position=-1 id=-1, oldPos=-1, pLpos:-1 unbound no parent}D/test: onBindViewHolder  holder=ViewHolder{f9a2345 position=1 id=-1, oldPos=-1, pLpos:-1 no parent} position=1D/test: getItemCountD/test: getItemViewType   position=2D/test: onCreateViewHolder  viewType=0D/test: onCreateViewHolder  ViewHolder=ViewHolder{27be3a9a position=-1 id=-1, oldPos=-1, pLpos:-1 unbound no parent}D/test: onBindViewHolder  holder=ViewHolder{27be3a9a position=2 id=-1, oldPos=-1, pLpos:-1 no parent} position=2D/test: getItemCountD/test: getItemViewType   position=3D/test: onCreateViewHolder  viewType=0D/test: onCreateViewHolder  ViewHolder=ViewHolder{ef60fcb position=-1 id=-1, oldPos=-1, pLpos:-1 unbound no parent}D/test: onBindViewHolder  holder=ViewHolder{ef60fcb position=3 id=-1, oldPos=-1, pLpos:-1 no parent} position=3D/test: getItemCountD/test: getItemViewType   position=4D/test: onCreateViewHolder  viewType=0D/test: onCreateViewHolder  ViewHolder=ViewHolder{1996c3a8 position=-1 id=-1, oldPos=-1, pLpos:-1 unbound no parent}D/test: onBindViewHolder  holder=ViewHolder{1996c3a8 position=4 id=-1, oldPos=-1, pLpos:-1 no parent} position=4D/test: getItemCountD/test: getItemViewType   position=5D/test: onCreateViewHolder  viewType=0D/test: onCreateViewHolder  ViewHolder=ViewHolder{10beaec1 position=-1 id=-1, oldPos=-1, pLpos:-1 unbound no parent}D/test: onBindViewHolder  holder=ViewHolder{10beaec1 position=5 id=-1, oldPos=-1, pLpos:-1 no parent} position=5D/test: getItemCountD/test: getItemViewType   position=6D/test: onCreateViewHolder  viewType=0D/test: onCreateViewHolder  ViewHolder=ViewHolder{24b56666 position=-1 id=-1, oldPos=-1, pLpos:-1 unbound no parent}D/test: onBindViewHolder  holder=ViewHolder{24b56666 position=6 id=-1, oldPos=-1, pLpos:-1 no parent} position=6D/test: getItemCountD/test: getItemViewType   position=7D/test: onCreateViewHolder  viewType=0D/test: onCreateViewHolder  ViewHolder=ViewHolder{3f53dda7 position=-1 id=-1, oldPos=-1, pLpos:-1 unbound no parent}D/test: onBindViewHolder  holder=ViewHolder{3f53dda7 position=7 id=-1, oldPos=-1, pLpos:-1 no parent} position=7D/test: getItemCountD/test: getItemViewType   position=8D/test: onCreateViewHolder  viewType=0D/test: onCreateViewHolder  ViewHolder=ViewHolder{1a687254 position=-1 id=-1, oldPos=-1, pLpos:-1 unbound no parent}D/test: onBindViewHolder  holder=ViewHolder{1a687254 position=8 id=-1, oldPos=-1, pLpos:-1 no parent} position=8D/test: getItemCountD/test: getItemCountD/test: getItemCountD/test: getItemViewType   position=0D/test: getItemCountD/test: getItemViewType   position=1D/test: getItemCountD/test: getItemViewType   position=2D/test: getItemCountD/test: getItemViewType   position=3D/test: getItemCountD/test: getItemViewType   position=4D/test: getItemCountD/test: getItemViewType   position=5D/test: getItemCountD/test: getItemViewType   position=6D/test: getItemCountD/test: getItemViewType   position=7D/test: getItemCountD/test: getItemViewType   position=8D/OpenGLRenderer: endAllStagingAnimators on 0x7f7a642c2800 (RippleDrawable) with handle 0x7f7a6550ff40D/test: getItemCountD/test: getItemViewType   position=9D/test: onCreateViewHolder  viewType=0D/test: onCreateViewHolder  ViewHolder=ViewHolder{124675fd position=-1 id=-1, oldPos=-1, pLpos:-1 unbound no parent}D/test: onBindViewHolder  holder=ViewHolder{124675fd position=9 id=-1, oldPos=-1, pLpos:-1 no parent} position=9D/test: getItemCountD/test: getItemViewType   position=10D/test: onCreateViewHolder  viewType=0D/test: onCreateViewHolder  ViewHolder=ViewHolder{3ea182f2 position=-1 id=-1, oldPos=-1, pLpos:-1 unbound no parent}D/test: onBindViewHolder  holder=ViewHolder{3ea182f2 position=10 id=-1, oldPos=-1, pLpos:-1 no parent} position=10D/test: getItemCountD/test: getItemViewType   position=11D/test: onCreateViewHolder  viewType=0D/test: onCreateViewHolder  ViewHolder=ViewHolder{19d6ad43 position=-1 id=-1, oldPos=-1, pLpos:-1 unbound no parent}D/test: onBindViewHolder  holder=ViewHolder{19d6ad43 position=11 id=-1, oldPos=-1, pLpos:-1 no parent} position=11D/test: getItemCountD/test: getItemViewType   position=12D/test: onBindViewHolder  holder=ViewHolder{584e7bc position=12 id=-1, oldPos=-1, pLpos:-1 no parent} position=12D/test: getItemCountD/test: getItemViewType   position=13D/test: onBindViewHolder  holder=ViewHolder{f9a2345 position=13 id=-1, oldPos=-1, pLpos:-1 no parent} position=13

可以发现滚动RecyclerView时从下标为12开始也就是第13个item开始就没有调用onCreateViewHolder()了,因为这时已经有itme已经完全移出屏幕了,可以复用了。

这里设置了DividerItemDecoration,我们来看一下它的实现:

首先看RecyclerView的这个函数:

addItemDecoration(RecyclerView.ItemDecoration decor)
看一下RecyclerView.ItemDecoration的类结构:



可以发现DividerItemDecoration是它的一个子类,但是android SDK中并没有包含该类的API

下载DividerItemDecoration源码点击这里

看一下它的实现:

package cj.com.recyclerviewdemo;/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */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.util.Log;import android.view.View;/** * This class is from the v7 samples of the Android SDK. It's not by me! * <p/> * See the license above for details. */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);//mDivider = context.getResources().getDrawable(R.drawable.divider);a.recycle();setOrientation(orientation);}public void setOrientation(int orientation){if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST){throw new IllegalArgumentException("invalid orientation");}mOrientation = orientation;}@Overridepublic void onDraw(Canvas c, RecyclerView parent){//Log.v("test", "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);//RecyclerView v = new 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);}}@Overridepublic void getItemOffsets(Rect outRect, int itemPosition,RecyclerView parent){//Log.v("test", "getItemOffsets");if (mOrientation == VERTICAL_LIST){outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());} else{outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);}}}

注意该DividerItemDecoration只能用于布局管理器是LinearLayoutManager时

mDivider = a.getDrawable(0);

divider是一个drawable,这里drawable就直接使用android默认的listView的分割线,也就属性listDivider对应的drawable。

当然这里可以改成我们自己想要的drawable:

mDivider = context.getResources().getDrawable(R.drawable.divider);

这里可以在修改主题中listDivider属性的值,引用该drawable。

看一下divider.xml文件

<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android"    android:shape="rectangle">    <solid android:color="@color/colorPrimaryDark"/>    <size android:height="3dp"        android:width="3dp"/></shape>

看一下效果:


继续看上面的DividerItemDecoration

重写两个重要函数:

public void getItemOffsets(Rect outRect, int itemPosition,RecyclerView parent)

public void onDraw(Canvas c, RecyclerView parent)

getItemOffsets这个函数用于获取item的偏移量

onDraw函数就开始绘制item的装饰,上面函数偏移量的位置就用来绘制装饰


if (mOrientation == VERTICAL_LIST){outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());} else{outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);}

比如这里是垂直布局,所以只需要在竖直方法有一定的偏移量来绘制divider,这里设置距离也就是divider(drawable)的内部高度,也就是drawable的高度了。

关于上面绘制divider没讲了,等下会根据这个给StaggeredGridLayoutManager,GridLayoutManager绘制分割线,再讲ondraw函数

将LinearLayoutManager设置为水平方向看看,改一下代码

mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
mRecyclerView.addItemDecoration(new DividerItemDecoration(this,LinearLayoutManager.HORIZONTAL));


既然上面都提到了有三种布局管理器,咱们都来看一下效果:

GridLayoutManager ,使用GridLayoutManager,那么就不能使用上面的DividerItemDecoration来绘制分割线了,咱先看没有分割线的效果

GridLayoutManager gridLayoutManager = new GridLayoutManager(this,4);        gridLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
 mRecyclerView.setLayoutManager(gridLayoutManager);
4表示有4列,方向为垂直方法


效果跟GridView一样,不过没有分割线,有点不好分辨,所以咱们就跟根据DividerItemDecoration重新来为GridLayoutManager 垂直方向写一个ItemDecoration,来绘制分割线:

可以发现GridLayoutManager下方和右边都应该绘制分割线

看代码:

package cj.com.recyclerviewdemo;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.util.Log;import android.view.View;/** * */public class DividerItemDecoration1 extends RecyclerView.ItemDecoration {    private static final int[] ATTRS = new int[] { android.R.attr.listDivider };    private Drawable mDivider;    public DividerItemDecoration1(Context context){        final TypedArray a = context.obtainStyledAttributes(ATTRS);       // mDivider = a.getDrawable(0);        mDivider = context.getResources().getDrawable(R.drawable.divider);        a.recycle();    }    @Override    public void onDraw(Canvas c, RecyclerView parent){        Log.v("test", "onDraw");            draw(c, parent);    }    public void draw(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 left = child.getLeft();            final int right = child.getRight();            final int top = child.getBottom() + params.bottomMargin;            final int bottom = top + mDivider.getIntrinsicHeight();            mDivider.setBounds(left, top, right, bottom);//水平方向            mDivider.draw(c);            final int left1 = child.getRight()+params.rightMargin;            final int right1 = left1+mDivider.getIntrinsicWidth();            final int top1 = child.getTop()+params.topMargin;            final int bottom1 = child.getBottom()+params.bottomMargin;            mDivider.setBounds(left1, top1, right1, bottom1);//垂直方向            mDivider.draw(c);        }    }    @Override    public void getItemOffsets(Rect outRect, int itemPosition,                               RecyclerView parent) {        Log.v("test", "getItemOffsets");        outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());    }    @Override    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {        super.onDrawOver(c, parent, state);    }}

先看这简单粗暴的:

 public void getItemOffsets(Rect outRect, int itemPosition,                               RecyclerView parent) {        Log.v("test", "getItemOffsets");        outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());    }

现在item的下方和右边都应该要有偏移量来绘制分割线,就以分割线的宽度高度了

然后看draw()函数

public void draw(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 left = child.getLeft();            final int right = child.getRight();            final int top = child.getBottom() + params.bottomMargin;            final int bottom = top + mDivider.getIntrinsicHeight();            mDivider.setBounds(left, top, right, bottom);//水平方向            mDivider.draw(c);            final int left1 = child.getRight()+params.rightMargin;            final int right1 = left1+mDivider.getIntrinsicWidth();            final int top1 = child.getTop()+params.topMargin;            final int bottom1 = child.getBottom()+params.bottomMargin;            mDivider.setBounds(left1, top1, right1, bottom1);//垂直方向            mDivider.draw(c);        }    }

这里代码应该不难,首先绘制水平的分割线,然后绘制竖直分割线,暂时这样,先看效果图:

mRecyclerView.addItemDecoration(new DividerItemDecoration1(this));




看效果貌似还可以,但是发现还有些许问题,每一行最后一个位置没必要竖直方向的分割线了而且四个item的交界处有空隙,最后一行貌似也没必要绘制水平分割线

咱们再改改

package cj.com.recyclerviewdemo;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.util.Log;import android.view.View;/** * */public class DividerItemDecoration1 extends RecyclerView.ItemDecoration {    private static final int[] ATTRS = new int[] { android.R.attr.listDivider };    private Drawable mDivider;    private int spanCount;    public DividerItemDecoration1(Context context,int spanCount){        final TypedArray a = context.obtainStyledAttributes(ATTRS);       // mDivider = a.getDrawable(0);        mDivider = context.getResources().getDrawable(R.drawable.divider);        this.spanCount = spanCount;        a.recycle();    }    @Override    public void onDraw(Canvas c, RecyclerView parent){        Log.v("test", "onDraw");            draw(c, parent);    }    public void draw(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 left = child.getLeft();            final int right = child.getRight()+mDivider.getIntrinsicWidth();            final int top = child.getBottom() + params.bottomMargin;            final int bottom = top + mDivider.getIntrinsicHeight();            mDivider.setBounds(left, top, right, bottom);//水平方向            mDivider.draw(c);            final int left1 = child.getRight()+params.rightMargin;            final int right1 = left1+mDivider.getIntrinsicWidth();            final int top1 = child.getTop()+params.topMargin;            final int bottom1 = child.getBottom()+params.bottomMargin;            mDivider.setBounds(left1, top1, right1, bottom1);//垂直方向            mDivider.draw(c);        }    }    @Override    public void getItemOffsets(Rect outRect, int itemPosition,                               RecyclerView parent) {        Log.v("test", "getItemOffsets");        final int itemCount  = parent.getAdapter().getItemCount();        if(isLastOnRow(itemPosition,itemCount)){//是不是每一行的最后一个            if(isLastOnLastRow(itemPosition,itemCount)){//刚好整数行整数列 最后一个 水平垂直都不要绘制                outRect.set(0, 0, 0, 0);//竖直方向需要绘制            }else{                outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());//水平方向需要绘制            }        }else if(isLastOnLastRow(itemPosition,itemCount)){//最后一个 不够spanCount个数            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);//竖直方向需要绘制        }else if(isLastRow(itemPosition,itemCount)){//最后一行            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);//竖直方向需要绘制        }else{            outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());//水平竖直方向需要绘制        }    }    private boolean isLastRow(int itemPosition,int itemCount){        int row = itemCount/spanCount;        int r = itemCount%spanCount;        if(r == 0){            if(itemPosition/spanCount == (row-1)){                return true;            }else {                return false;            }        }else{            if((itemPosition+1) > row*spanCount){                return true;            }else{                return false;            }        }    }    private boolean isLastOnLastRow(int itemPosition,int itemCount){        return  (itemPosition+1) == itemCount;    }    private boolean isLastOnRow(int itemPosition,int itemCount){        return  (itemPosition+1)%spanCount == 0;    }    @Override    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {        super.onDrawOver(c, parent, state);    }}

多了些判断,看效果:


好看多了。不过这里我只是针对GridLayoutManager 垂直方向做了修改,应用水平方向可能又不一样了,所以可以写一个通用的,根据管理器不同使用不同的显示效果,这里没有细说了,只要知道如何去修改就可以了

GridLayoutManager也有水平和竖直方向之分,这里就没演示了。

接着看另外一种管理器StaggeredGridLayoutManager

同样可以设置方向和相应的列数或者函数

StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL);        mRecyclerView.setLayoutManager(staggeredGridLayoutManager);


瀑布流,就是每个item的宽高可能不一样

关于RecyclerView.LayoutManager就讲这些了。

相比ListView,RecyclerView没有类似

setOnItemClickListener

这种函数来监听item的点击事件。需要自己去设置监听例如:

  @Override        public void onBindViewHolder(final ViewHolder holder, int position) {            // - get element from your dataset at this position            // - replace the contents of the view with that element            Log.d("test","onBindViewHolder  holder="+holder+ " position="+position);            holder.View.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    //.....处理                }            });            holder.name.setText(mDataset.get(position).getName());            holder.number.setText(mDataset.get(position).getNumber());        }
这里的处理大家有不同处理,就不再说了

最后提几个RecyclerView.Adapter的API:

notifyDataSetChanged()  通知数据变化了 notifyItemChanged(int position) 某一个item发生变化了notifyItemInserted(int position) 在某个位置添加一个itemnotifyItemRemoved(int position)删除某个位置上的item
这里没演示了,添加数据和删除数据都有动画效果,通过下面这个函数可以添加自定义动画

setItemAnimator(RecyclerView.ItemAnimator animator)Sets the RecyclerView.ItemAnimator that will handle animations involving changes to the items in this RecyclerView.




0 0
原创粉丝点击