preface

ItemDecoration is a good tool for recyclerView expansion. It supports us to do various operations in recyclerView, and the coupling is low, easy to add. In this article, we first use ItemDecoration to make the floating/sticky head. Later, we can also use ItemDecoration to make the timeline, and the letter on the right of the mobile phone contact interface.





Same old rule. Picture first.









Integrated way

Making address: github.com/qdxxxx/Stic… The weather is hot, the github has installed air conditioning, star can enjoy free ~~

  • Step 1. Add the JitPack repository to your build file Step 2
    allprojects {
        repositories {
            .
            maven { url 'https://jitpack.io'}}}Copy the code
    dependencies {
       compile 'com. Making. QDXXXX: StickyHeaderDecoration: 1.0.1'
    }Copy the code

Integrate code into the Activity

  • Group head
        NormalDecoration decoration = new NormalDecoration() {
            @Override
            public String getHeaderName(int pos) {
                return // Return each group header name;}};Copy the code
  • Please use loadImage() to load images for custom headers.

        decoration.setOnDecorationHeadDraw(new NormalDecoration.OnDecorationHeadDraw() {
            @Override
            public View getHeaderView(int pos) {
                return // Return a custom header view;}});Copy the code
  • Head click event

        decoration.setOnHeaderClickListener(new NormalDecoration.OnHeaderClickListener() {
            @Override
            public void headerClick(int pos) {
            }
        });Copy the code

GridLayoutManager please use with GridDecoration.

Methods and properties are introduced


name format Chinese interpretation
setHeaderHeight integer Group head height
setTextPaddingLeft integer Normal group header [text only] left margin of text
setTextSize integer Normal group header [text only] Text size
setTextColor integer Normal group header [text only] text color
setHeaderContentColor integer Normal group header [text only] text background color
onDestory Empty data set/listen etc
*loadImage String,integer,ImageView Used to load and refresh images to the group header.

Implementation solution of plane

It’s time to start decoding the code again, non-professional combatants… Please be patient. First of all, let’s divide up several main functional modules

  • Reserve header space for different groups
  • Draw different group headers
  • Draw floating head
  • Suspension head viscosity effect (push up effect)
  • Head click processing
  • Customize the layout header
  • The adapter GridLayoutManager

To advance theItemDecoration

The following paragraph quotes Jane’s book “Travel with the Mood”, which is very specific.

To look at the first RecyclerView. ItemDecoration source (part) :

public static abstract class ItemDecoration {
    ...
    public void onDraw(Canvas c, RecyclerView parent, State state) {
        onDraw(c, parent);
    }
    public void onDrawOver(Canvas c, RecyclerView parent, State state) {
        onDrawOver(c, parent);
    }
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent); }}Copy the code

Here are three methods we use:

  • GetItemOffsets: Sets offsets for each Item using Rect to draw decorations.
  • OnDraw: With this method, draw content on Canvas, called before drawing Item. (If offset is not set with getItemOffsets, the contents of the Item will overwrite them.)
  • OnDrawOver: This method draws on the Canvas and is called after the Item. (The contents of the drawing will be overlaid on top of the item)

RecyclerView background, onDraw content, Item, onDrawOver content, the hierarchical relationship is as follows:

【 Travel with the Mood 】


Reserve header space for different groups

We set the header height for the first item of each different header name

According to the above explanation, we use the getItemOffsets() method to set the item header of the group. We just need to determine whether the current item and the previous item belong to the same group.

        /* We set the header height */ for the first item of each different header name
        int pos = parent.getChildAdapterPosition(itemView); // Get the current itemView position
        String curHeaderName = getHeaderName(pos);         // Get the grouping header name according to pos

        if (pos = = 0 || !curHeaderName.equals(getHeaderName(pos - 1))) {// If the current position is 0, or if the header name is different from that of the previous item, free the header space
            outRect.top = headerHeight;                                 // Set the distance of itemView PaddingTop
        }Copy the code




Draw different group headers

Using onDrawOver() to draw the group header is like drawing on the interface of the Item (because the item is offset). As before, we get the position of each group and then draw the text.

  • We first get all the recyclerView items of the current screen
  • If head distance from top ==2*headerHeight, floating head will be offset up (push effect)
  • Head from top ==headerHeight floating head offset headerHeight (pushed off screen effect)

        int childCount = recyclerView.getChildCount();// Get the number of items visible on the screen
Copy the code
        for (int i = 0; i < childCount; i++) {
            View childView = recyclerView.getChildAt(i);
            int pos = recyclerView.getChildAdapterPosition(childView); // Get the pos of the current view in the Adapter
            String curHeaderName = getHeaderName(pos);                 // Get the name of the header to float according to pos
            int viewTop = childView.getTop() + recyclerView.getPaddingTop();
            if (pos == 0| |! curHeaderName.equals(getHeaderName(pos -1))) {// If the current position is 0, or if the header name is different from that of the previous item, free the header space
                // Draw each group head [AUDI a(Alfa Romeo does not draw a), Honda B]

            canvas.drawRect(left, viewTop - headerHeight, right, viewTop, mHeaderContentPaint);// Draw the header background
            canvas.drawText(curHeaderName, left + textPaddingLeft, viewTop - headerHeight / 2 + txtYAxis, mHeaderTxtPaint);// Draw text, text baseline can see my custom menu, said

                if (headerHeight < viewTop && viewTop <= 2 * headerHeight) { // If two heads collide, the floating head will be offset
                    translateTop = viewTop - 2 * headerHeight;// The floating head needs to offset the distance (y direction)
                }

stickyHeaderPosArray.put(pos, viewTop);// Put the header information into the array.}}Copy the code




Draw floating head

Using the above method, we can draw the head of each group. Finally we draw a floating head

        canvas.save(a);
        canvas.translate(0, translateTop);
        canvas.drawRect(left, 0, right, headerHeight, mHeaderContentPaint);
        canvas.drawText(firstHeaderName, left + textPaddingLeft, headerHeight / 2 + txtYAxis, mHeaderTxtPaint);
//      canvas.drawLine(0, headerHeight / 2, right, headerHeight / 2, mHeaderTxtPaint); // Draw a line to see if the text is centered
        canvas.restore(a);
Copy the code




Head click processing

Head clicking on this was a bit tricky at first, because the heads of this group were extra drawn by us, so we had to calculate and store the head information by ourselves. When we draw the header, we use SparseArray to store the header information in the collection, but we need to clear each onDrawOver to make sure the header data is correct. Finally, the GestureDetector processes user touch events and determines whether SparseArray contains the position according to the Y-axis position of the user’s touch.

        @Override// Click events
        public boolean onSingleTapUp(MotionEvent e) {
            for (int i = 0; i < stickyHeaderPosArray.size(); i++) {
                int value = stickyHeaderPosArray.valueAt(i);
                float y = e.getY();
                if (value - headerHeight <= y && y <= value) {// If you click on the group header
                    if(headerClickEvent ! =null) {
                        headerClickEvent.headerClick(stickyHeaderPosArray.keyAt(i));
                    }
                    return true; }}return false;
        }Copy the code




Customize the layout header

Drawing the header of a custom layout has two main points

  • How to draw a Layout to canvas
  • If there are pictures in the layout, you need to notify canvas to refresh after the picture is loaded to display the header picture (otherwise, users need to swipe to update the picture).
Draw the View to canvas

We can use the setDrawingCacheEnabled (true) method, through the cache will view into a bitmap, in headerView. GetDrawingCache () to obtain the bitmap object.

    View headerView = headerDrawEvent.getHeaderView(firstPos);
    headerView.measure(/ / measure layout View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
    headerView.setDrawingCacheEnabled(true);
    headerView.layout(0.0, right, headerHeight); / / layout layout
    canvas.drawBitmap(headerView.getDrawingCache(), left, 0, null);Copy the code

However, if the view contains an image, it is unlikely to be a pre-stored image URL obtained through a network request and then loaded. So that’s one of the difficulties.

Draw the image to the header through the URL
  • If the image is not finished loading, Glide load, after loading through the map set to store the image.
  • After the image is loadedmRecyclerView.postInvalidate(), thus making indirect manual callsonDrawOver()Method to redraw the loaded image.
public void loadImage(final String url, final int pos, ImageView imageView) {
        if(imgDrawableMap.get(url) ! =null) {// If the image is already loaded and stored
            imageView.setImageDrawable(imgDrawableMap.get(url));
        } else {
            Glide.with(mRecyclerView.getContext()).load(url).into(new SimpleTarget<Drawable>() {
                @Override
                public void onResourceReady(Drawable resource, Transition<? super Drawable> transition) {

                    headViewMap.remove(pos);// Delete, update againimgDrawableMap.put(url, resource); mRecyclerView.postInvalidate(); }}); }}Copy the code

For more details, go to NormalDecoration and onDrawOver(). Therefore, you must use loadImage() method for custom layout to draw the loaded image to the interface in time.

The adapter GridLayoutManager

GridGridDecoration also has two difficult breakthroughs

  • Set the itemgetItemOffsetsAnd not just group heads
  • Sets the Span of the last item of the current group.
  • We do not need to set other decorations. NormalDecoration has already been completed for us.

public abstract class GridDecoration extends NormalDecoration {
    private int itemTotalCount;

    public GridDecoration(int itemTotalCount, int span) {
        this.itemTotalCount = itemTotalCount;
        for (int pos = 0; pos < itemTotalCount; pos++) {
            /* We set the header height */ for the first item of each different header name
            String curHeaderName = getRealHeaderName(pos);         // Get the name of the head to float according to j
            if(! headerPaddingSet.contains(pos) && (pos ==0| |! curHeaderName.equals(getRealHeaderName(pos -1)))) {// If it is a group header
                groupHeadPos.add(pos);
                for (int i = 0; i < span; i++) {
                    headerPaddingSet.add(pos + i);
                    if(! curHeaderName.equals(getRealHeaderName(pos + i +1))) {// If the name of the next group is inconsistent, pass
                        break; }}}if(! curHeaderName.equals(getRealHeaderName(pos +1)) && groupHeadPos.size() > 0) {
                int preHeadPos = (int) ((TreeSet) (groupHeadPos)).last();
                intpadSpan = span - (pos - preHeadPos) % span; headerSpanArray.put(pos, padSpan); }}}private Set<Integer> headerPaddingSet = new TreeSet<>();                // paddintTop information for each header
    private Set<Integer> groupHeadPos = new TreeSet<>();                    // Count the span of the last item in the current group.
    private SparseArray<Integer> headerSpanArray = new SparseArray<>();     // Span is used to record the last item of each group
    private GridLayoutManager.SpanSizeLookup lookup;

    @Override
    public void getItemOffsets(Rect outRect, View itemView, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, itemView, parent, state);
        if (lookup == null) {
            lookup = new GridLayoutManager.SpanSizeLookup() {// equivalent to weight
                @Override
                public int getSpanSize(int position) {
                    int returnSpan = 1;
                    int index = headerSpanArray.indexOfKey(position);
                    if (index >= 0) {
                        returnSpan = headerSpanArray.valueAt(headerSpanArray.indexOfKey(position));   // Set the distance of itemView PaddingTop
                    }

                    returnreturnSpan; }};final GridLayoutManager gridLayoutManager = (GridLayoutManager) parent.getLayoutManager();
            gridLayoutManager.setSpanSizeLookup(lookup);
        }


        /* We set the header height */ for the first item of each different header name
        int pos = parent.getChildAdapterPosition(itemView); // Get the current itemView position
        if (headerPaddingSet.contains(pos)) {
            outRect.top = headerHeight;   // Set the distance of itemView PaddingTop}}}Copy the code

conclusion

So far, all our functions have been described. Indeed, we received a lot of goods after making this small function, which took a lot of time in the design of GridDecoration. Because it was not clear that the Span could be set dynamically, we calculated it by setting the paddingRight of itemOffsets at the beginning. Then we need to calculate the head of the next group, all kinds of problems, so in the future when we do the function, we will first see if there is an API to operate, so that it is more convenient and easy. Finally attached to github hope partners a lot of praise ha. There are suggestions and opinions also hope to put forward in the comments ~~ github.com/qdxxxx/Stic…