高级控件 RecyclerView 总结

RecyclerView 初识

  RecyclerView 是Android 5.0 materials design中的组件之一,其目标是替代广为使用的ListView.其有什么特点呢?从名字就可以看出个大概来 recycler(反复循环器),也就是RecyclerView 只负责子视图的回收和复用其他的统统交给用户,因此相较于ListView 其更加灵活可以定制更多的个性也更符合我们低耦合的编程思想。
  RecyclerView的功能甚是强大,虽然较ListView 实现起来略微复杂但怎么艺术般的控件绝对值得你我一试。

准备工作

  RecyclerView 放在 android-support-v7.jar 中,如果本地v7包中没有可以用sdkManager下载/更新Android Support Libraries(目前最新版本为 21)。如果被困在墙内无法更新,可以从网盘直接下载,加入到项目libs中。
  网盘地址:http://pan.baidu.com/s/1nuFYBRb

做个对比

ListView 我们已经比较熟悉,使用也较为简单

//设置适配器
listView.setAdapter(new MyAdapter(mContext));

RecyclerView 则需要较多的步骤

//设置适配器
mRecyclerView.setAdapter(new MyAdapter(mContext));
//设置布局样式
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
//设置分割线
mRecyclerView.addItemDecoration(new LinearItemDecoration(this, R.drawable.main_recycler_divider));
//设置默认的Item动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());

对照以上,本文将从以下几点详细介绍RecyclerView 的使用:

  • 继承RecyclerView.Adapter 实现自己的适配器
  • RecyclerView.LayoutManager抽象类的介绍
  • 实现RecyclerView.ItemDecoration抽象类绘制个性分割线
  • 好看的Item添加,删除动画

实现自己的适配器:

该部分并没有什么难点,不做过多解释

public class MainAdapter extends RecyclerView.Adapter<MainAdapter.ViewHolder>{

    private LayoutInflater layoutInflater;
    private ArrayList<ItemObj> datas;
    private Context context;
    public MainAdapter(Context context){
        this.context = context;
        this.layoutInflater = LayoutInflater.from(context);
        this.datas = new ArrayList<>();
        for(int i=0;i<15;i++){
            //通过getIdentifier获得资源id的时候文件名不要带后缀
            int imgId = context.getResources().getIdentifier("img_"+(i+1), "drawable", "com.ygl.road");
            ItemObj item = new ItemObj(String.valueOf(i+1), imgId);
            datas.add(item);
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        return new ViewHolder(layoutInflater.inflate(R.layout.main_recycler_item_layout, viewGroup, false));
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        viewHolder.textView.setText(datas.get(i).getText());
        viewHolder.imageView.setImageDrawable(ContextCompat.getDrawable(context, datas.get(i).getDrawableId()));
    }

    @Override
    public int getItemCount() {
        return datas.size();
    }
    /*当Item有多种类型的时候,通过此方法返回Item的类型*/
    @Override
    public int getItemViewType(int position) {
        return super.getItemViewType(position);
    }

    final class ViewHolder extends RecyclerView.ViewHolder{

       TextView textView;
       ImageView imageView;

       public ViewHolder(View itemView) {
           super(itemView);
           textView = (TextView) itemView.findViewById(R.id.main_recycler_item_text);
           imageView = (ImageView) itemView.findViewById(R.id.main_recycler_item_image);
       }
   }
}

LayoutManager快速的实现不同的布局样式:

  RecyclerView.LayoutManager 是一个抽象类,喜欢研究源码的童鞋可以查看一下源码,这个类主要是对item的大小和位置属性进行了计算,代码较为复杂。好在的是google给我们提供了三个实现类。
  

/*横竖显示,竖列显示时与ListView相似
 *context上下文 |item排列方向 | 是否倒序显示*/
 LinearLayoutManager llm = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);

/*以表格的形式显示,与GridView相似
 *context上下文 | 每行/列item个数 | item排列方向 | 是否倒序显示*/
 GridLayoutManager glm = new GridLayoutManager(this, 2, GridLayoutManager.VERTICAL, false);

/*通过设置item 的LayoutParams值可以实现瀑布样式的布局
 *每行/列item个数 | item排列方向*/
 StaggeredGridLayoutManager sglm = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);

RecyclerView通过setLayoutManager(LayoutManger)设置布局管理;
当使用普通的LinearLayoutManager时:

LinearLayoutManager llm = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
mRecyclerView.setLayoutManager(llm);




LinearLayoutManager

或许会感觉这很普通啊,ListView 实现起来分分钟钟的事,还搞的这么麻烦,不划算啊!!!
咱程序员可从不是舍近求远的人,来个大招瞅瞅!!!
只需要更改一行代码, 一秒钟变GridView

GridLayoutManager glm = new GridLayoutManager(this, 2, GridLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(glm);




GridLayoutManager

  如果感觉还是一般的话,可以试试改变LayoutManager的排列方向,立马让你横着滑。对于有竖屏竖着滑横屏横着滑的童鞋,RecyclerView 简直就是神器啊!!

ItemDecoration绘制个性分割线:

  用过ListView的童鞋都知道,在ListView中我们可以通过setDivider()简单粗暴的实现分割线,在为RecyclerView设置分割线的时候你会发现傲娇如他是不会让你就这么随随便便的设置的了。
  RecyclerView的内部抽象类ItemDecoration是我们实现分割线的关键。设置分割线大概思路是:根据ItemView的大小及dividerDrawable的宽,高,计算divider的宽高和ItemView的偏移值。细心的童鞋就会发现,绘制divider的时候其实是跟具体的Item内容是没有关系的。因此我们可以编写一个低耦合的divider计算类以便在以后可以方便的使用。
  


BaseItemDecoration是自定义的抽象类,他继承自RecyclerView.ItemDecoration,在其中实现一些公用方法

/**
 * Created by Ygl on 2016/8/1.
 * 绘制 RecyclerView 的分割线
 *
 * onDraw 优先于 drawChildren
 * onDrawOver 在 drawChildren 之后,一般重写其中一个即可
 * getItemOffsets 可以通过 outRect.set() 为每一个Item设置一定的偏移量
 */
public abstract class BaseItemDecoration extends RecyclerView.ItemDecoration{

    public static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL;
    public static final int VERTICAL = LinearLayoutManager.VERTICAL;

    protected Drawable mDivider;//分割线

    protected int orientation;//排列方向

    protected boolean isLastDraw;//是否绘制最后的边线

    public BaseItemDecoration(Context context, int drawableId){
        this(context, drawableId, VERTICAL);
    }

    public BaseItemDecoration(Context context, int drawableId, int orientation){
        if(orientation != HORIZONTAL && orientation != VERTICAL){
            throw new IllegalArgumentException("invalid orientation,you can choose 'HORIZONTAL_LIST' or 'VERTICAL_LIST'");
        }
        this.orientation = orientation;
        this.mDivider = ContextCompat.getDrawable(context, drawableId);
        this.isLastDraw = true;
    }

    /**
     * 是否绘制最后的边线
     * @param isLastDraw
     */
    public void isLastDraw(boolean isLastDraw){
        this.isLastDraw = isLastDraw;
    }

    protected void drawDivider(int left, int top,int right,int bottom, Canvas c){
        mDivider.setBounds(left, top, right, bottom);
        mDivider.draw(c);
    }
    protected abstract void drawHorizontal(Canvas c, RecyclerView parent);
    protected abstract void drawVertical(Canvas c, RecyclerView parent);
}

  接下来实现一个具体的分割线类 LinearItemDecoration,在采用ListView的样式时就可以直接拿来用了。这段代码的重点是计算一下要绘制的分割线的宽度和高度。如果只是看着不好懂得话,小伙伴们可以拿出笔和纸画一画还是很好理解的。代码有点长,不过有了上一个的经验这个还是很好理解的。其与上一个的主要区别在于,上一个只需要绘制一边,而在网格中则需要绘制底边和右边。

public class LinearItemDecoration extends BaseItemDecoration{

    public LinearItemDecoration(Context context, int dividerId){
        super(context, dividerId);
    }
    public LinearItemDecoration(Context context, int dividerId, int orientation){
        super(context, dividerId, orientation);
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        if(orientation == HORIZONTAL){
            drawHorizontal(c, parent);
        }else if(orientation == VERTICAL){
            drawVertical(c, parent);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        /*横向排列时,将childView 的 rightPadding 加上 divider的宽度
           如果不加,分割线视图会被覆盖*/
        if(orientation == HORIZONTAL){
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }else if(orientation == VERTICAL){
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        }
    }
    /*若采用纵向的排列方式*/
    protected void drawVertical(Canvas c, RecyclerView parent){
        if(c == null || parent == null){
            return;
        }
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth()-parent.getPaddingRight();
        int childCount = parent.getChildCount();
        if(!isLastDraw)childCount--;
        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 + mDivider.getIntrinsicHeight();
            drawDivider(left, top, right, bottom, c);
        }
    }
    protected void drawHorizontal(Canvas c, RecyclerView parent){
        if(c== null || parent == null)return;
        final int top = parent.getPaddingTop();
        final int bottom = parent.getHeight()-parent.getPaddingBottom();
        int childCount = parent.getChildCount();
        if(!isLastDraw)childCount--;
        for(int i=0;i<childCount;i++){
            final View childView = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) childView.getLayoutParams();
            final int left = childView.getRight()+params.rightMargin;
            final int right = left+mDivider.getIntrinsicWidth();
            drawDivider(left, top, right, bottom, c);
        }
    }
}

  RecyclerView 的功能可不仅仅是实现一个ListView样式那么简单啊!!网格,瀑布什么的显然我们上边实现的这个divider不是很适用,我们再来写一个用于网格的divider.

public class GridItemDecoration extends BaseItemDecoration {

    public GridItemDecoration(Context context, int dividerId) {
        super(context, dividerId);
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        drawHorizontal(c, parent);
        drawVertical(c, parent);
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int spanCount = getSpanCount(parent);
        int childCount = parent.getAdapter().getItemCount();
        if(isLastRaw(parent, ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewPosition(), spanCount, childCount)){
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }else if(isLastColum(parent, ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewPosition(), spanCount, childCount)){
            outRect.set(0 ,0 ,0, mDivider.getIntrinsicHeight());
        }else{
            outRect.set(0,0,mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
        }
    }

    //获得列数
    private int getSpanCount(RecyclerView parent){
        RecyclerView.LayoutManager manager = parent.getLayoutManager();
        if(manager instanceof GridLayoutManager){
            return ((GridLayoutManager)manager).getSpanCount();
        }else if(manager instanceof StaggeredGridLayoutManager){
            return ((StaggeredGridLayoutManager)manager).getSpanCount();
        }
        return -1;
    }
    //是否为最后一列,最后一列不需要绘制右边
    private boolean isLastColum(RecyclerView parent, int pos, int spanCount, int childCount){
        RecyclerView.LayoutManager manager = parent.getLayoutManager();
        if(manager instanceof GridLayoutManager){
            if((pos+1)%spanCount == 0)return true;
        }else if(manager instanceof StaggeredGridLayoutManager){
            int orientation = ((StaggeredGridLayoutManager)manager).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){
        RecyclerView.LayoutManager manager = parent.getLayoutManager();
        if(manager instanceof GridLayoutManager){
            childCount = childCount - childCount%spanCount;
            if(pos > childCount)return true;
        }else if(manager instanceof StaggeredGridLayoutManager){
            int orientation = ((StaggeredGridLayoutManager)manager).getOrientation();
            if(orientation == StaggeredGridLayoutManager.VERTICAL){
                childCount = childCount - childCount%spanCount;
                if(pos > childCount)return true;
            }else{
                if((pos+1) % spanCount == 0)return true;
            }
        }
        return false;
    }
    protected 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();
            drawDivider(left, top, right, bottom, c);
        }
    }
    protected void drawVertical(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.getRight()+params.rightMargin;
            final int right = left+mDivider.getIntrinsicWidth();
            final int top = child.getTop()-params.topMargin;
            final int bottom = child.getBottom()+params.bottomMargin;
            drawDivider(left, top, right, bottom, c);
        }
    }
}



怎么能少得了动画呢?

  在RecyclerView 中可以设置Item的添加,移除动画,通过设置 ItemAnimator。ItemAnimator 是一个抽象类,不过不用担心,google已经为我们实现了一个默认ItemAnimator 效果还是很不错的基本可以满足平时编程的需求,而且在github上还有许多关于ItemAnimation的项目,感兴趣的童鞋可以搜搜看看。
  通过以下代码就可以设置默认动画了。

mRecyclerView.setItemAnimator(new DefaultItemAnimator());



结束语

需要源码的童鞋可以在git上下载:https://git.coding.net/ygl12337/android_road.git
首次写博客肯定有很多不足的地方,望有缘看到的网友多多指出,多多支持。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值