【Android】學習ItemDecoration並製作時間軸效果

首先一個問題,什麼是ItemDecoration呢?

有道翻譯來看,Decoration翻譯爲“裝飾”。那顧名思義,就是用來裝飾我們的Item的類。

下面將針對ItemDecoration中的幾個方法進行詳細的講解。

1、getItemOffsets

/**
     * 執行ItemCount次
     *
     * @param outRect 操作item的邊距
     * @param view    itemView
     * @param parent  recyclerVIew
     * @param state   recyclerView當前的狀態
     */
    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        
    }

這個方法主要用來對Item進行偏移,類似於在xml中設置的padding的效果,大多數的時候我們都會在這裏偏移出我們需要畫的圖形的範圍出來。對於第一個參數outRect,表示的是包含Item的一個矩形,具體可以參考下圖。

outRect

圖中黃色區域表示的是我們Item所佔據並顯示的區域,而當我們對outRect設置了屬性之後,就可以得到藍色的區域。 

2、onDraw

 /**
     * 繪製的View會被Item覆蓋
     * 只執行一次
     *
     * @param c      畫布
     * @param parent recyclerView
     * @param state  recyclerView的狀態
     */
    @Override
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
          }

看到這個方法,瞭解過View的繪製流程的都會覺得很熟悉吧,這裏和View中的onDraw類似,都提供了一個Cancas對象給我們去自由發揮。不過不同的是,這裏的canvas起始座標點(0,0)所在的位置不是ItemView的左上角,而是上圖中outRect的左上角。結合上圖不難發現,我們在這個方法中繪製的圖像只能在藍色區域才能被看到,ItemView的區域將會遮擋在outRect之上。

3、onDrawOver

/**
     * 繪製的View會覆蓋在Item之上
     * 只執行一次
     *
     * @param c      畫布
     * @param parent recyclerView
     * @param state  recyclerView的狀態
     */
    @Override
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {

    }

與onDraw基本一樣的方法,只不過此處繪製的圖像將會覆蓋在ItemView之上。

關於ItemDecoration的基本介紹就結束了,下面的話我將用一個時間軸的例子來展示一下在日常編碼時ItemDecoration的作用。

效果圖

 看了效果圖,我們先將每一項需要偏移的距離給計算出來。創建一個類繼承自RecyclerView.ItemDecoration,並重寫其中的getItemOffsets和onDraw方法。

在getItemOffsets中,我們進行偏移,預留出我們的畫布區域。

public class TimeDecoration extends RecyclerView.ItemDecoration {
    private int screenWidth;//屏幕寬度
    private int radius = 20;//圓半徑
    private Paint paint;

    public TimeDecoration() {
        paint = new Paint();
    }

  ...

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        screenWidth = CommonUtil.getScreenWH(parent.getContext())[0];
        int marginLeft = 50;//itemView和時間軸之間的外邊距
        int left = screenWidth / 2 + radius + marginLeft;
        int bottom = 50;
        outRect.set(left,0,0,bottom);
    }
}

放張圖,比較容易理解這些距離是怎麼計算出來的,其中獲取屏幕寬度用的是一個工具類,大家可以去百度怎麼獲取。

分解圖

偏移後效果圖
偏移後的效果圖

下一步我們將在onDraw中繪製出時間軸。

首先我們先將每個時間軸對應的實心圓畫出來,座標的計算的話可以根據代碼以及之前給的分解圖自行理解。

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

        //循環當前所有顯示出來的ItemView
        int len = parent.getChildCount();
        View childView, nextChildView;
        float centerX = screenWidth / 2f - radius;//圓心X座標
        float centerY;
        for (int i = 0; i < len; i++) {
            childView = parent.getChildAt(i);
            centerY = childView.getY() + radius;//圓心Y座標

            //畫圓
            paint.setColor(Color.WHITE);
            paint.setStyle(Paint.Style.FILL);
            c.drawCircle(centerX,centerY,radius,paint);
        }
    }
實心圓圖
實心圓效果圖

然後我們再將每個實心圓用一條線(實際上是一個高>寬的矩形)連接起來。

@Override
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(c, parent, state);
        //c.drawColor(Color.RED);

        //循環當前所有顯示出來的ItemView
        int len = parent.getChildCount();
        View childView, nextChildView = null;
        float centerX = screenWidth / 2f - radius;//圓心X座標
        float centerY;
        int lineWidth = 10;//線寬

        for (int i = 0; i < len; i++) {
            childView = parent.getChildAt(i);
            centerY = childView.getY() + radius;//圓心Y座標

            //畫線
            float rect_left = centerX - (lineWidth / 2f);
            float rect_top = centerY + radius;
            float rect_right = rect_left + lineWidth;
            float rect_bottom;
            if (i < len - 1) {
                //不是最後一項,則線的高度默認爲當前項到下一項之間的高度
                nextChildView = parent.getChildAt(i + 1);
                rect_bottom = nextChildView.getY();
            }else {
                //最後一項由於沒有下一項,所以線的高度需要自己定義一個
                rect_bottom = centerY + radius + 50;
            }
            paint.setColor(Color.BLUE);
            c.drawRect(rect_left, rect_top, rect_right, rect_bottom, paint);

            //畫圓
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.FILL);
            c.drawCircle(centerX, centerY, radius, paint);
        }
    }

到此,一個時間軸就做好啦。放下最終的效果圖。

最終效果圖
最終效果圖

所有代碼已打包上傳的GitHub。

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