美文网首页
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