前言
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