首页 > 代码库 > 艺术控件RecyclerView的分隔线&bug解决

艺术控件RecyclerView的分隔线&bug解决

前言

        RecyclerView是Google在support-v7里面添加的控件,是5.0 Material Design模式下的一员,在众多的App中使用非常频繁,之前是ListView现在是RecyclerView,想比之下RecyclerView更加的灵活,高内聚低耦合,将ListView功能进行了拆分,各个类各司其职构成了现在的RecyclerView。

效果~

技术分享

Part 1、LinearLayoutAppCompat源码分析

在使用RecyclerView的分割线之前,不得不介绍一下LinearLayoutAppCompat,此控件是support-v7提供的,你可以通过在xml里面配置实现每个子孩子下面都会有个分割线

效果~

技术分享

[java] view plain copy 技术分享技术分享
  1. app:divider="@drawable/divider_vertical_holo_light"  
  2. app:showDividers="middle"  
tips:

1、app:divider=""  设置分割线的drawable

2、app:showDivider=""  设置显示分割线(none、begining、end、middle)

源码分析

LinearLayoutAppCompat继承于ViewGroup,都会调用onMeasure、onLayout、onDraw方法,通过查看可知绘制分割线的代码在onDraw方法里面

[java] view plain copy 技术分享技术分享
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     if (mDivider == null) {  
  4.         return;  
  5.     }  
  6.   
  7.     if (mOrientation == VERTICAL) {  
  8.         drawDividersVertical(canvas);  
  9.     } else {  
  10.         drawDividersHorizontal(canvas);  
  11.     }  
  12. }  
这里我们只需要看drawDividersVertical的方法即可,drawDividersHorizontal绘制差不多
[java] view plain copy 技术分享技术分享
  1. void drawDividersVertical(Canvas canvas) {  
  2.     final int count = getVirtualChildCount();  
  3.     for (int i = 0; i < count; i++) {  
  4.         final View child = getVirtualChildAt(i);  
  5.   
  6.         if (child != null && child.getVisibility() != GONE) {  
  7.             if (hasDividerBeforeChildAt(i)) {  
  8.                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();  
  9.                 final int top = child.getTop() - lp.topMargin - mDividerHeight;  
  10.                 drawHorizontalDivider(canvas, top);  
  11.             }  
  12.         }  
  13.     }  
  14.   
  15.     if (hasDividerBeforeChildAt(count)) {  
  16.         final View child = getVirtualChildAt(count - 1);  
  17.         int bottom = 0;  
  18.         if (child == null) {  
  19.             bottom = getHeight() - getPaddingBottom() - mDividerHeight;  
  20.         } else {  
  21.             final LayoutParams lp = (LayoutParams) child.getLayoutParams();  
  22.             bottom = child.getBottom() + lp.bottomMargin;  
  23.         }  
  24.         drawHorizontalDivider(canvas, bottom);  
  25.     }  
  26. }  
tips:

1、绘制过程:第一个Child的顶部分割线->最后一个Child顶部分割线->最后一个Child底部分割线

2、通过判断hasDividerBeforeChildAt()方法来判断(none、begining、end、middle)


Part 2、实现RecyclerView线性方向间隔线

因为RecyclerView没有提供分割线,所以我们需要自己来进行添加

[java] view plain copy 技术分享技术分享
  1. public class MyItemDecoration extends RecyclerView.ItemDecoration {  
  2.     @Override  
  3.     public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {  
  4.     }  
  5.   
  6.     @Override  
  7.     public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {  
  8.         super.onDraw(c, parent, state);  
  9.     }  
  10.     @Override  
  11.     public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {  
  12.         super.onDrawOver(c, parent, state);  
  13.     }  
  14. }  
tips:

1、回调先后顺序:getItemOffsets()->onDraw()->onDrawOver()

2、getItemOffsets()   获得每个Item的偏移量   onDraw() : 绘制分割线   onDrawOver():绘制在Item的上层(也可以将绘制分割线放在此方法里面)

偏移量具体表示:

技术分享

所以在getItemOffsets方法应该这样写

[java] view plain copy 技术分享技术分享
  1. @Override  
  2. public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {  
  3.     //获得每个Item条目的偏移量  
  4.     if(orientation == LinearLayoutManager.VERTICAL){  
  5.         outRect.set(0,0,0,divide.getIntrinsicHeight());  
  6.     }else{  
  7.         outRect.set(0,0,divide.getIntrinsicWidth(),0);  
  8.     }  
  9. }  
然后就是在onDraw里面进行绘制分割线
[java] view plain copy 技术分享技术分享
  1. /** 
  2.  * 方向为竖直方向进行绘制 
  3.  * 
  4.  * @param c 
  5.  * @param parent 
  6.  */  
  7. private void drawableVertical(Canvas c, RecyclerView parent) {  
  8.     int count = parent.getChildCount();  
  9.     int left = parent.getPaddingLeft();  
  10.     int right = parent.getWidth() - parent.getPaddingRight();  
  11.     for (int i = 0; i < count; i++) {  
  12.         View child = parent.getChildAt(i);  
  13.         RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();  
  14.         int top = child.getBottom() + params.bottomMargin+(int)ViewCompat.getTranslationY(child);  
  15.         int bottom = top + divide.getIntrinsicHeight();  
  16.         divide.setBounds(left, top, right, bottom);//为分割线确定绘制的位置  
  17.         divide.draw(c);//绘制  
  18.     }  
  19. }  

tips:

1、ViewCompat.getTranslationY(child) : 考虑动画变换的影响应该加上这个


Part 3、RecyclerView的网格分割线

思路:定义两个方法,一个画横线一个画竖线

so,getItemOffsets()方法

[java] view plain copy 技术分享技术分享
  1. @Override  
  2. @Deprecated  
  3. public void getItemOffsets(Rect outRect, int itemPosition,  
  4.                            RecyclerView parent) {  
  5.     // 四个方向的偏移值  
  6.     int right = divide.getIntrinsicWidth();  
  7.     int bottom = divide.getIntrinsicHeight();  
  8.     outRect.set(00, right, bottom);  
  9.   
  10. }  
onDraw方法
[java] view plain copy 技术分享技术分享
  1. private void drawHorizontal(Canvas c, RecyclerView parent) {  
  2.     int count = parent.getChildCount();  
  3.     for (int i = 0; i < count; i++) {  
  4.         View childView = parent.getChildAt(i);  
  5.         RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) childView.getLayoutParams();  
  6.         int left = childView.getLeft() - params.leftMargin;  
  7.         int right = childView.getRight() + params.rightMargin;  
  8.         int top = childView.getBottom() + params.bottomMargin;  
  9.         int bottom = top + divide.getIntrinsicHeight();  
  10.         divide.setBounds(left, top, right, bottom);  
  11.         divide.draw(c);  
  12.     }  
  13. }  
  14.   
  15. private void drawVertical(Canvas c, RecyclerView parent) {  
  16.     int count = parent.getChildCount();  
  17.     for (int i = 0; i < count; i++) {  
  18.         View childView = parent.getChildAt(i);  
  19.         RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) childView.getLayoutParams();  
  20.         int left = childView.getRight() + params.rightMargin;  
  21.         int right = left + divide.getIntrinsicWidth();  
  22.         int top = childView.getTop() - params.topMargin;  
  23.         int bottom = childView.getBottom() + params.bottomMargin;  
  24.         divide.setBounds(left, top, right, bottom);  
  25.         divide.draw(c);  
  26.     }  
  27. }  
具体意思很容易理解
效果~

技术分享

在效果图中可以发现,顶部和左侧没有线,下部和右侧有线。那怎么样才能去掉下部和右侧的线呢?这里有个好方法便是在分配Item便宜量的时候对position进行判断,符合则下部和右侧条件则将bottom和right置为0即可,代码如下

[java] view plain copy 技术分享技术分享
  1. @Override  
  2. @Deprecated  
  3. public void getItemOffsets(Rect outRect, int itemPosition,  
  4.                            RecyclerView parent) {  
  5.     // 四个方向的偏移值  
  6.     int right = divide.getIntrinsicWidth();  
  7.     int bottom = divide.getIntrinsicHeight();  
  8.     if (isLastColum(itemPosition, parent)) {//是否是最后一列  
  9.         right = 0;  
  10.     }  
  11.     if (isLastRow(itemPosition, parent)) {//是最后一行  
  12.         bottom = 0;  
  13.     }  
  14.     outRect.set(00, right, bottom);  
  15. }  
判断是否为最后一列
[java] view plain copy 技术分享技术分享
  1. /** 
  2.  * 判断是否是最后一列 
  3.  * 
  4.  * @param itemPosition 
  5.  * @param parent 
  6.  * @return 
  7.  */  
  8. private boolean isLastColum(int itemPosition, RecyclerView parent) {  
  9.     RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();  
  10.     //有多少列  
  11.     if (layoutManager instanceof GridLayoutManager) {  
  12.         int spanCount = getSpanCount(parent);  
  13.         if ((itemPosition + 1) % spanCount == 0) {//因为是从0可以所以要将ItemPosition先加1  
  14.             return true;  
  15.         }  
  16.     }  
  17.     return false;  
  18. }  
判断是否为最后一行
[java] view plain copy 技术分享技术分享
  1. /** 
  2.  * 是否是最后一行 
  3.  * 
  4.  * @param itemPosition 
  5.  * @param parent 
  6.  * @return 
  7.  */  
  8. private boolean isLastRow(int itemPosition, RecyclerView parent) {  
  9.     int spanCount = getSpanCount(parent);  
  10.     RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();  
  11.     //有多少列  
  12.     if (layoutManager instanceof GridLayoutManager) {  
  13.         int childCount = parent.getAdapter().getItemCount();  
  14.         int lastRowCount = childCount % spanCount;  
  15.         //最后一行的数量小于spanCount  
  16.         if (lastRowCount == 0 || lastRowCount < spanCount) {  
  17.             return true;  
  18.         }  
  19.     }  
  20.     return false;  
  21. }  
[java] view plain copy 技术分享技术分享
  1. private int getSpanCount(RecyclerView parent) {  
  2.     RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();  
  3.     if (layoutManager instanceof GridLayoutManager) {  
  4.         GridLayoutManager lm = (GridLayoutManager) layoutManager;  
  5.         int spanCount = lm.getSpanCount();  
  6.         return spanCount;  
  7.     }  
  8.     return 0;  
  9. }  

至此bug解决了

技术分享









艺术控件RecyclerView的分隔线&bug解决