Android学习之RecyclerView

来源:互联网 发布:上海大学乐乎论坛圈子 编辑:程序博客网 时间:2024/06/05 18:41

概述

RecyclerView,提供了一种插拔式的体验,高度的解耦,异常的灵活,通过设置它的不同的LayoutManager,ItemDecoration,ItemAnimator实现各种效果

  • 想要控制其显示方法,请通过布局管理器LayoutManager
  • 想要控制Item间的间隔(可绘制),请通过ItemDecoration
  • 想要控制Item增删的动画,请通过ItemAnimator
  • 想要控制点击,长按事件,自己写去

基本使用

public class MainActivity extends AppCompatActivity {    RecyclerView recyclerView;    MyAdapter adapter;    List<String> data;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        init();        recyclerView = (RecyclerView)findViewById(R.id.recycler);        adapter = new MyAdapter(data,this);        recyclerView.setLayoutManager(new LinearLayoutManager(this));        //设置布局管理器        recyclerView.setAdapter(adapter);        //设置适配器    }    private  void init(){        data = new ArrayList<>();        for (int i = 'A'; i < 'z'; i++)        {            data.add("" + (char) i);        }    }}public class MyAdapter extends RecyclerView.Adapter<MyAdapter.myHolder> {    List<String> data ;    Context context;    public MyAdapter(List<String> data, Context context) {        this.data =data;        this.context = context;    }    @Override    public void onBindViewHolder(myHolder holder, int position) {            holder.textView.setText(data.get(position));        Log.d("onBind","aaaaaaaaaaa");    }    @Override    public int getItemCount() {        return data.size();    }    @Override    public myHolder onCreateViewHolder(ViewGroup parent, int viewType) {        myHolder holder = new myHolder(LayoutInflater.from(context).                inflate(R.layout.recycleritem,parent,false));        Log.d("onCreate","bbbbbbbbb");        return holder;    }    class  myHolder extends RecyclerView.ViewHolder{        TextView textView;        public myHolder(View itemView) {            super(itemView);            textView = (TextView) itemView.findViewById(R.id.text);        }    }}

在第一次创建item的时候,是先调用onCreateViewHolder再调用onBindViewHolder的,在滑动中,只要不是再创建新的,一直调用的是onBingViewHolder。

关于其他:

mRecyclerView = findView(R.id.id_recyclerview);//设置布局管理器mRecyclerView.setLayoutManager(layout);//设置adaptermRecyclerView.setAdapter(adapter)//设置Item增加、移除动画mRecyclerView.setItemAnimator(new DefaultItemAnimator());//添加分割线mRecyclerView.addItemDecoration(new DividerItemDecoration(                getActivity(), DividerItemDecoration.HORIZONTAL_LIST));

看上面例子的效果:
这里写图片描述

这样好丑,感觉item之间应该有分割线,可是RecyclerView并没有支持divider这样的属性
我们可以通过添加mRecyclerView.addItemDecoration()来添加分割线

该方法的参数为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);}@Deprecatedpublic void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {            outRect.set(0, 0, 0, 0); }

当我们调用recyclerview.addItemDecoration()方法添加decoration的时候,Recycler在绘制的时候,会去绘制decorator,即调用该类的onDraw和onDrawOver方法:

  • onDraw方法先于drawChildern
  • onDrawOver在drawChildern之后,一般我们选择复写一个即可
  • getItemOffset可以通过outRect.set()为每一个Item设置一定的偏移量,主要用户绘制Decorator。

下面是一个例子:

public class DividerItemDecaration 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 divider;    private int orientation;    public DividerItemDecaration(Context context,int orientation) {    //取默认的listdivider的属性        final TypedArray a = context.obtainStyledAttributes(ATTRS);       //得到这个属性        divider = a.getDrawable(0);        a.recycle();        setOrientation(orientation);    }    public void setOrientation(int orientation){        if(orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST){            throw  new IllegalArgumentException("invalid orientation");        }        this.orientation = orientation;    }    @Override    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {        if(orientation == VERTICAL_LIST){            drawVertical(c,parent);        }else {            drawHorizontal(c,parent);        }    }    public void drawVertical(Canvas c,RecyclerView parent){        final  int left = parent.getPaddingLeft();        final  int riht  = 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 +divider.getIntrinsicHeight();            divider.setBounds(left,top,riht,bottom);            divider.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 + divider.getIntrinsicHeight();            divider.setBounds(left, top, right, bottom);            divider.draw(c);        }    }    @Override    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {    //是向上偏移还是向左偏移        if(orientation == VERTICAL_LIST){            outRect.set(0,0,0,divider.getIntrinsicHeight());        }else {            outRect.set(0,0,divider.getIntrinsicWidth(),0);        }    }}

效果:
这里写图片描述

该实现类通过读取系统主题中的Android.R.attr.listDivider作为Item间的分割线,并且支持横向和纵向
获取到listdivider后,该属性的值是个Drawable,在getItemOffsets中,outRect设置绘制范围。onDraw中实现了绘制
然后在原来的代码中加:

mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL_LIST));

该分割线是系统默认的,我们可以在theme.xml中找到该属性的使用的情况,并且可以进行改变

<resources>    <!-- Base application theme. -->    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">        <!-- Customize your theme here. -->        <item name="colorPrimary">@color/colorPrimary</item>        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>        <item name="colorAccent">@color/colorAccent</item>        <item name="android:listDivider">@drawable/divider</item>    </style></resources>

在style中找一个item,叫android:listDivider
然后自己写一个drawable

<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android"    android:shape="rectangle" >    <gradient        android:centerColor="#ff00ff00"        android:endColor="#ff0000ff"        android:startColor="#ffff0000"        android:type="linear" />    <size android:height="4dp"/></shape>

这里写图片描述

LayoutManager

上面的例子是通过使用默认的LinearLayoutManager实现的
RecyclerView.LayoutManager,是一个抽象类,系统提供了三个实现类:

  1. LinearLyaoutManager 线性布局,支持横向,纵向
  2. GridLayoutManager 网格布局
  3. StaggeredGridLayoutManager 瀑布流布局

GridLayoutManager

 mRecyclerView.setLayoutManager(new GridLayoutManager(this,4));

重新写适合于这个的divider

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

这里写图片描述

注意,当开始我的左边的分割线怎么都画不出来,但是用系统默认的就可以,是因为在我的drawable文件下的画图,没有规定width,在规定width后,好了

<shape xmlns:android="http://schemas.android.com/apk/res/android"    android:shape="rectangle"    >    <gradient        android:startColor="#ffff0000"        android:centerColor="#ff00ff00"        android:endColor="#ff0000ff"        android:type="linear"/>    <size android:height="1dp"        android:width="1dp"        /></shape>

StaggeredGridLayoutManager

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

这种写法和上面是的效果是一致的,但是第二个参数传的是一个orientation,如果传入的是StaggeredGridLayoutManager.VERTICAL代表有多少列;那么传入的如果是StaggeredGridLayoutManager.HORIZONTAL就代表有多少行

如果改为:

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

这里写图片描述

可以进行左右滑动


我们都是固定了高度
现在我们再onBindViewHolder只能怪为我们的Item设置个随机高度

    @Override    public void onBindViewHolder(myHolder holder, int position) {            holder.textView.setText(data.get(position));        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) holder.textView.getLayoutParams();        params.height = size[position%6];    }

这里写图片描述

ItemAnimator

ItemAnimator也是个抽象类,好在系统为我们提供了一种默认的实现类

借助默认的实现,当Item添加和移除的时候,添加动画效果很简单

// 设置item动画recyclerView.setItemAnimator(new DefaultItemAnimator());

这里写图片描述

如果是GridLayoutManger:
这里写图片描述

注意,这里更新数据集不是用adapter.notifyDataSetChanged()而是
notifyItemInserted(position)与notifyItemRemoved(position)

为adapter添加两个方法:

public void addData(int position) {        mDatas.add(position, "Insert One");        notifyItemInserted(position);    }    public void removeData(int position) {            mDatas.remove(position);        notifyItemRemoved(position);    }

在Main中点击MenuItem触发:

 @Override    public boolean onCreateOptionsMenu(Menu menu)    {        getMenuInflater().inflate(R.menu.main, menu);        return super.onCreateOptionsMenu(menu);    }    @Override    public boolean onOptionsItemSelected(MenuItem item)    {        switch (item.getItemId())        {        case R.id.id_action_add:            mAdapter.addData(1);            break;        case R.id.id_action_delete:            mAdapter.removeData(1);            break;        }        return true;    }

Click and LingClick

recyclerview没有提供ClickListener和LongClickListener
不过我们可以自己添加
我选择通过adapter中提供回调
首先在Adaptet中定一个接口和方法

    public interface onItemClickListener{        void onClick(int position);        void LongClivk(int position);    }    public void SetOnItmeClickListener(onItemClickListener listener){        this.listener = listener;    }

然后在onBindViewHolder中为每一个Item设置onClickListener和OnLongClickListener

 @Override    public void onBindViewHolder(final myHolder holder, int position) {            holder.textView.setText(data.get(position));        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) holder.textView.getLayoutParams();        params.height = size[position%6];        if(listener != null){            holder.itemView .setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    listener.onClick(holder.getLayoutPosition());                }            });            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {                @Override                public boolean onLongClick(View v) {                    listener.LongClivk(holder.getLayoutPosition());                    return false;                }            });        }    }

然后在MainActivity中设置Listener

  adapter.SetOnItmeClickListener(new MyAdapter.onItemClickListener() {            @Override            public void onClick(int position) {                Toast.makeText(MainActivity.this,"onClick",Toast.LENGTH_SHORT).show();            }            @Override            public void LongClivk(int position) {                adapter.remove(position);            }        });

刚开始我对于画分割线充满了问号,下来自己解析解析,以画水平分割线为例:

 public void drawVertical(Canvas c,RecyclerView parent){        final  int left = parent.getPaddingLeft();        final  int riht  = 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 +divider.getIntrinsicHeight();            divider.setBounds(left,top,riht,bottom);            divider.draw(c);            Log.d("wnw",String.valueOf(top)+"  "+String.valueOf(bottom)+"  "+String.valueOf(left)+                    "   "+String.valueOf(riht) );        }    }

这里写图片描述

final View child = parent.getChildAt(i);得到的是LinearLayout整个布局。
left是图中的红点,我只是大概画了个位置,right是绿点,
确定了两个X轴上的点,现在是确定Y轴。
top的长度其实就是图中margin的长度,所以矩形第一个左上角的(left,top)就确定了
bottom就是top加上分割线的高度,所以矩形右下角的(right,bottom)就确定了。这样我们分割线画的位置和大小也确定了
然后再通过getItemOffsets()将整个LinearLayout布局向上平移分割线的高度,这样我们分割线的画的空间就有了,就可以画了

参考于:
http://blog.csdn.net/lmj623565791/article/details/45059587;
本文出自:【张鸿洋的博客】

原创粉丝点击