Android学习之RecyclerView

概述

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);
//设置adapter
mRecyclerView.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);
}

@Deprecated
public 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
本文出自:【张鸿洋的博客】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值