這是我見過有關Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration類(含實例講解)

轉載自:https://www.aliyun.com/jiaocheng/10353.html

  • 摘要:前言RecyclerView在Android開發中非常常用,如果能結合ItemDecoration類使用,那麼將大大提高RecyclerView的表現效果本文全面解析了ItemDecoration類,包括ItemDecoration類簡介、使用方法&;實例講解,最終結合自定義View實現時間軸UI開發,希望你們會喜歡。ItemDecoration類屬於RecyclerView的高級用法閱讀本文前請先學習RecyclerView的使用:Android開發:ListVie


  • 教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解)

    前言 

    RecyclerView在 Android開發中非常常用,如果能結合 ItemDecoration類使用,那麼將大大提高 RecyclerView的表現效果 
    本文全面解析了 ItemDecoration類,包括 ItemDecoration類簡介、使用方法 &; 實例講解,最終結合 自定義View實現 時間軸UI開發,希望你們會喜歡。


    ItemDecoration類屬於RecyclerView的高級用法
    閱讀本文前請先學習RecyclerView的使用:Android開發:ListView、AdapterView、RecyclerView全面解析



    目錄 
    教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解)
    目錄 

    1. ItemDecoration類 簡介 
    1.1 定義 

    RecyclerView類的靜態內部類


    1.2 作用 

    向 RecyclerView中的 ItemView 添加裝飾

     

     

    即繪製更多內容,豐富ItemView的UI效果

     


    2. 具體使用 

    ItemDecoration類中僅有3個方法,具體如下:


    public class TestDividerItemDecoration extends RecyclerView.ItemDecoration { 
    // 方法1:getItemOffsets() 
    // 作用:設置ItemView的內嵌偏移長度(inset) 
    @Override 
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
    ... 

    // 方法2:onDraw() 
    // 作用:在子視圖上設置繪製範圍,並繪製內容 
    // 類似平時自定義View時寫onDraw()一樣 
    // 繪製圖層在ItemView以下,所以如果繪製區域與ItemView區域相重疊,會被遮擋 
    @Override 
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 
    ... 

    // 方法3:onDrawOver() 
    // 作用:同樣是繪製內容,但與onDraw()的區別是:繪製在圖層的最上層 
    @Override 
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 
    ... 

    下面,我將詳細介紹這3個方法。


    2.1 getItemOffsets() 
    2.1.1 作用 

    設置ItemView的內嵌偏移長度(inset)


    如圖,其實 RecyclerView 中的 ItemView 外面會包裹着一個矩形( outRect) 
    內嵌偏移長度 是指:該矩形( outRect)與 ItemView的間隔 

    教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解)
    示意圖 

    內嵌偏移長度分爲4個方向:上、下、左、右,並由 outRect 中的 top、left、right、bottom參數 控制

     

    top、left、right、bottom參數默認 = 0,即矩形和Item重疊,所以看起來矩形就消失了

     



    教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解)
    示意圖 
    2.1.2 具體使用 
    @Override 
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
    // 參數說明: 
    // 1. outRect:全爲 0 的 Rect(包括着Item) 
    // 2. view:RecyclerView 中的 視圖Item 
    // 3. parent:RecyclerView 本身 
    // 4. state:狀態 
    outRect.set(50, 0, 0,50); 
    // 4個參數分別對應左(Left)、上(Top)、右(Right)、下(Bottom) 
    // 上述語句代表:左&;下偏移長度=50px,右 &; 上 偏移長度 = 0 
    ... 

    教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解)
    示意圖 
    2.1.3 源碼分析 

    RecyclerView本質上是一個自定義 ViewGroup,子視圖 child = 每個 ItemView 
    其通過 LayoutManager測量並佈局 ItemView 
    public void measureChild(View child, int widthUsed, int heightUsed) { 
    // 參數說明: 
    // 1. child:要測量的子view(ItemView) 
    // 2. widthUsed: 一個ItemView的所有ItemDecoration佔用的寬度(px) 
    // 3. heightUsed:一個ItemView的所有ItemDecoration佔用的高度(px) 
    final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 
    final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 
    // 累加當前ItemDecoration 4個屬性值->>分析1 
    widthUsed += insets.left + insets.right; 
    // 計算每個ItemView的所有ItemDecoration的寬度 
    heightUsed += insets.top + insets.bottom; 
    // 計算每個ItemView的所有ItemDecoration的高度 
    final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), 
    getPaddingLeft() + getPaddingRight() + widthUsed, lp.width, 
    canScrollHorizontally()); 
    // 測量child view(ItemView)的寬度 
    // 第三個參數設置 child view 的 padding,即ItemView的Padding 
    // 而該參數把 insets 的值算進去,所以insets 值影響了每個 ItemView 的 padding值 
    // 高度同上 
    final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), 
    getPaddingTop() + getPaddingBottom() + heightUsed, lp.height, 
    canScrollVertically()); 
    if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { 
    child.measure(widthSpec, heightSpec); 


    // 分析完畢,請跳出 
    <-- 分析1:getItemDecorInsetsForChild()--> 
    Rect getItemDecorInsetsForChild(View child) { 
    final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 
    insets.set(0, 0, 0, 0); 
    for (int i = 0; i < decorCount; i++) { 
    mTempRect.set(0, 0, 0, 0); 
    // 獲取getItemOffsets() 中設置的值 
    mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState); 
    // 將getItemOffsets() 中設置的值添加到insets 變量中 
    insets.left += mTempRect.left; 
    insets.top += mTempRect.top; 
    insets.right += mTempRect.right; 
    insets.bottom += mTempRect.bottom; 

    // 最終返回 
    return insets; 

    // insets介紹 
    // 1. 作用: 
    // a. 把每個ItemView的所有 ItemDecoration 的 getItemOffsets 中設置的值累加起來,(每個ItemView可添加多個ItemDecoration) 
    // 即把每個ItemDecoration的left, top, right, bottom 4個屬性分別累加 
    // b. 記錄上述結果 
    // c. inset就像padding和margin一樣,會影響view的尺寸和位置 
    // 2. 使用場景:設置View的邊界大小,使得其大小>View的背景大小 
    // 如 按鈕圖標(View的背景)較小,但是我們希望按鈕有較大的點擊熱區(View的邊界大小) 
    // 返回到分析1進來的原處 
    總結 
    結論: outRect4個屬性值影響着 ItemView的Padding值 
    具體過程:在 RecyclerView進行子 View寬高測量時( measureChild()),會將 getItemOffsets()裏設置的 outRect4個屬性值( Top、Bottom、Left、Right)通過 insert值累加 ,並最終添加到子 View的 Padding屬性中 

    2.2 onDraw() 
    2.2.1 作用 

    通過 Canvas 對象繪製內容


    2.2.2 具體使用 
    使用方法類似自定義View時的 onDraw()

     

    請看我寫的自定義View文章:自定義View Draw過程- 最易懂的自定義View原理系列(4)

     



    @Override 
    public void onDraw(Canvas c, RecyclerView parent, 
    RecyclerView.State state) { 
    .... 
    // 使用類似自定義View時的 onDraw() 

    2.2.3 特別注意 

    注意點1:Itemdecoration的onDraw()繪製會先於ItemView的onDraw()繪製,所以如果在Itemdecoration的onDraw()中繪製的內容在ItemView邊界內,就會被ItemView遮擋住。如下圖:

     

     

    此現象稱爲onDraw()的 OverDraw現象

     


    教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解)
    示意圖 

    解決方案:配合前面的 getItemOffsets() 一起使用在outRect矩形 與 ItemView的間隔區域 繪製內容

     

     

    即:通過getItemOffsets() 設置與 Item 的間隔區域,從而獲得與ItemView不重疊的繪製區域

     


    教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解)
    示意圖 

    注意點2: getItemOffsets() 針對是每一個 ItemView的,而 onDraw() 針對 RecyclerView 本身

     

    解決方案:在 使用onDraw()繪製時,需要先遍歷RecyclerView 的所有ItemView分別獲取它們的位置信息,然後再繪製內容

     


    此處遍歷的RecyclerView的ItemView(即Child view),並不是 Adapter 設置的每一個 item,而是可見的 item 
    因爲只有可見的Item 纔是RecyclerView的 Child view


    @Override 
    public void onDraw(Canvas c, RecyclerView parent, 
    RecyclerView.State state) { 
    // RecyclerView 的左邊界加上 paddingLeft距離 後的座標位置 
    final int left = parent.getPaddingLeft(); 
    // RecyclerView 的右邊界減去 paddingRight 後的座標位置 
    final int right = parent.getWidth() - parent.getPaddingRight(); 
    // 即左右邊界就是 RecyclerView 的 ItemView區域 
    // 獲取RecyclerView的Child view的個數 
    final int childCount = parent.getChildCount(); 
    // 設置佈局參數 
    final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 
    .getLayoutParams(); 
    // 遍歷每個RecyclerView的Child view 
    // 分別獲取它們的位置信息,然後再繪製內容 
    for (int i = 0; i < childCount; i++) { 
    final View child = parent.getChildAt(i); 
    int index = parent.getChildAdapterPosition(view); 
    // 第一個Item不需要繪製 
    if ( index == 0 ) { 
    continue; 

    // ItemView的下邊界:ItemView 的 bottom座標 + 距離RecyclerView底部距離 +translationY 
    final int top = child.getBottom() + params.bottomMargin + 
    Math.round(ViewCompat.getTranslationY(child)); 
    // 繪製分割線的下邊界 = ItemView的下邊界+分割線的高度 
    final int bottom = top + mDivider.getIntrinsicHeight(); 
    mDivider.setBounds(left, top, right, bottom); 
    mDivider.draw(c); 



    2.2.4 應用場景 

    在豐富 ItemView 的顯示效果,即在ItemView 的基礎上繪製內容

     

     

    如分割線等等

     


    2.2.5 實例講解 
    實例說明:在 ItemView設計一個高度爲 10 px 的紅色分割線 
    思路 
    通過 getItemOffsets()設置與 Item 的下間隔區域 = 10 px

     

    設置好onDraw()可繪製的區域

     


    通過 onDraw()繪製一個高度 = 10px的矩形(填充顏色=紅色) 


    教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解)
    示意圖 

    具體實現 

    步驟1:自定義ItemDecoration類

     

    ItemDecoration.java


    public class DividerItemDecoration extends RecyclerView.ItemDecoration { 
    private Paint mPaint; 
    // 在構造函數裏進行繪製的初始化,如畫筆屬性設置等 
    public DividerItemDecoration() { 
    mPaint = new Paint(); 
    mPaint.setColor(Color.RED); 
    // 畫筆顏色設置爲紅色 

    // 重寫getItemOffsets()方法 
    // 作用:設置矩形OutRect 與 Item 的間隔區域 
    @Override 
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
    super.getItemOffsets(outRect, view, parent, state); 

    int itemPosition = parent.getChildAdapterPosition(view); 
    // 獲得每個Item的位置 
    // 第1個Item不繪製分割線 
    if (itemPosition != 0) { 
    outRect.set(0, 0, 0, 10); 
    // 設置間隔區域爲10px,即onDraw()可繪製的區域爲10px 


    // 重寫onDraw() 
    // 作用:在間隔區域裏繪製一個矩形,即分割線 
    @Override 
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 
    super.onDraw(c, parent, state); 
    // 獲取RecyclerView的Child view的個數 
    int childCount = parent.getChildCount(); 
    // 遍歷每個Item,分別獲取它們的位置信息,然後再繪製對應的分割線 
    for ( int i = 0; i < childCount; i++ ) { 
    // 獲取每個Item的位置 
    final View child = parent.getChildAt(i); 
    int index = parent.getChildAdapterPosition(child); 
    // 第1個Item不需要繪製 
    if ( index == 0 ) { 
    continue; 

    // 獲取佈局參數 
    final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 
    .getLayoutParams(); 
    // 設置矩形(分割線)的寬度爲10px 
    final int mDivider = 10; 
    // 根據子視圖的位置 &; 間隔區域,設置矩形(分割線)的2個頂點座標(左上 &; 右下) 
    // 矩形左上頂點 = (ItemView的左邊界,ItemView的下邊界) 
    // ItemView的左邊界 = RecyclerView 的左邊界 + paddingLeft距離 後的位置 
    final int left = parent.getPaddingLeft(); 
    // ItemView的下邊界:ItemView 的 bottom座標 + 距離RecyclerView底部距離 +translationY 
    final int top = child.getBottom() + params.bottomMargin + 
    Math.round(ViewCompat.getTranslationY(child)); 
    // 矩形右下頂點 = (ItemView的右邊界,矩形的下邊界) 
    // ItemView的右邊界 = RecyclerView 的右邊界減去 paddingRight 後的座標位置 
    final int right = parent.getWidth() - parent.getPaddingRight(); 
    // 繪製分割線的下邊界 = ItemView的下邊界+分割線的高度 
    final int bottom = top + mDivider; 

    // 通過Canvas繪製矩形(分割線) 
    c.drawRect(left,top,right,bottom,mPaint); 


    步驟2:在設置RecyclerView時添加該分割線即可


    Rv = (RecyclerView) findViewById(R.id.my_recycler_view); 
    //使用線性佈局 
    LinearLayoutManager layoutManager = new LinearLayoutManager(this); 
    Rv.setLayoutManager(layoutManager); 
    Rv.setHasFixedSize(true); 
    // 通過自定義分割線類 添加分割線 
    Rv.addItemDecoration(new DividerItemDecoration()); 
    //爲ListView綁定適配器 
    myAdapter = new MyAdapter(this,listItem); 
    Rv.setAdapter(myAdapter); 
    myAdapter.setOnItemClickListener(this); 
    2.2.6 結果展示 
    教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解) 
    示意圖 
    2.2.7 源碼地址 

    Carson_Ho的Github地址:RecyclerView_ItemDecoration


    2.3 onDrawOver() 
    2.3.1 作用 
    與 onDraw()類似,都是繪製內容 
    但與 onDraw()的區別是: Itemdecoration的 onDrawOver()繪製 是後於 ItemView的 onDraw()繪製


    即不需要考慮繪製內容被ItemView遮擋的問題,反而 ItemView會被onDrawOver()繪製的內容遮擋
    繪製時機比較:
    Itemdecoration.onDraw()> ItemView.onDraw() > Itemdecoration.onDrawOver()



    教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解)
    示意圖 
    2.3.2 具體使用 
    使用方法類似自定義View時的 onDraw()

     

    請看我寫的自定義View文章:自定義View Draw過程- 最易懂的自定義View原理系列(4)

     



    @Override 
    public void onDrawOver(Canvas c, RecyclerView parent, 
    RecyclerView.State state) { 
    .... 
    // 使用類似自定義View時的 onDraw() 

    2.3.3 應用場景 

    在 RecyclerView / 特定的 ItemView 上繪製內容,如蒙層、重疊內容等等


    2.3.4 實例講解 
    實例說明:在 RecyclerView 上每個 ItemView 上疊加一個角標 

    教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解) 
    角度示意圖 

    具體代碼實現 

    步驟1:自定義 ItemDecoration類


    public class DividerItemDecoration extends RecyclerView.ItemDecoration { 
    private Paint mPaint; 
    private Bitmap mIcon; 
    // 在構造函數裏進行繪製的初始化,如畫筆屬性設置等 
    public DividerItemDecoration(Context context) { 
    mPaint = new Paint(); 
    mPaint.setColor(Color.RED); 
    // 畫筆顏色設置爲紅色 
    // 獲取圖片資源 
    mIcon = BitmapFactory.decodeResource(context.getResources(), R.mipmap.logo); 

    // 重寫onDrawOver() 
    // 將角度繪製到ItemView上 
    @Override 
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 
    super.onDrawOver(c, parent, state); 
    // 獲取Item的總數 
    int childCount = parent.getChildCount(); 
    // 遍歷Item 
    for ( int i = 0; i < childCount; i++ ) { 
    // 獲取每個Item的位置 
    View view = parent.getChildAt(i); 
    int index = parent.getChildAdapterPosition(view); 
    // 設置繪製內容的座標(ItemView的左邊界,ItemView的上邊界) 
    // ItemView的左邊界 = RecyclerView 的左邊界 = paddingLeft距離 後的位置 
    final int left = parent.getWidth()/2; 
    // ItemView的上邊界 
    float top = view.getTop(); 
    // 第1個ItemView不繪製 
    if ( index == 0 ) { 
    continue; 

    // 通過Canvas繪製角標 
    c.drawBitmap(mIcon,left,top,mPaint); 


    步驟2:在設置RecyclerView時添加即可


    Rv = (RecyclerView) findViewById(R.id.my_recycler_view); 
    //使用線性佈局 
    LinearLayoutManager layoutManager = new LinearLayoutManager(this); 
    Rv.setLayoutManager(layoutManager); 
    Rv.setHasFixedSize(true); 
    //用自定義分割線類設置分割線 
    Rv.addItemDecoration(new DividerItemDecoration()); 
    //爲ListView綁定適配器 
    myAdapter = new MyAdapter(this,listItem); 
    Rv.setAdapter(myAdapter); 
    myAdapter.setOnItemClickListener(this); 
    2.3.5 結果展示 
    教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解) 
    示意圖 
    2.3.6 源碼地址 

    Carson_Ho的Github地址:RecyclerView_ItemDecoration


    3. 使用總結 

    我用一張圖總結RecyclerView ItemDecoration類的使用


    教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解)
    示意圖 

    4. 結合自定義View的實踐應用:時間軸 
     

    Android開發中,時間軸的 UI需求非常常見,如下圖:


    教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解)
    示意圖 
     

    本次實例將結合 自定義View &; RecyclerView的知識,手把手教你實現該常見 &; 實用的自定義View:時間軸

     

     

    具體請看文章:Android 自定義View實戰系列 :時間軸

     




    下一篇文章我將繼續結合 自定義 View &; RecyclerView.ItemDecoration類進行一些有趣的自定義 View實例講解,有興趣可以繼續關注Carson_Ho的安卓開發筆記 

    請點贊!因爲你的鼓勵是我寫作的最大動力! 

     

    相關文章閱讀
    Android開發:最全面、最易懂的Android屏幕適配解決方案
    Android事件分發機制詳解:史上最全面、最易懂
    Android開發:史上最全的Android消息推送解決方案
    Android開發:最全面、最易懂的Webview詳解
    Android開發:JSON簡介及最全面解析方法!
    Android四大組件:Service服務史上最全面解析
    Android四大組件:BroadcastReceiver史上最全面解析

     


    歡迎關注Carson_Ho的簡書! 

    不定期分享關於安卓開發的乾貨,追求短、平、快,但卻不缺深度。


    教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解)


     
  • 以上是教你玩轉 Android RecyclerView:深入解析 RecyclerView.ItemDecoration類(含實例講解)的內容,更多 RecyclerViewItemDecoration 講解 實例 解析 深入 Android 的內容,請您使用右上方搜索功能獲取相關信息
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章