RecyclerView更新与原理

RecyclerView更新与原理之基本使用和分割线解析

说个外话:启动页 并不是简简单单的等几秒钟进入主页,app的启动速度跟app启动页关系很大
可以用命令去检测启动速度,(热启动和冷启动)怎么去优化和加速应用的启动速度

思考一个问题:如果不在androidmanifest.xml配置Activity,启动该Activity会不会报错
仔细阅读源码Activity的启动流程,那么是可以解决这个问题的,就能够用到app的插件开发

1.来源

RecyclerView是谷歌V7包下的,用来替代ListVIew和GridView使用的一个控件。在使用的过程中,往往要用到divider(item之间的分割线)。RecyclerVIew并不像ListView一样自带苏醒。而是需要用到(support 25已加入分割线方法)RecyclerView.ItemDecoration这样的类,但是ItemDecoration是一个抽象类,而且android内部并没有给他做一些效果的实现。那么就需要我们自己去继承并实现其中的方法本文讲述的就是在GridLayoutManager和LinearLayoutManager下如何去实现ItemDecoration。至于RecyclerView.ItemDecoration的具体分析,大家可以去看看这篇文章

http://blog.piasy.com/2016/03/26/Insight-Android-RecyclerView-ItemDecoration/ 这里不作过多的阐述。
那么有了ListView、GridView为什么还需要RecyclerView这样的控件呢?整体上看RecyclerView架构,提供了一种插拔式的体验,高度的解耦,异常的灵活,通过设置它提供的不同LayoutManager,ItemDecoration , ItemAnimator实现令人瞠目的效果。

1.你想要控制其显示的方式,请通过布局管理器LayoutManager,ListView–>GridView–>瀑布流 只需要一行代码;
2.你想要控制Item间的间隔(可绘制),请通过ItemDecoration(这个比较蛋疼) ;
3.你想要控制Item增删的动画,请通过ItemAnimator;
4.你想要控制点击、长按事件,请自己写(这点尼玛)。

2基本使用 

首先我们需要添加RecyclerView的依赖包,在build.gradle中添加依赖:

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

 // 设置recyclerView的布局管理  
// LinearLayoutManager -> ListView风格
// GridLayoutManager -> GridView风格
// StaggeredGridLayoutManager -> 瀑布流风格
LinearLayoutManager linearLayoutManager = new 
      LinearLayoutManager(this);
mRecyclerView.setLayoutManager(linearLayoutManager);

 和ListView一样通过设置Adapter()给他绑定数据源,但在这之前必须设置setLayoutManager()这个方法是用来设置显示效果的(这样我们就可以通过设置布局管理显示不同的效果:ListView、GridView、瀑布流等等)。

2.1 Adapter编写

接下来我们先去后台服务器请求数据,然后我们来编写Adapter,我们直接使用Okhttp获取服务器数据,在这里就不多说了,我们主要看Adapter怎么写。在ListView和GridView中我们可以不用ViewHolder,反正没人打我,只是数据特别多可能会崩溃而已;但是在RecyclerView中就不一样了它强制我们使用ViewHolder:

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

private List<ChannelListResult.DataBean.CategoriesBean.CategoryListBean> mList;
private Context mContext;
private LayoutInflater mInflater;

public CategoryListAdapter(Context context, List<ChannelListResult.DataBean.CategoriesBean.CategoryListBean> list) {
    this.mContext = context;
    this.mList = list;
    this.mInflater = LayoutInflater.from(mContext);
}

/**
      创建条目ViewHolder

  @param parent   RecyclerView
  @param viewType view的类型可以用来显示多列表布局等等
  @return
 **/
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    // 创建条目
    View itemView = mInflater.inflate(R.layout.channel_list_item, parent, false);
    // 创建ViewHolder
    ViewHolder viewHolder = new ViewHolder(itemView);
    return viewHolder;
}

/**
  绑定ViewHolder设置数据

  @param holder
  @param position 当前位置
 **/
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    // 设置绑定数据
    ChannelListResult.DataBean.CategoriesBean.CategoryListBean item = mList.get(position);
    holder.nameTv.setText(item.getName());
    holder.channelTopicTv.setText(item.getIntro());
    String str = item.getSubscribe_count() + " 订阅 | " +
            "总帖数 <font color='#FF678D'>" + item.getTotal_updates() + "</font>";
    holder.channelUpdateInfo.setText(Html.fromHtml(str));
    // 是否是最新
    if (item.isIs_recommend()) {
        holder.recommendLabel.setVisibility(View.VISIBLE);
    } else {
        holder.recommendLabel.setVisibility(View.GONE);
    }
    // 加载图片
    Glide.with(mContext).load(item.getIcon_url()).centerCrop().into(holder.channelIconIv);
}

/**
  总共有多少条数据
 **/
@Override
public int getItemCount() {
    return mList.size();
}

/**
  RecyclerView的Adapter需要一个ViewHolder必须要    extends RecyclerView.ViewHolder
 **/
public static class ViewHolder extends RecyclerView.ViewHolder {
    public TextView nameTv;
    public TextView channelTopicTv;
    public TextView channelUpdateInfo;
    public View recommendLabel;
    public ImageView channelIconIv;

    public ViewHolder(View itemView) {
        super(itemView);
        // 在创建的时候利用传递过来的View去findViewById
        nameTv = (TextView) itemView.findViewById(R.id.channel_text);
        channelTopicTv = (TextView) itemView.findViewById(R.id.channel_topic);
        channelUpdateInfo = (TextView) itemView.findViewById(R.id.channel_update_info);
        recommendLabel = itemView.findViewById(R.id.recommend_label);
        channelIconIv = (ImageView) itemView.findViewById(R.id.channel_icon);
    }
}
}

这只是用来测试一下,并不是我们所需要的效果,只是说一下这两个方法都可以干什么就是你想怎么弄分割线就怎么弄。我们一般会使用Drawable去画,所以我们必须调整成我们最终的效果,代码其实基本一致。

public class CategoryItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;

public CategoryItemDecoration(Drawable divider) {
    // 利用Drawable绘制分割线
    mDivider = divider;
}

@Override
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
    int childCount = parent.getChildCount();
    // 计算需要绘制的区域
    Rect rect = new Rect();
    rect.left = parent.getPaddingLeft();
    rect.right = parent.getWidth() - parent.getPaddingRight();
    for (int i = 0; i < childCount; i++) {
        View childView = parent.getChildAt(i);
        rect.top = childView.getBottom();
        rect.bottom = rect.top + mDivider.getIntrinsicHeight();
        // 直接利用Canvas去绘制
        mDivider.draw(canvas);
    }
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    // 在每个子View的下面留出来画分割线
    outRect.bottom += mDivider.getIntrinsicHeight();
}

}

2.2 分隔线定制

对于分隔线这个也比较蛋疼,你会发现RecyclerView并没有支持divider这样的属性。那么怎么办,你可以在创建item布局的时候直接写在布局中,当然了这种方式不够优雅,我们早就说了可以自由的去定制它。
  既然比较麻烦那么我们可以去github上面下载一个:DividerItemDecoration来参考一下。我这里就直接来写一个效果,大家也可以找找别人的博客看看。
  分割线我们利用RecyclerView的addItemDecoration(ItemDecoration fromHtml) 新建一个类来看看到底是什么:

public class CategoryItemDecoration extends    RecyclerView.ItemDecoration {
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {

}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

}
}


public class CategoryItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;

public CategoryItemDecoration(int color) {
    // 直接绘制颜色  只是用来测试
    mPaint = new Paint();
    mPaint.setColor(color);
    mPaint.setAntiAlias(true);
}

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    int childCount = parent.getChildCount();
    // 获取需要绘制的区域
    Rect rect = new Rect();
    rect.left = parent.getPaddingLeft();
    rect.right = parent.getWidth() - parent.getPaddingRight();
    for (int i = 0; i < childCount; i++) {
        View childView = parent.getChildAt(i);
        rect.top = childView.getBottom();
        rect.bottom = rect.top + 20;
        // 直接利用Canvas去绘制一个矩形 在留出来的地方
        c.drawRect(rect, mPaint);
    }
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    // 在每个子View的下面留出20px来画分割线
    outRect.bottom += 20;
}

}

有两个方法getItemOffsets()这里我一般指定偏移量就可以了,就是分割线占多少高度,或者说是画在什么位置,你总的给我留出位置来;
onDraw()我们可以直接去绘制,绘制什么都可以因为有Canvas ,但一般都是绘制Drawable,这里不多说具体看视频吧。

public class CategoryItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;

public CategoryItemDecoration(int color) {
    // 直接绘制颜色  只是用来测试
    mPaint = new Paint();
    mPaint.setColor(color);
    mPaint.setAntiAlias(true);
}

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    int childCount = parent.getChildCount();
    // 获取需要绘制的区域
    Rect rect = new Rect();
    rect.left = parent.getPaddingLeft();
    rect.right = parent.getWidth() - parent.getPaddingRight();
    for (int i = 0; i < childCount; i++) {
        View childView = parent.getChildAt(i);
        rect.top = childView.getBottom();
        rect.bottom = rect.top + 20;
        // 直接利用Canvas去绘制一个矩形 在留出来的地方
        c.drawRect(rect, mPaint);
    }
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    // 在每个子View的下面留出20px来画分割线
    outRect.bottom += 20;
}

}


这只是用来测试一下,并不是我们所需要的效果,只是说一下这两个方法都可以干什么就是你想怎么弄分割线就怎么弄。我们一般会使用Drawable去画,所以我们必须调整成我们最终的效果,代码其实基本一致。
public class CategoryItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;

public CategoryItemDecoration(Drawable divider) {
    // 利用Drawable绘制分割线
    mDivider = divider;
}

@Override
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
    int childCount = parent.getChildCount();
    // 计算需要绘制的区域
    Rect rect = new Rect();
    rect.left = parent.getPaddingLeft();
    rect.right = parent.getWidth() - parent.getPaddingRight();
    for (int i = 0; i < childCount; i++) {
        View childView = parent.getChildAt(i);
        rect.top = childView.getBottom();
        rect.bottom = rect.top + mDivider.getIntrinsicHeight();
        // 直接利用Canvas去绘制
        mDivider.draw(canvas);
    }
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    // 在每个子View的下面留出来画分割线
    outRect.bottom += mDivider.getIntrinsicHeight();
}

}

接下来我们就可以在drawable,下面新建一个xxx.xml的分割线文件了

<?xml version="1.0" encoding="utf-8"?>




 基本没什么效果因为分割线比较小0.5dp,仔细看还是看得出来的,最后声明一下因为整个项目涉及到一键更换皮肤,那么恭喜恭喜这样做并没什么卵用,我们还是得直接写在item布局中,而不是用代码设置分割线。

3自定义RecyclerView.ItemDecoration,实现Item的等间距分割以及分割线效果

3.1实现基本的Item的divider

3.1.1创建SpacesItemDecoration

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
private int leftRight;
private int topBottom;

public SpacesItemDecoration(int leftRight, int topBottom) {
    this.leftRight = leftRight;
    this.topBottom = topBottom;
}

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

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

}}

在这里我们主要实现的方法是onDraw和getItemOffsets,getItemOffsets重要确定divider的范围,而onDraw是对divider的具体实现。

4.2 LinearLayoutManager下divider的实现

首先在getItemOffsets方法中需要判断当前的RecyclerView所采用的哪种LayoutManager。这里要注意的是GridLayoutManager是继承LinearLayoutManager的,所以需要先判断是否为GridLayoutManager。

private SpacesItemDecorationEntrust getEntrust(RecyclerView.LayoutManager manager) {
    SpacesItemDecorationEntrust entrust = null;
    //要注意这边的GridLayoutManager是继承LinearLayoutManager,所以要先判断GridLayoutManager
    if (manager instanceof GridLayoutManager) {
        entrust = new GridEntrust(leftRight, topBottom, mColor);
    } else {//其他的都当做Linear来进行计算
        entrust = new LinearEntrust(leftRight, topBottom, mColor);
    }
    return entrust;
}

然后我们来看具体的实现,首先判断是VERTICAL还是HORIZONTAL。对于VERTICAL,每一个item必需的是top,left和right,但是最后一个item还需要bottom。而对于HORIZONTAL,每一个item必需的是top,left和bottom,但是最后一个item还需要right。

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
    //竖直方向的
    if (layoutManager.getOrientation() == LinearLayoutManager.VERTICAL) {
        //最后一项需要 bottom
        if (parent.getChildAdapterPosition(view) == layoutManager.getItemCount() - 1) {
            outRect.bottom = topBottom;
        }
        outRect.top = topBottom;
        outRect.left = leftRight;
        outRect.right = leftRight;
    } else {
        //最后一项需要right
        if (parent.getChildAdapterPosition(view) == layoutManager.getItemCount() - 1) {
            outRect.right = leftRight;
        }
        outRect.top = topBottom;
        outRect.left = leftRight;
        outRect.bottom = topBottom;
    }
}

就这样,divider效果就实现了(当然是没有任何的颜色的)。调用方式只需要。

int leftRight = dip2px(7);
  int topBottom = dip2px(7);
  rv_content.addItemDecoration(new SpacesItemDecoration(leftRight, topBottom));

当然你也可以使用多个ItemDecoration

 int leftRight = dip2px(10);
  int topBottom = dip2px(10);
  rv_content.addItemDecoration(new SpacesItemDecoration(leftRight, topBottom));
  rv_content.addItemDecoration(new SpacesItemDecoration(dip2px(2), dip2px(2), getResources().getColor(R.color.colorPrimary)));

4.2 GridManager下的实现

 GridManager下的实现的步骤类似与LinearManager,不同的是确定绘制分割线的区域。它的分割线的区域是相邻的item之间都需要有分割线。废话不多说,先上代码。
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
if (mDivider == null || layoutManager.getChildCount() == 0) {
return;
}
//判断总的数量是否可以整除
int totalCount = layoutManager.getItemCount();
int surplusCount = totalCount % layoutManager.getSpanCount();

    int left;
    int right;
    int top;
    int bottom;

    final int childCount = parent.getChildCount();
    if (layoutManager.getOrientation() == GridLayoutManager.VERTICAL) {

        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            //得到它在总数里面的位置
            final int position = parent.getChildAdapterPosition(child);
            //将带有颜色的分割线处于中间位置
            final float centerLeft = (layoutManager.getLeftDecorationWidth(child) - leftRight) / 2;
            final float centerTop = (layoutManager.getTopDecorationHeight(child) - topBottom) / 2;
            //是否为最后一排
            boolean isLast = surplusCount == 0 ?
                    position > totalCount - layoutManager.getSpanCount() - 1 :
                    position > totalCount - surplusCount - 1;
            //画下边的,最后一排不需要画
            if ((position + 1) % layoutManager.getSpanCount() == 1 && !isLast) {
                //计算下边的
                left = layoutManager.getLeftDecorationWidth(child);
                right = parent.getWidth() - layoutManager.getLeftDecorationWidth(child);
                top = (int) (child.getBottom() + params.bottomMargin + centerTop);
                bottom = top + topBottom;
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
            }
            //画右边的,能被整除的不需要右边,并且当数量不足的时候最后一项不需要右边
            boolean first = totalCount > layoutManager.getSpanCount() && (position + 1) % layoutManager.getSpanCount() != 0;
            boolean second = totalCount < layoutManager.getSpanCount() && position + 1 != totalCount;
            if (first || second) {
                //计算右边的
                left = (int) (child.getRight() + params.rightMargin + centerLeft);
                right = left + leftRight;
                top = child.getTop() + params.topMargin;
                //第一排的不需要上面那一丢丢
                if (position > layoutManager.getSpanCount() - 1) {
                    top -= centerTop;
                }
                bottom = child.getBottom() - params.bottomMargin;
                //最后一排的不需要最底下那一丢丢
                if (!isLast) {
                    bottom += centerTop;
                }
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
            }
        }
    } else {

        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            //得到它在总数里面的位置
            final int position = parent.getChildAdapterPosition(child);
            //将带有颜色的分割线处于中间位置
            final float centerLeft = (layoutManager.getLeftDecorationWidth(child) - leftRight) / 2;
            final float centerTop = (layoutManager.getTopDecorationHeight(child) - topBottom) / 2;
            //是否为最后一排
            boolean isLast = surplusCount == 0 ?
                    position > totalCount - layoutManager.getSpanCount() - 1 :
                    position > totalCount - surplusCount - 1;
            //画右边的,最后一排不需要画
            if ((position + 1) % layoutManager.getSpanCount() == 1 && !isLast) {
                //计算右边的
                left = (int) (child.getRight() + params.rightMargin + centerLeft);
                right = left + leftRight;
                top = layoutManager.getTopDecorationHeight(child);
                bottom = parent.getHeight() - layoutManager.getTopDecorationHeight(child);
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
            }
            boolean first = totalCount > layoutManager.getSpanCount() && (position + 1) % layoutManager.getSpanCount() != 0;
            boolean second = totalCount < layoutManager.getSpanCount() && position + 1 != totalCount;
            //画下边的,能被整除的不需要下边
            if (first || second) {
                left = child.getLeft() + params.leftMargin;
                if (position > layoutManager.getSpanCount() - 1) {
                    left -= centerLeft;
                }
                right = child.getRight() - params.rightMargin;
                if (!isLast) {
                    right += centerLeft;
                }
                top = (int) (child.getBottom() + params.bottomMargin + centerTop);
                bottom = top + topBottom;
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
            }
        }
    }
}

我们就VERTICAL的情况下来进行分析,首先横向的分割线,只需要在最左侧的item绘制出来的时候进行分割线的绘制就行了。当然最后一排是不需要的。

if ((position + 1) % layoutManager.getSpanCount() == 1 && !isLast) {
                //计算下边的
left = layoutManager.getLeftDecorationWidth(child);
right = parent.getWidth() - layoutManager.getLeftDecorationWidth(child);
top = (int) (child.getBottom() + params.bottomMargin + centerTop);
bottom = top + topBottom;
mDivider.setBounds(left, top, right, bottom); 
mDivider.draw(c);

}

水平的分割线的计算方式类似与LinearLayoutManager下的计算方式。这里不过多阐述。而竖直方向的会有一些区别。由于GridLayoutManager下,item的数量不一定能够刚好整除每排的数量。所以这边的绘制区域是根据每个item来进行确定的。
  能被整除的或者当数量不足的时候最后一项不需要竖直的分割线。同时要注意补齐centerTop(分割线绘制在中间区域的位置)。

 //画右边的,能被整除的不需要右边,并且当数量不足的时候最后一项不需要右边
  boolean first = totalCount > layoutManager.getSpanCount() && (position + 1) % layoutManager.getSpanCount() != 0;
  boolean second = totalCount < layoutManager.getSpanCount() && position + 1 != totalCount;
 if (first || second) {
 //计算右边的
    left = (int) (child.getRight() + params.rightMargin + centerLeft);
    right = left + leftRight;
    top = child.getTop() + params.topMargin;
    //第一排的不需要上面那一丢丢
if (position > layoutManager.getSpanCount() - 1) {
      top -= centerTop;
   }
bottom = child.getBottom() - params.bottomMargin;
 //最后一排的不需要最底下那一丢丢
    if (!isLast) {
   bottom += centerTop;
   }
    mDivider.setBounds(left, top, right, bottom);
    mDivider.draw(c);
 }

 HORIZONTAL下的情况可以进行类似的分析,代码的调用方式跟LinearLayoutManager下是一样的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值