RecyclerView源碼解析(二)-ItemDecoration

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繪製的時機就可以做到這個效果了。


會以這個形式粘性展示!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章