自定義ItemDecoration分割線的高度、顏色、偏移,看完這個你就懂了

重拾丟卻的夢發表於2019-05-28

想到分割線,原先一直是在item的佈局中直接加入,在adapter中進行判斷,若是最後一個子項則將分割線隱藏,感覺太小兒科了,今天來好好研究這個ItemDecoration的使用。

文章參考自RecyclerView 之 ItemDecoration 講解及高階特性實踐,寫的很詳細,仔細看後就會用了,我只是在此基礎上增添了可以更改顏色、寬度、左右偏移的功能。廢話不多說,我們們開始做吧。

簡單的新增分割線:

一、建立工程,建立Adapter,載入佈局檔案

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.recyclerview)
    RecyclerView recyclerview;			
    private List<String> dataList;		//資料項
    private MyAdapter myAdapter;		//介面卡

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      	...
        initData();

        myAdapter = new MyAdapter(R.layout.item_recyclerview,dataList);
        recyclerview.setAdapter(myAdapter);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerview.setLayoutManager(layoutManager);
    }
    private void initData(){
        dataList = new ArrayList<>();
        for(int i = 0;i<20;i++){
            dataList.add("子項"+i);
        }
    }
}
複製程式碼
public class MyAdapter extends BaseQuickAdapter<String, BaseViewHolder> {
    public MyAdapter(int layoutResId, List<String> data) {
        super(layoutResId, data);
    }
    @Override
    protected void convert(BaseViewHolder helper, String item) {
        helper.setText(R.id.tv_content, item);
    }
}
複製程式碼

1

二、建立分割器

提前說明,以下內容均是在每個ItemView的頂部加入分割線,第一個不加

通過recyclerview.addItemDecoration(new SimpleItemDecoration());將以下分割器加入到RecyclerView中即可

public class SimpleItemDecoration extends RecyclerView.ItemDecoration {
    /**
     * @param outRect   全為0的rect,用來指定偏移區域
     * @param view      指RecyclerView中的Item
     * @param parent    指RecyclerView本身
     * @param state     狀態
     */
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);

        if (parent.getChildAdapterPosition(view) != 0) {
            //直接設定為1px
            outRect.top = 1;
        }
    }
}
複製程式碼

加入後的效果:

2

自定義顏色偏移寬度的分割器

以上方法,通過使每個ItemView向上撐出1px距離,而RecyclerView背景為灰色,這樣就顯示出1px的灰色線,實現分割線功能,看到這你可能會想,這也太粗糙了,如果我想要改變分割線的寬度、顏色該怎麼辦,總不能每寫一個RecyclerView都再寫一套分割器,更改RecyclerView背景顏色吧,而且一般分割線並不佔滿全部寬度,有左右偏移,那該怎麼實現呢?

別急,我們先了解下getItemOffsets()方法中的outRect這個引數。

3

其中的藍色部分為我們的RecyclerView的子項ItemView,外部黃色部分為outRect,只是黃色,並不包含ItemView壓的那部分,left,right,top,bottom四個引數其實就是距離itemView的四個方向的偏移量,是指偏移 ItemView 各個方向的數值,在上面的例子中,我們設定了outRect.top=1,所以每個ItemView之間有1px的空隙,所以呈現出1px灰色的分割線,分割線顏色決定於RecyclerView的背景色。

一、設定高度:

既然知道了這四個引數代表相對itemview的偏移,那麼分割線的高度就好辦了。

4

如圖,想要紅色那樣高度的分割線,只需要outRect.top等於該高度就可以了。我們將該高度定義為mDividerHeight

二、設定顏色、左右偏移:

高度有了,如果我們只想繪製紅色那部分矩形而不是ItemView上方的全部該怎麼辦?我們知道每一個View中的onDraw()方法是用來繪製元件的UI效果,所以想要顏色的話,需要我們重寫ItemDecoration中的onDraw()方法。

public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state);
複製程式碼

可以看到onDraw()方法中有引數Canvas,通過它來繪製紅色矩形,所以我們需要知道該矩形的四條邊的位置。

float dividerTop = view.getTop() - mDividerHeight;  						//矩形頂部
float dividerBottom = view.getTop();									   //矩形底部
float dividerLeft = parent.getPaddingLeft() + margin;                         //矩形左側         
float dividerRight = parent.getWidth() - parent.getPaddingRight() - margin;   //矩形右側
複製程式碼

矩形頂部=itemview的頂部加上分割線的高度,咦?我怎麼寫的減號?看下圖你應該就會明白

5

安卓中座標是這樣的,向下向右為正,所以紅色矩形頂部位置就應該是itemView的top位置-矩形高度

偏移的話左側加上偏移量,右側減去偏移量即可。

c.drawRect(dividerLeft, dividerTop, dividerRight, dividerBottom, mPaint);
複製程式碼

這樣我們要繪製的矩形就出來了,等等,我們只是畫了個矩形,還沒顏色呢,再來看看drawRect()中的引數,我們還缺一個mPaint畫筆,通過它來設定矩形分割線顏色。

public MyDecoration() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);          //抗鋸齒
        mPaint.setColor(Color.GRAY);        //預設灰色
}
複製程式碼

通過以上步驟,帶有顏色和偏移量,且具有一定高度的分割線就畫好了,其實還沒完,需要注意:getItemOffsets 是針對每一個 ItemView,而 onDraw 方法卻是針對 RecyclerView 本身,所以在 onDraw 方法中需要遍歷螢幕上可見的 ItemView,分別獲取它們的位置資訊,然後分別的繪製對應的分割線。

程式碼如下:

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

        int childCount = parent.getChildCount();	//可見ItemView個數
//因為getItemOffsets是針對每一個ItemView,而onDraw方法是針對RecyclerView本身,所以需要迴圈遍歷來設定
        for (int i = 0; i < childCount; i++) {
            View view = parent.getChildAt(i);
            int index = parent.getChildAdapterPosition(view);
            //第一個ItemView不需要繪製
            if (index == 0) {
                continue;//跳過本次迴圈體中尚未執行的語句,立即進行下一次的迴圈條件判斷
            }
          float dividerTop = view.getTop() - mDividerHeight;                	//矩形頂部    
          float dividerLeft = parent.getPaddingLeft() + margin;            		//矩形左側       
          float dividerBottom = view.getTop();									//矩形底部
          float dividerRight = parent.getWidth() - parent.getPaddingRight() - margin;//矩形右側 
          c.drawRect(dividerLeft, dividerTop, dividerRight, dividerBottom, mPaint);
        }
    }
複製程式碼

在實際運用中,我們的分割線顏色高度等樣式可能不一樣,這裡我們通過建造者模式來設定這些屬性

//設定左右偏移(預設是設定的一樣的,若需要自己更改)
    public MyDecoration setMargin(float margin) {
        this.margin = margin;
        return this;
    }
    //設定顏色
    public MyDecoration setColor(int color) {
        mPaint.setColor(color);
        return this;
    }
    //設定分割線高度
    public MyDecoration setDividerHeight(float height) {
        this.mDividerHeight = height;
        return this;
    }
複製程式碼

這樣我們就完成了分割線的自定義

完整程式碼如下:

public class MyDecoration extends RecyclerView.ItemDecoration {

    private float mDividerHeight = 1; //線的高度
    private Paint mPaint;           //畫筆將自己做出來的分割線矩形畫出顏色
    private float margin = 0;       //左右偏移量

    public MyDecoration() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);          //抗鋸齒
        mPaint.setColor(Color.GRAY);        //預設顏色
    }

    //通過建造者模式來設定三個屬性
    //設定左右偏移(預設是設定的一樣的,若需要自己更改)
    public MyDecoration setMargin(float margin) {
        this.margin = margin;
        return this;
    }

    //設定顏色
    public MyDecoration setColor(int color) {
        mPaint.setColor(color);
        return this;
    }

    //設定分割線高度
    public MyDecoration setDividerHeight(float height) {
        this.mDividerHeight = height;
        return this;
    }

    //在這裡就已經把寬度的偏移給做好了
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        //第一個ItemView不需要在上面繪製分割線
        if (parent.getChildAdapterPosition(view) != 0) {
            
            outRect.top = (int) mDividerHeight;//指相對itemView頂部的偏移量
        }
    }
    //這裡主要是繪製顏色的
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);

        int childCount = parent.getChildCount();
//因為getItemOffsets是針對每一個ItemView,而onDraw方法是針對RecyclerView本身,所以需要迴圈遍歷來設定
        for (int i = 0; i < childCount; i++) {
            View view = parent.getChildAt(i);
            int index = parent.getChildAdapterPosition(view);
            //第一個ItemView不需要繪製
            if (index == 0) {
                continue;//跳過本次迴圈體中尚未執行的語句,立即進行下一次的迴圈條件判斷
            }
            float dividerTop = view.getTop() - mDividerHeight;                                  
            float dividerLeft = parent.getPaddingLeft() + margin;                               
            float dividerBottom = view.getTop();
            float dividerRight = parent.getWidth() - parent.getPaddingRight() - margin;         
            c.drawRect(dividerLeft, dividerTop, dividerRight, dividerBottom, mPaint);
        }
    }
}
複製程式碼

使用:

MyDecoration myDecoration = new MyDecoration();

myDecoration.setColor(ContextCompat.getColor(getContext(),R.color.line_gray)).setMargin(ConvertUtils.dp2px(getContext(), 15)).setDividerHeight(ConvertUtils.dp2px(getContext(),1));

recyclerView.addItemDecoration(myDecoration);
複製程式碼

實際使用中我們是dp單位,所以這裡我使用了ConvertUtils工具類,將dp轉為px

程式碼如下:

public static int dp2px(Context context, final float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
複製程式碼

看下效果:

6

到這裡我們想要的功能就全部完成了。(終於碼完了開心!)

如果我們的RecyclerView是橫向的滑動,原理類似,剩下的就交給你們了(懶得寫了嘿嘿)

GitHub地址

總結一下

一共兩步:

1、通過getItemOffsets()在itemView頂部撐出來一片區域

2、通過onDraw()方法來在該區域內繪製想要顏色及偏移量的分割線

其實ItemDecoration還有很多很牛逼的地方,例如實現時光軸效果,排行榜的角標,可以看看我參考的那篇文章的實現,寫得很詳細的,是真大佬!日後Demo寫出來了再來更新

相關文章