首页 > 代码库 > 艺术控件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里面配置实现每个子孩子下面都会有个分割线
效果~
- app:divider="@drawable/divider_vertical_holo_light"
- app:showDividers="middle"
1、app:divider="" 设置分割线的drawable
2、app:showDivider="" 设置显示分割线(none、begining、end、middle)
源码分析
LinearLayoutAppCompat继承于ViewGroup,都会调用onMeasure、onLayout、onDraw方法,通过查看可知绘制分割线的代码在onDraw方法里面
- @Override
- protected void onDraw(Canvas canvas) {
- if (mDivider == null) {
- return;
- }
- if (mOrientation == VERTICAL) {
- drawDividersVertical(canvas);
- } else {
- drawDividersHorizontal(canvas);
- }
- }
- void drawDividersVertical(Canvas canvas) {
- final int count = getVirtualChildCount();
- for (int i = 0; i < count; i++) {
- final View child = getVirtualChildAt(i);
- if (child != null && child.getVisibility() != GONE) {
- if (hasDividerBeforeChildAt(i)) {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- final int top = child.getTop() - lp.topMargin - mDividerHeight;
- drawHorizontalDivider(canvas, top);
- }
- }
- }
- if (hasDividerBeforeChildAt(count)) {
- final View child = getVirtualChildAt(count - 1);
- int bottom = 0;
- if (child == null) {
- bottom = getHeight() - getPaddingBottom() - mDividerHeight;
- } else {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- bottom = child.getBottom() + lp.bottomMargin;
- }
- drawHorizontalDivider(canvas, bottom);
- }
- }
1、绘制过程:第一个Child的顶部分割线->最后一个Child顶部分割线->最后一个Child底部分割线
2、通过判断hasDividerBeforeChildAt()方法来判断(none、begining、end、middle)
Part 2、实现RecyclerView线性方向间隔线
因为RecyclerView没有提供分割线,所以我们需要自己来进行添加
- public class MyItemDecoration extends RecyclerView.ItemDecoration {
- @Override
- public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
- }
- @Override
- public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
- super.onDraw(c, parent, state);
- }
- @Override
- public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
- super.onDrawOver(c, parent, state);
- }
- }
1、回调先后顺序:getItemOffsets()->onDraw()->onDrawOver()
2、getItemOffsets() 获得每个Item的偏移量 onDraw() : 绘制分割线 onDrawOver():绘制在Item的上层(也可以将绘制分割线放在此方法里面)
偏移量具体表示:
所以在getItemOffsets方法应该这样写
- @Override
- public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
- //获得每个Item条目的偏移量
- if(orientation == LinearLayoutManager.VERTICAL){
- outRect.set(0,0,0,divide.getIntrinsicHeight());
- }else{
- outRect.set(0,0,divide.getIntrinsicWidth(),0);
- }
- }
- /**
- * 方向为竖直方向进行绘制
- *
- * @param c
- * @param parent
- */
- private void drawableVertical(Canvas c, RecyclerView parent) {
- int count = parent.getChildCount();
- int left = parent.getPaddingLeft();
- int right = parent.getWidth() - parent.getPaddingRight();
- for (int i = 0; i < count; i++) {
- View child = parent.getChildAt(i);
- RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
- int top = child.getBottom() + params.bottomMargin+(int)ViewCompat.getTranslationY(child);
- int bottom = top + divide.getIntrinsicHeight();
- divide.setBounds(left, top, right, bottom);//为分割线确定绘制的位置
- divide.draw(c);//绘制
- }
- }
tips:
1、ViewCompat.getTranslationY(child) : 考虑动画变换的影响应该加上这个
Part 3、RecyclerView的网格分割线
思路:定义两个方法,一个画横线一个画竖线
so,getItemOffsets()方法
- @Override
- @Deprecated
- public void getItemOffsets(Rect outRect, int itemPosition,
- RecyclerView parent) {
- // 四个方向的偏移值
- int right = divide.getIntrinsicWidth();
- int bottom = divide.getIntrinsicHeight();
- outRect.set(0, 0, right, bottom);
- }
- private void drawHorizontal(Canvas c, RecyclerView parent) {
- int count = parent.getChildCount();
- for (int i = 0; i < count; i++) {
- View childView = parent.getChildAt(i);
- RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) childView.getLayoutParams();
- int left = childView.getLeft() - params.leftMargin;
- int right = childView.getRight() + params.rightMargin;
- int top = childView.getBottom() + params.bottomMargin;
- int bottom = top + divide.getIntrinsicHeight();
- divide.setBounds(left, top, right, bottom);
- divide.draw(c);
- }
- }
- private void drawVertical(Canvas c, RecyclerView parent) {
- int count = parent.getChildCount();
- for (int i = 0; i < count; i++) {
- View childView = parent.getChildAt(i);
- RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) childView.getLayoutParams();
- int left = childView.getRight() + params.rightMargin;
- int right = left + divide.getIntrinsicWidth();
- int top = childView.getTop() - params.topMargin;
- int bottom = childView.getBottom() + params.bottomMargin;
- divide.setBounds(left, top, right, bottom);
- divide.draw(c);
- }
- }
效果~
在效果图中可以发现,顶部和左侧没有线,下部和右侧有线。那怎么样才能去掉下部和右侧的线呢?这里有个好方法便是在分配Item便宜量的时候对position进行判断,符合则下部和右侧条件则将bottom和right置为0即可,代码如下
- @Override
- @Deprecated
- public void getItemOffsets(Rect outRect, int itemPosition,
- RecyclerView parent) {
- // 四个方向的偏移值
- int right = divide.getIntrinsicWidth();
- int bottom = divide.getIntrinsicHeight();
- if (isLastColum(itemPosition, parent)) {//是否是最后一列
- right = 0;
- }
- if (isLastRow(itemPosition, parent)) {//是最后一行
- bottom = 0;
- }
- outRect.set(0, 0, right, bottom);
- }
- /**
- * 判断是否是最后一列
- *
- * @param itemPosition
- * @param parent
- * @return
- */
- private boolean isLastColum(int itemPosition, RecyclerView parent) {
- RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
- //有多少列
- if (layoutManager instanceof GridLayoutManager) {
- int spanCount = getSpanCount(parent);
- if ((itemPosition + 1) % spanCount == 0) {//因为是从0可以所以要将ItemPosition先加1
- return true;
- }
- }
- return false;
- }
- /**
- * 是否是最后一行
- *
- * @param itemPosition
- * @param parent
- * @return
- */
- private boolean isLastRow(int itemPosition, RecyclerView parent) {
- int spanCount = getSpanCount(parent);
- RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
- //有多少列
- if (layoutManager instanceof GridLayoutManager) {
- int childCount = parent.getAdapter().getItemCount();
- int lastRowCount = childCount % spanCount;
- //最后一行的数量小于spanCount
- if (lastRowCount == 0 || lastRowCount < spanCount) {
- return true;
- }
- }
- return false;
- }
- private int getSpanCount(RecyclerView parent) {
- RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
- if (layoutManager instanceof GridLayoutManager) {
- GridLayoutManager lm = (GridLayoutManager) layoutManager;
- int spanCount = lm.getSpanCount();
- return spanCount;
- }
- return 0;
- }
至此bug解决了
艺术控件RecyclerView的分隔线&bug解决