Using GridLayoutManager like this, you may not really try

https://www.jianshu.com/p/60aa2fc17870
Foreword

Last week I mentioned in the article "RecyclerView-LayoutManager" that the GridLayoutManagerfollowing homepage can be realized by using :


Some students expressed interest in this, but there is no ready-made case, so I simply implemented one by myself. The final result is as follows:

effect


I believe that many students have the same feeling as me, thinking that GridLayoutManageronly standard grid layout can be achieved. It was not until I decided to study some time ago RecyclerViewand read GridLayoutManagerthe source code that I found out that it can do more things, for example, write one Home page.

 

Before reading this article, you need some knowledge reserves :

  1. ViewHave some simple understanding of the drawing process.
  2. CanvasSimple and practical.
  3. RecyclerView+GridLayoutManagerusage of.

table of Contents

table of Contents

One, the scene

Use RecyclerView+ GridLayoutManager+ ItemDecorationCustomized homepage applicable scenarios:

  • There are multiple functional modules
  • Multiple styles of subviews
  • The last module needs to be refreshed (if there is such a function, it must be RecyclerViewrealized), for example, QQ music slides down to recommend music that users may be interested in.

I personally feel that the significance of this solution is to reduce the nesting of the layout and make the interface management easier , but it may not be suitable for particularly complex business situations.

2. Thinking

Two difficulties need to be solved to realize the above functions :

  1. How to show different numbers of subviews for different rows
  2. Drawing of each module title

The solutions to these two problems correspond to GridLayoutManagerand respectively ItemDecoration, we understand one by one.

1. GridLayoutManager

GridLayoutManagerIn fact, we are already familiar with it, but we don’t usually understand SpanSizethis concept. Let’s look at the following piece of code:

 

GridLayoutManager gll = new GridLayoutManager(this, 6);
mRecyclerView.setLayoutManager(gll);

In the above code, we created a vertical view with a maximum capacity of 6 child views per row GridLayoutManager. By default, the total number SpanSizeof one row is 6, and the default value of each child view SpanSizeis 1. Therefore, GridLayoutManagereach row will be divided into 6 without processing. Each copy shows a sub-view, as shown in the first row of the following figure:

style


At this time, if I SpanSizeset all the subviews to 2, then this subview will occupy the entire available width of the RecyclerView 2/6, as shown in the second row of the above figure. Similarly, I will SpanSizerise to 3, then the width of the subview will also rise For the available width 3/6, as shown in the third row of the above figure, this is also GridLayoutManagerthe reason why different numbers of subviews can be set in different rows. Of course, you can also set the three subviews in the same row SpanSizeto 1, 2, and 3.

 

Okay, how to draw the title is one step away from actual code combat.

2. ItemDecoration

The dividing line ItemDecorationis a very interesting thing, because it can achieve some fun things, such as the following letter titles and timeline of the address book :

Letter Title of Address Book

Timeline

 

You can also use it to do some special effects, such as the ceiling of letter titles. Here I recommend two libraries:

Here is a brief introduction to ItemDecorationthe principle. Here I assume that Viewthe surveying and mapping process that the students have already understood is mainly divided into two parts:

  1. Draw the dividing line below the RecyclerViewsub-view, because the ItemDecorationfirst drawing method of the dividing line ItemDecoration#onDrawoccurs before drawing the RecyclerViewsub-view. If you want it to be displayed, you need to ItemDecorationset an offset to offset the sub-view so that it will not be blocked. ItemDecoration.
  2. Draw the dividing line on RecyclerViewthe upper layer of the sub-view, because the drawing method ItemDecoration#onDrawOvertakes place after the drawing of the RecyclerViewsub-view is completed, which ItemDecorationcan also achieve the ceiling effect.

Three, code combat

With the knowledge reserve above, the following is simple.

1. Custom ItemDecoration

Custom ItemDecorationthree methods need to be implemented, with the related principle that we mentioned above:

Method name Explanation
onDraw Draw the dividing line under the subview
getItemOffsets The space usually reserved for displaying the lower divider
onDrawOver Draw the upper divider

Our task is only to draw a title, so the above two methods are enough.

1.1 Define data interface

 

/**
 * 数据约束
 */
public interface IGridItem {
    /**
     * 是否启用分割线
     * @return true
     */
    boolean isShow();

    /**
     * 分类标签
     */
    String getTag();

    /**
     * 权重
     */
    int getSpanSize();
}

1.2 Custom ItemDecoration class

The core code is more than 100 lines:

 

/**
 * 适用于GridLayoutManager的分割线
 */
public class GridItemDecoration extends RecyclerView.ItemDecoration {
    // 记录上次偏移位置 防止一行多个数据的时候视图偏移
    private List<Integer> offsetPositions = new ArrayList<>();
    // 显示数据
    private List<? extends IGridItem> gridItems;
    // 画笔
    private Paint mTitlePaint;
    // 存放文字
    private Rect mRect;
    // 颜色
    private int mTitleBgColor;
    private int mTitleColor;
    private int mTitleHeight;
    private int mTitleFontSize;
    private Boolean isDrawTitleBg = false;
    private Context mContext;
    // 总的SpanSize
    private int totalSpanSize;
    private int mCurrentSpanSize;

    //... 省略一些方法

    @Override
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(c, parent, state);
        // 绘制标题的逻辑:
        // 如果该行的数据的需要显示的标题不同于上行的标题,就绘制标题
        final int paddingLeft = parent.getPaddingLeft();
        final int paddingRight = parent.getPaddingRight();
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            int pos = params.getViewLayoutPosition();
            IGridItem item = gridItems.get(pos);
            if (item == null || !item.isShow())
                            continue;
            if (i == 0) {
                drawTitle(c, paddingLeft, paddingRight, child
                                        , (RecyclerView.LayoutParams) child.getLayoutParams(), pos);
            } else {
                IGridItem lastItem = gridItems.get(pos - 1);
                if (lastItem != null && !item.getTag().equals(lastItem.getTag())) {
                    drawTitle(c, paddingLeft, paddingRight, child,
                                                (RecyclerView.LayoutParams) child.getLayoutParams(), pos);
                }
            }
        }
    }
    /**
     * 绘制标题
     *
     * @param canvas 画布
     * @param pl     左边距
     * @param pr     右边距
     * @param child  子View
     * @param params RecyclerView.LayoutParams
     * @param pos    位置
     */
    private void drawTitle(Canvas canvas, int pl, int pr, View child, RecyclerView.LayoutParams params, int pos) {
        if (isDrawTitleBg) {
            mTitlePaint.setColor(mTitleBgColor);
            canvas.drawRect(pl, child.getTop() - params.topMargin - mTitleHeight, pl
                                , child.getTop() - params.topMargin, mTitlePaint);
        }
        IGridItem item = gridItems.get(pos);
        String content = item.getTag();
        if (TextUtils.isEmpty(content))
                    return;
        mTitlePaint.setColor(mTitleColor);
        mTitlePaint.setTextSize(mTitleFontSize);
        mTitlePaint.setTypeface(Typeface.DEFAULT_BOLD);
        mTitlePaint.getTextBounds(content, 0, content.length(), mRect);
        float x = UIUtils.dip2px(20f);
        float y = child.getTop() - params.topMargin - (mTitleHeight - mRect.height()) / 2;
        canvas.drawText(content, x, y, mTitlePaint);
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        // 预留逻辑:
        // 只要是标题下面的一行,无论这行几个,都要预留空间给标题显示
        int position = parent.getChildAdapterPosition(view);
        IGridItem item = gridItems.get(position);
        if (item == null || !item.isShow())
                    return;
        if (position == 0) {
            outRect.set(0, mTitleHeight, 0, 0);
            mCurrentSpanSize = item.getSpanSize();
        } else {
            if (!offsetPositions.isEmpty() && offsetPositions.contains(position)) {
                outRect.set(0, mTitleHeight, 0, 0);
                return;
            }
            if (!TextUtils.isEmpty(item.getTag()) && !item.getTag().equals(gridItems.get(position - 1).getTag())) {
                mCurrentSpanSize = item.getSpanSize();
            } else
                            mCurrentSpanSize += item.getSpanSize();
            if (mCurrentSpanSize <= totalSpanSize) {
                outRect.set(0, mTitleHeight, 0, 0);
                offsetPositions.add(position);
            }
        }
    }
}

The general logic is:

  1. If the position of the RecyclerViewsub-view is below the title, you need to reserve space, and set it in the outRectmiddle. It should be noted that multiple sub-views in the same row need to reserve space.
  2. Draw the title of the current data that is different from the previous data title.
  3. Repeat 1 and 2.

2. Interface part

 

public class SpecialGridActivity extends AppCompatActivity {

    // GridItem实现了IGridItem接口
    private List<GridItem> values;
    private RecyclerView mRecyclerView;
    private GridItemDecoration itemDecoration;
    // 自己封装的RecyclerAdapter
    private RecyclerAdapter<GridItem> mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_special_grid);

        initWidget();
    }

    private void initWidget() {
        mRecyclerView = findViewById(R.id.rv_content);

        // 创建GridLayoutManager,并设置SpanSizeLookup
        GridLayoutManager gll = new GridLayoutManager(this, 3);
        gll.setSpanSizeLookup(new SpecialSpanSizeLookup());
        mRecyclerView.setLayoutManager(gll);
        values = initData();
        
        // 自己封装的RecyclerAdapter
        mRecyclerView.setAdapter(mAdapter = new RecyclerAdapter<GridItem>(values,null) {
            @Override
            public ViewHolder<GridItem> onCreateViewHolder(View root, int viewType) {
                switch (viewType) {
                    case R.layout.small_grid_recycle_item:
                        return new SmallHolder(root);
                    case R.layout.normal_grid_recycle_item:
                        return new NormalHolder(root);
                    case R.layout.special_grid_recycle_item:
                        return new SpecialHolder(root);
                    default:
                        return null;
                }

            }

            @Override
            public int getItemLayout(GridItem gridItem, int position) {
                switch (gridItem.getType()) {
                    case GridItem.TYPE_SMALL:
                        return R.layout.small_grid_recycle_item;
                    case GridItem.TYPE_NORMAL:
                        return R.layout.normal_grid_recycle_item;
                    case GridItem.TYPE_SPECIAL:
                        return R.layout.special_grid_recycle_item;
                }
                return 0;
            }
        });
        
        //...

        // 分隔线生成
        // 之前的GridItemDecoration代码中我将构建者模式部分省略了
        itemDecoration = new GridItemDecoration.Builder(this,values, 3)
                .setTitleTextColor(Color.parseColor("#4e5864"))
                .setTitleFontSize(22)
                .setTitleHeight(52)
                .build();
        mRecyclerView.addItemDecoration(itemDecoration);
    }

    // 数据初始化
    private List<GridItem> initData() {
        List<GridItem> values = new ArrayList<>();
        values.add(new GridItem("我很忙", "", R.drawable.head_1,"最近常听",1,GridItem.TYPE_SMALL));
        values.add(new GridItem("治愈:有些歌比闺蜜更懂你", "", R.drawable.head_2,"最近常听",1,GridItem.TYPE_SMALL));
        values.add(new GridItem("「华语」90后的青春纪念手册", "", R.drawable.head_3,"最近常听",1,GridItem.TYPE_SMALL));
      
        values.add(new GridItem("流行创作女神你霉,泰勒斯威夫特的创作历程", "", R.drawable.special_2
                ,"更多为你推荐",3,GridItem.TYPE_SPECIAL));
        values.add(new GridItem("行走的CD写给别人的歌", "给「跟我走吧」几分,试试这些", R.drawable.normal_1
                ,"更多为你推荐",3,GridItem.TYPE_NORMAL));
        values.add(new GridItem("爱情里的酸甜苦辣,让人捉摸不透", "听完「靠近一点点」,他们等你翻牌", R.drawable.normal_2
                ,"更多为你推荐",3,GridItem.TYPE_NORMAL));
        values.add(new GridItem("关于喜欢你这件事,我都写在了歌里", "「好想你」听罢,听它们吧", R.drawable.normal_3
                ,"更多为你推荐",3,GridItem.TYPE_NORMAL));
        values.add(new GridItem("周杰伦暖心混剪,短短几分钟是多少人的青春", "", R.drawable.special_1
                ,"更多为你推荐",3,GridItem.TYPE_SPECIAL));
        values.add(new GridItem("我好想和你一起听雨滴", "给「发如雪」几分,那这些呢", R.drawable.normal_4
                ,"更多为你推荐",3,GridItem.TYPE_NORMAL));
        values.add(new GridItem("油管周杰伦热门单曲Top20", "「周杰伦」的这些哥,你听了吗", R.drawable.normal_5
                ,"更多为你推荐",3,GridItem.TYPE_NORMAL));

        return values;
    }

    class SpecialSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {

        @Override
        public int getSpanSize(int i) {
            // 返回在数据中定义的SpanSize
            GridItem gridItem = values.get(i);
            return gridItem.getSpanSize();
        }
    }

    class SmallHolder extends RecyclerAdapter.ViewHolder<GridItem> {    
        //... 代码省略,就是设置图片和文字的操作
        // 小的Holder
    }

    class NormalHolder extends RecyclerAdapter.ViewHolder<GridItem> {
        //... 中等的Holder
    }

    class SpecialHolder extends RecyclerAdapter.ViewHolder<GridItem> {
        //... 横向大的Holder
    }
}

We usually use GridLayoutManageris not the same, GridLayoutManageryou need to set SpanSizeLookUpis that we need to set for each child view SpanSize, because we have achieved each data IGridIteminterface, which will be provided from outside SpanSize, so we return here in the data set SpanSizecan be .

Due to space limitations, the layout file and package of ReyclerAdapter will not be posted, and interested students can check the source code. The following is what we accomplished:

 

effect

Four, summary

 

to sum up


Some details in the source code are very interesting. It is because of GridLayoutManagerthe source code that I have read that this article appeared. After reading this article, I believe you, like me, have RecyclerViewa deeper understanding.

 

Demo site: https://github.com/mCyp/Orient-Ui

If you want and RecyclerViewhave a more in-depth exchanges, Welcome to my unraveling the RecyclerViewseries of articles :

First: "unraveling RecyclerView - LayoutManager"
The second: "unraveling RecyclerView - piecemeal"
Part III: "unraveling RecyclerView - ItemAnimator & Adapter"



Author: Nine heart _
link: https: //www.jianshu.com/p/60aa2fc17870
Source: Jane books
are copyrighted by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

Guess you like

Origin blog.csdn.net/az44yao/article/details/112973589