Android開發 RecyclerView.ItemDecoration

前言

  RecyclerView.ItemDecoration是用於實現RecyclerView的Item間距,當然除了實現間距更酷炫的是它可以實現一些在間距上繪製各種分割線。繪製分割線也還是一般操作,深度瞭解後你甚至可以實現各種時間軸,item分組標題等等功能。因爲提供了onDraw方法與Canvas,所以在繪製上自由度極大,可以讓你實現各種天馬行空的效果。

 

主要的三個重寫方法

  ItemDecoration只有3個重要的重寫方法:

  • getItemOffsets  用於實現item的上下左右的間距大小
  • onDraw  在這個方法裏繪製的文字、顏色、圖形都會比item更低一層,這些繪製效果如果與item重疊,就會被item遮蓋
  • onDrawOver  在這個方法繪製的文字、顏色、圖形都會比item更高一層,這些繪製效果始終在最上層,不會被遮蓋。

我們逐一瞭解這些方法如何使用。

getItemOffsets

 首先先要寫一個RecyclerView列表的Demo來進行演示。這裏實現了一個item的背景爲藍色的LinearLayoutManager的列表,如下圖:

給列表的item增加上邊距

代碼如下:

public class MainActivity extends AppCompatActivity {
    private RecyclerView mRecyclerView;
    private RecyclerViewAdapter mRecyclerViewAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRecyclerView = findViewById(R.id.recyclerview);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerViewAdapter = new RecyclerViewAdapter();
        mRecyclerView.setAdapter(mRecyclerViewAdapter);
        addData();
        /*
        將itemDecoration添加到RecyclerView。
        請注意這裏是add的,在底層源碼裏面可以看到ItemDecoration是可以被添加多個的.這裏是一個RecyclerView持有ItemDecoration集合。
        能添加當然就可以移除,所以對應移除的方法
        mRecyclerView.removeItemDecoration();//根據目標移除
        mRecyclerView.removeItemDecorationAt();//根據索引index移除
         */
        mRecyclerView.addItemDecoration(getItemDecoration());

    }

    private RecyclerView.ItemDecoration getItemDecoration() {
        return new RecyclerView.ItemDecoration() {
            @Override
            public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
                outRect.top = 20;//這裏增加了20的上邊距

            }
        };
    }
    
    private void addData() {
        List<String> list = new ArrayList<>();
        list.add("獵戶座");
        list.add("織女座");
        list.add("天馬座");
        list.add("天秤座");
        list.add("劍魚座");
        list.add("飛馬座");
        list.add("三角座");
        list.add("天琴座");
        list.add("蛇夫座");
        mRecyclerViewAdapter.refreshData(list);
    }

}

請注意!在首次觸發的getItemOffsets返回的View都是沒有經過measure測量的,所以這裏的View沒有尺寸值。但是滾動後重新觸發的getItemOffsets返回的View就有了尺寸值。但是這裏返回的RecyclerView是已經measure測量過了。

效果圖:

 

舉一反三,我們可以給左邊添加邊距

代碼:

    private RecyclerView.ItemDecoration getItemDecoration() {
        return new RecyclerView.ItemDecoration() {
            @Override
            public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
                outRect.top = 20;
                outRect.left = 100;

            }
        };
    }

效果圖:

給指定位置的Item設置邊距

有時候,我們的邊距需求並不是全部item都是要求有的,比如我們需求“第一個item有 50 的上邊距與最後一個item要求有 50 的下邊距”。我們可以根據getItemOffsets 方法提供的 RecyclerView, RecyclerView.State 這兩個值來確定需要實現邊距的指定item。

代碼:

    private RecyclerView.ItemDecoration getItemDecoration() {
        return new RecyclerView.ItemDecoration() {
            @Override
            public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
                if (parent.getChildAdapterPosition(view) == 0){ //給第一位的item設置50上邊距
                    outRect.top = 50;
                    return;
                }
                if (parent.getChildAdapterPosition(view) == state.getItemCount() -1){ //給最後一位的item設置50下邊距
                    outRect.bottom = 50;
           return; } } }; }

效果圖:

 

 

onDraw

  在重寫實現getItemOffsets方法給item增加邊距後,我們可以在onDraw方法實現一些文字,圖標等等效果。另外Draw其實是自定義View的知識,如果你還沒了解過Android 的Draw是如何實現的,你應該先去了解自定義View。

給空白邊距裏繪製文字

代碼:

    private RecyclerView.ItemDecoration getItemDecoration() {
        return new RecyclerView.ItemDecoration() {
            private Paint paint = new Paint();

            @Override
            public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
                outRect.top = 20;

            }

            @Override
            public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
                int count = parent.getChildCount();     //獲得當前RecyclerView數量
                paint.setColor(Color.RED);              //設置畫筆爲紅色
                paint.setTextSize(20);                  //設置文字大小
                for (int i = 0; i < count; i++) {       //遍歷全部item View
                    View view = parent.getChildAt(i);
                    int top = view.getTop();            //獲得這個item View的top位置
                    int bottom = view.getBottom();
                    int left = view.getLeft();
                    int right = view.getRight();
                    c.drawText("第" + i, left, top, paint);
                }
            }
        };
    }

這裏有些人會有一些誤區,認爲這裏返回的canvas是某一個item下的canvas。(我之前有這樣的理解)實際上這裏返回的canvas是整個RecyclerView的canvas,如果你把座標值固定死,也是在RecyclerView裏面某個位置繪製這個文字或者圖像。所以,這裏需要你自己獲取全部Item的座標值,用獲取到的Item座標值來繪製你需要位置上的內容。

效果圖:

 

給空白邊距裏繪製分割線

這裏提醒,除了繪製shape分割線,其實還可以使用xml矢量圖來繪製圖標。

先實現一個shape分割線:

shape_gray_line.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="line">
    <stroke android:width="5dp" android:color="@color/yellow_color"/>
</shape>

代碼:

    private Drawable mDividingLineDrawable;

    private RecyclerView.ItemDecoration getItemDecoration() {
        mDividingLineDrawable = getDrawable(R.drawable.shape_gray_line);
        return new RecyclerView.ItemDecoration() {

            @Override
            public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
                outRect.top = 20;

            }

            @Override
            public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
                int count = parent.getChildCount();
                parent.getPaddingLeft();
                for (int i = 0; i < count; i++) {
                    View view = parent.getChildAt(i);
                    int top = view.getTop();
                    int bottom = view.getBottom();
                    int left = view.getLeft();
                    int right = view.getRight();
                    mDividingLineDrawable.setBounds(left , top - 20, right, top);
                    mDividingLineDrawable.draw(c);
                }
            }
        };
    }

效果圖:

解釋onDraw繪製在Item下層是什麼效果

開頭我們說過 “ 在這個方法裏繪製的文字、顏色、圖形都會比item更低一層,這些繪製效果如果與item重疊,就會被item遮蓋 ” ,要證明這個效果很簡單,我們只需要將繪製內容位置與item重疊一下就能明白效果了。

代碼:

            @Override
            public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
                int count = parent.getChildCount();
                paint.setColor(Color.RED);
                paint.setTextSize(20);
                for (int i = 0; i < count; i++) {
                    View view = parent.getChildAt(i);
                    int top = view.getTop();
                    int bottom = view.getBottom();
                    int left = view.getLeft();
                    int right = view.getRight();
                    c.drawText("第" + i, left, top + 10, paint);//這裏top 增加10 讓繪製文字與item重疊
                }
            }

效果圖:

可以從這個效果圖看到,文字被item覆蓋了。

 

onDrawOver

onDrawOver在使用上與onDraw上是一致的,說這裏不在重複說明怎麼繪製內容。 這裏只解釋下onDrawOver的特性與onDraw對比理解。

解釋onDrawOver繪製在Item上層是什麼效果

這裏很簡單只要在上面onDraw繪製文字的代碼了複製一下到onDrawOver裏實現就可以了

代碼:

            @Override
            public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
                int count = parent.getChildCount();
                paint.setColor(Color.RED);
                paint.setTextSize(20);
                for (int i = 0; i < count; i++) {
                    View view = parent.getChildAt(i);
                    int top = view.getTop();
                    int bottom = view.getBottom();
                    int left = view.getLeft();
                    int right = view.getRight();
                    c.drawText("第" + i, left, top + 10, paint);//這裏top 增加10 讓繪製文字與item重疊
                }
            }

效果圖:

可以從這個效果圖看到,文字在最上層。

 

End

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