美文网首页
RecyclerView源码解析(二)-ItemDecorati

RecyclerView源码解析(二)-ItemDecorati

作者: MrLgc | 来源:发表于2019-05-09 17:56 被阅读0次

RecyclerView.ItemDecoration可以对item添加分割线及添加视图!
下面我们进行分析!

public class linearSpacingItemDecoration extends RecyclerView.ItemDecoration {
    pr

    /**
     * 可以实现类似padding的效果,但是如果以重写这个方法设置下bottom,那么他的背景颜色是父view的背景颜色
     *
     * @param outRect
     * @param view
     * @param parent
     * @param state
     */
    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.bottom = spacing;
        outRect.left = spacing;
        outRect.top = spacing;
        outRect.right = spacing;

    }

    /**
     * 可以实现类似绘制背景的效果,内容在上面,但是如果这个方法结合getItemOffsets()那就不一样了,他会把绘制的padding
     * 绘制自己想要的颜色!
     *
     * @param c
     * @param parent
     * @param state
     */
    @Override
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(c, parent, state);
        int size = parent.getChildCount();
        for (int i = 0; i < size; i++) {
            View view = parent.getChildAt(i);
            c.drawRect(view.getLeft(),view.getBottom(), view.getRight(), view.getBottom() + spacing * 2, mPaint);
        }

    }

    /**
     * 可以绘制在内容的上面,覆盖内容
     * 可以绘制标签之类的附加属性
     *
     * @param c
     * @param parent
     * @param state
     */
    @Override
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        int size = parent.getChildCount();
        for (int i = 0; i < size; i++) {
              View view = parent.getChildAt(i);
              c.drawText("onDrawOver", view.getLeft(), view.getBottom() - spacing, mPaintText);
        } 
    }
}

我们只需要写的类继承 RecyclerView.ItemDecoration就ok了,
你怎么用呢?

    mRecyclerView.addItemDecoration(new linearSpacingItemDecoration());

这样就可以用我们的ItemDecoration了!
我们只要重写上面的方法就可以做到我们想要的效果了!
那么RecyclerView内部是怎么实现的呢?
addItemDecoration()这一方法,

public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
        this.addItemDecoration(decor, -1);
    }

public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor, int index) {
        if (this.mLayout != null) {
            this.mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll  or layout");
        }

        if (this.mItemDecorations.isEmpty()) {
            this.setWillNotDraw(false);
        }

        if (index < 0) {
            this.mItemDecorations.add(decor);
        } else {
            this.mItemDecorations.add(index, decor);
        }

        this.markItemDecorInsetsDirty();
        this.requestLayout();
    }

final ArrayList<RecyclerView.ItemDecoration> mItemDecorations;

所以说当我们去addItemDecoration的时候,会加到RecyclerView本身的数组中!
而对也我们重写的RecyclerView.ItemDecoration的3个方法是什么时候调用的呢?(都是在RecyclerView中!)
首先我们先看getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state)这一方法

 public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right, int bottom) {
            RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)child.getLayoutParams();
            Rect insets = lp.mDecorInsets;
            child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin, right - insets.right - lp.rightMargin,   bottom - insets.bottom - lp.bottomMargin);
        }

而这个insets就是上面重写的getItemOffsets()这一方法中的 Rect outRect,这面就是对childView的摆放!

这面应该会有问题?
ItemDecoration 应该是数组啊!为啥会只返回一个Rect呢?正因为如下所示Offsets是被叠加的。

 Rect getItemDecorInsetsForChild(View child) {
        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)child.getLayoutParams();
        if (!lp.mInsetsDirty) {
            return lp.mDecorInsets;
        } else if (this.mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
            return lp.mDecorInsets;
        } else {
            Rect insets = lp.mDecorInsets;
            insets.set(0, 0, 0, 0);
            int decorCount = this.mItemDecorations.size();

            for(int i = 0; i < decorCount; ++i) {
                this.mTempRect.set(0, 0, 0, 0);
                ((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).getItemOffsets(this.mTempRect, child, this, this.mState);
                insets.left += this.mTempRect.left;
                insets.top += this.mTempRect.top;
                insets.right += this.mTempRect.right;
                insets.bottom += this.mTempRect.bottom;
            }

            lp.mInsetsDirty = false;
            return insets;
        }
    }

然后再看onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) 这一方法

 public void onDraw(Canvas c) {
        super.onDraw(c);
        int count = this.mItemDecorations.size();

        for(int i = 0; i < count; ++i) {
            ((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).onDraw(c, this, this.mState);
        }

    }

在绘制childView前会进行绘制ItemDecorations的onDraw()方法!

最后看onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) 这一方法

 public void draw(Canvas c) {
        super.draw(c);
        int count = this.mItemDecorations.size();

        for(int i = 0; i < count; ++i) {
            ((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).onDrawOver(c, this, this.mState);
        }
............
}

在绘制之后会进行绘制ItemDecorations的onDrawOver()方法!
好的这面方法就分析好了。

那下面给大家带来一一个仿通讯录粘性列表:

自定义RecyclerView.ItemDecoration

public class LinearMountingItemDecoration extends RecyclerView.ItemDecoration {

    private int spacing; //分割线的高度
    private Paint mPaintSpacing; //分割线的画笔
    private Paint mPaintText; //展示文字的画笔

    Paint.FontMetrics fontMetrics;

    //通过接口获取当前下标所展示的文本内容
    private OnGetTextListener onGetTextListener;

    public OnGetTextListener getOnGetTextListener() {
        return onGetTextListener;
    }

    public void setOnGetTextListener(OnGetTextListener onGetTextListener) {
        this.onGetTextListener = onGetTextListener;
    }

    public LinearMountingItemDecoration(int spacing) {
        this.spacing = spacing;

        mPaintSpacing = new Paint();
        mPaintSpacing.setColor(Color.YELLOW);

        mPaintText = new Paint();
        mPaintText.setColor(Color.parseColor("#80ff0000"));
        mPaintText.setTextAlign(Paint.Align.LEFT);
        mPaintText.setTextSize(Utils.dp2px(28));
        fontMetrics = new Paint.FontMetrics();
        mPaintText.getFontMetrics(fontMetrics);
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        if (isFirstInGroup(parent.getChildAdapterPosition(view))) {
            //绘制分割线
            outRect.top = spacing;
        }
        ;

    }

    @Override
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(c, parent, state);
        int size = parent.getChildCount();
        for (int i = 0; i < size; i++) {
            View view = parent.getChildAt(i);
            int position = parent.getChildAdapterPosition(view);
            String content = onGetTextListener.getText(position).substring(0, 1);
            if (isFirstInGroup(position)) {
                //绘制分割线的颜色
                c.drawRect(view.getLeft(), view.getTop() - spacing, view.getRight(), view.getTop(), mPaintSpacing);
                c.drawText(content, 0, view.getTop() - fontMetrics.descent, mPaintText);
            }
        }
    }

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

        //绘制上面粘性的布局!
        View view = parent.getChildAt(0);
        int position = parent.getChildAdapterPosition(view);
        String content = onGetTextListener.getText(position).substring(0,1);
        if (view.getBottom() <= spacing && isFirstInGroup(position + 1)) {
            //如果第一个view展示的bottom小于spacing并且下一个item内容的首字母也是第一次出现,则用第一个view的bottom高度进行绘制,
            c.drawRect(0, 0, view.getRight(), view.getBottom(), mPaintSpacing);
            c.drawText(content, 0, view.getBottom() - fontMetrics.descent, mPaintText);

        } else {
            c.drawRect(0, 0, view.getRight(), spacing, mPaintSpacing);
            c.drawText(content, 0, spacing - fontMetrics.descent, mPaintText);


        }
    }

    //回调接口,通过该回调获取item的昵称
    public interface OnGetTextListener {
        String getText(int position);
    }

    //item的内容首字母是否是第一次出现
    private boolean isFirstInGroup(int position) {
        if (position == 0) return true;
        String lastName = onGetTextListener.getText(position - 1);
        String currentName = onGetTextListener.getText(position);
        if (lastName.substring(0, 1).equals(currentName.substring(0, 1))) {
            return false;
        } else {
            return true;
        }
    }
}

entity实例

public class User {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public User(String name) {
        this.name = name;
    }
}

数据源

  private void initData() {
        arrayList.add(new User("a"));
        arrayList.add(new User("ab"));
        arrayList.add(new User("abb"));
        arrayList.add(new User("abbb"));
        arrayList.add(new User("abbbb"));
        arrayList.add(new User("abbbb"));

        arrayList.add(new User("b"));
        arrayList.add(new User("bc"));
        arrayList.add(new User("bcc"));
        arrayList.add(new User("bccc"));
        arrayList.add(new User("bcccc"));
        arrayList.add(new User("bccccc"));

        arrayList.add(new User("c"));
        arrayList.add(new User("cd"));
        arrayList.add(new User("cdd"));
        arrayList.add(new User("cddd"));
        arrayList.add(new User("cdddd"));
        arrayList.add(new User("cddddd"));

        arrayList.add(new User("d"));
        arrayList.add(new User("de"));
        arrayList.add(new User("dee"));
        arrayList.add(new User("deee"));
        arrayList.add(new User("deeee"));
        arrayList.add(new User("deeeee"));

        arrayList.add(new User("e"));
        arrayList.add(new User("ef"));
        arrayList.add(new User("eff"));
        arrayList.add(new User("efff"));
        arrayList.add(new User("effff"));
        arrayList.add(new User("efffff"));
    }
public class MountingRyActivity extends AppCompatActivity {
    private RecyclerView mRecyclerView;
    private ArrayList<User> arrayList = new ArrayList<>();

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

        initData();
        initView();

    }

    private void initView() {
        mRecyclerView = findViewById(R.id.rv);
        LinearMountingItemDecoration linearMountingItemDecoration = new LinearMountingItemDecoration(Utils.dp2px(30));
        linearMountingItemDecoration.setOnGetTextListener(new LinearMountingItemDecoration.OnGetTextListener() {
            @Override
            public String getText(int position) {
                return arrayList.get(position).getName();
            }
        });
        mRecyclerView.addItemDecoration(linearMountingItemDecoration);

        MyAdapter myAdapter = new MyAdapter(arrayList, this);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setAdapter(myAdapter);
    }

    private void initData() {
        arrayList.add(new User("a"));
        arrayList.add(new User("ab"));
        arrayList.add(new User("abb"));
        arrayList.add(new User("abbb"));
        arrayList.add(new User("abbbb"));
        arrayList.add(new User("abbbb"));

        arrayList.add(new User("b"));
        arrayList.add(new User("bc"));
        arrayList.add(new User("bcc"));
        arrayList.add(new User("bccc"));
        arrayList.add(new User("bcccc"));
        arrayList.add(new User("bccccc"));

        arrayList.add(new User("c"));
        arrayList.add(new User("cd"));
        arrayList.add(new User("cdd"));
        arrayList.add(new User("cddd"));
        arrayList.add(new User("cdddd"));
        arrayList.add(new User("cddddd"));

        arrayList.add(new User("d"));
        arrayList.add(new User("de"));
        arrayList.add(new User("dee"));
        arrayList.add(new User("deee"));
        arrayList.add(new User("deeee"));
        arrayList.add(new User("deeeee"));

        arrayList.add(new User("e"));
        arrayList.add(new User("ef"));
        arrayList.add(new User("eff"));
        arrayList.add(new User("efff"));
        arrayList.add(new User("effff"));
        arrayList.add(new User("efffff"));
    }

    private class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
        private ArrayList<User> arrayList; //数据源
        private Context mContext; //上下文

        public MyAdapter(ArrayList<User> arrayList, Context mContext) {
            this.arrayList = arrayList;
            this.mContext = mContext;
        }

        @NonNull
        @Override
        public MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
            View itemView = LayoutInflater.from(mContext).inflate(R.layout.item_rv_mounting, viewGroup, false);
            return new MyViewHolder(itemView);
        }

        @Override
        public void onBindViewHolder(@NonNull MyViewHolder myViewHolder, int i) {
            myViewHolder.mTvName.setText(arrayList.get(i).getName());
        }

        @Override
        public int getItemCount() {
            return arrayList.size();
        }

        class MyViewHolder extends RecyclerView.ViewHolder {
            TextView mTvName;

            public MyViewHolder(@NonNull View itemView) {
                super(itemView);
                mTvName = itemView.findViewById(R.id.tv_name);
            }
        }
    }
}

大体思路就是通过绘制分割线,及控制最上面view绘制的时机就可以做到这个效果了。

IMG_2872.JPG IMG_2873.JPG IMG_2874.JPG IMG_2875.JPG

会以这个形式粘性展示!

相关文章

网友评论

      本文标题:RecyclerView源码解析(二)-ItemDecorati

      本文链接:https://www.haomeiwen.com/subject/atrvoqtx.html