ItemDecoration固定头部(分组+分割线)

参考资料:https://www.jianshu.com/p/b46a4ff7c10a

先来张最终效果图

具体说明代码注释有了,这里就不说了。

  1. public class BudgetContrastDataStickHeaderDecoration extends RecyclerView.ItemDecoration {
  2. private Context context;
  3. private Drawable drawable;
  4. private StickHeaderCallBack callBack;
  5. private Paint paint;
  6. private TextPaint textPaint;
  7. private TextPaint.FontMetrics fontMetrics;
  8.  
  9. public BudgetContrastDataStickHeaderDecoration(Context context, StickHeaderCallBack callBack) {
  10. this.context = context;
  11. this.callBack = callBack;
  12. // 用於獲取分割線高度
  13. int[] ATTRS = new int[]{android.R.attr.listDivider};
  14. TypedArray typedArray = this.context.obtainStyledAttributes(ATTRS);
  15. drawable = typedArray.getDrawable(0);
  16. typedArray.recycle();
  17. // 分組欄背景顏色
  18. paint = new Paint();
  19. paint.setColor(this.context.getResources().getColor(R.color.stick_header));
  20. // 分組文字
  21. textPaint = new TextPaint();
  22. // 粗體
  23. textPaint.setTypeface(Typeface.DEFAULT_BOLD);
  24. // 抗鋸齒
  25. textPaint.setAntiAlias(true);
  26. // 字體大小
  27. textPaint.setTextSize(40);
  28. // 字體顏色
  29. textPaint.setColor(Color.BLACK);
  30. // 用於計算字體基準
  31. fontMetrics = new Paint.FontMetrics();
  32. textPaint.getFontMetrics(fontMetrics);
  33. // 左對齊
  34. textPaint.setTextAlign(Paint.Align.LEFT);
  35. }
  36.  
  37. @Override
  38. public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
  39. super.onDraw(c, parent, state);
  40. c.save();
  41. int left;
  42. int right;
  43. // 從DividerItemDecoration源碼抄來,大概為根據Padding切割畫布
  44. if (parent.getClipToPadding()) {
  45. left = parent.getPaddingLeft();
  46. right = parent.getWidth() - parent.getPaddingRight();
  47. c.clipRect(left, parent.getPaddingTop(), right,
  48. parent.getHeight() - parent.getPaddingBottom());
  49. } else {
  50. left = 0;
  51. right = parent.getWidth();
  52. }
  53.  
  54. int childCount = parent.getChildCount();
  55. Rect mBounds = new Rect();
  56. for (int i = 0; i < childCount; i++) {
  57. View child = parent.getChildAt(i);
  58. //獲取itemView的範圍,這個範圍包含margin和offset,它們被保存在mBounds中
  59. parent.getDecoratedBoundsWithMargins(child, mBounds);
  60. int bottom = mBounds.bottom + Math.round(child.getTranslationY());
  61. int top = bottom - drawable.getIntrinsicHeight();
  62. drawable.setBounds(left, top, right, bottom);
  63. drawable.draw(c);
  64. }
  65. c.restore();
  66. }
  67.  
  68. @Override
  69. public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
  70. super.onDrawOver(c, parent, state);
  71. c.save();
  72. float left;
  73. float right;
  74. // 從DividerItemDecoration源碼抄來,大概為根據Padding切割畫布
  75. if (parent.getClipToPadding()) {
  76. left = parent.getPaddingLeft();
  77. right = parent.getWidth() - parent.getPaddingRight();
  78. c.clipRect(left, parent.getPaddingTop(), right,
  79. parent.getHeight() - parent.getPaddingBottom());
  80. } else {
  81. left = 0;
  82. right = parent.getWidth();
  83. }
  84. // left、right、top、bottom實現Padding 3 的效果
  85. left += context.getResources().getDimensionPixelSize(R.dimen.stick_header_padding);
  86. right -= context.getResources().getDimensionPixelSize(R.dimen.stick_header_padding);
  87. int childCount = parent.getChildCount();
  88. for (int i = 0; i < childCount; i++) {
  89. View child = parent.getChildAt(i);
  90. int position = parent.getChildAdapterPosition(child);
  91. String deptName = this.callBack.getDeptName(parent.getAdapter(), position);
  92. if (deptName != null && deptName.length() > 0) {
  93. /*
  94. * 1.position=0 列表的第一個項目始終顯示固定頭部
  95. * 2.分組中的第一個項目顯示固定頭部
  96. * 3.不是分組中的第一個,且頂部座標已是0或者負數,表示分組中的第一個已經滾動到不可見區域,但分組中其他項目仍然顯示在屏幕上
  97. * child.getBottom()+drawable.getIntrinsicHeight()>=0 底部+分隔線>=0 表示該項目還在屏幕中,繼續顯示固定表頭
  98. */
  99. if (position == 0 || isFirstInGroup(parent.getAdapter(), position) || (!isFirstInGroup(parent.getAdapter(), position) && position > 0 && child.getTop() <= 0 && child.getBottom() + drawable.getIntrinsicHeight() >= 0)) {
  100. // 分組欄位是VIEW的頂部座標
  101. float bottom = Math.max(child.getTop(), this.context.getResources().getDimensionPixelSize(R.dimen.stick_header_height));
  102. float top = bottom - this.context.getResources().getDimensionPixelSize(R.dimen.stick_header_height);
  103. // 判斷下一個項目是否當前項目的分組內
  104. if (position + 1 < parent.getAdapter().getItemCount() && !this.callBack.getDeptName(parent.getAdapter(), position + 1).equals(deptName)) {
  105. float temp = child.getBottom() + drawable.getIntrinsicHeight() - bottom;
  106. // 如果不是,判斷當前項目的底部+分割線是否小於固定頭部的底部座標
  107. if (temp < 0) {
  108. // 相當於移動固定頭部
  109. top+=temp;
  110. bottom+=temp;
  111. }
  112. }
  113. top += context.getResources().getDimensionPixelSize(R.dimen.stick_header_padding);
  114. bottom -= context.getResources().getDimensionPixelSize(R.dimen.stick_header_padding);
  115. // 畫圓角方塊,rx:橢圓的短半徑,ry:橢圓的長半徑
  116. c.drawRoundRect(left, top, right, bottom, 50, 80, this.paint);
  117. float baseline = top + (bottom - top - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
  118. // left + 30 字體偏移,隨個人喜歡
  119. c.drawText(deptName, left + 30, baseline, textPaint);
  120. }
  121. }
  122. }
  123. c.restore();
  124. }
  125.  
  126. @Override
  127. public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
  128. super.getItemOffsets(outRect, view, parent, state);
  129. int stickHeaderHeight = 0;
  130. int position = parent.getChildAdapterPosition(view);
  131. String deptName = this.callBack.getDeptName(parent.getAdapter(), position);
  132. if (deptName != null && deptName.length() > 0) {
  133. if (position == 0 || isFirstInGroup(parent.getAdapter(), position)) {
  134. stickHeaderHeight = this.context.getResources().getDimensionPixelSize(R.dimen.stick_header_height);
  135. }
  136. }
  137. if (drawable == null) {
  138. outRect.set(0, stickHeaderHeight, 0, 0);
  139. return;
  140. }
  141. outRect.set(0, stickHeaderHeight, 0, drawable.getIntrinsicHeight());
  142. }
  143.  
  144. /**
  145. * 是否分組中的第一個
  146. *
  147. * @param adapter RecyclerView適配器
  148. * @param position 項目序號
  149. * @return true or false
  150. */
  151. private boolean isFirstInGroup(RecyclerView.Adapter adapter, int position) {
  152. if (position == 0) {
  153. return true;
  154. } else {
  155. String previousDeptName = this.callBack.getDeptName(adapter, position - 1);
  156. String deptName = this.callBack.getDeptName(adapter, position);
  157. return !deptName.equals(previousDeptName);
  158. }
  159. }
  160.  
  161. public interface StickHeaderCallBack {
  162. String getDeptName(RecyclerView.Adapter adapter, int position);
  163. }
  164. }

 

实现的接口StickHeaderCallBack就是return一个字符串。

  1. @Override
  2. public String getDeptName(RecyclerView.Adapter adapter, int position) {
  3. if (adapter instanceof BudgetAdapter) {
  4. BudgetAdapter budgetAdapter = (BudgetAdapter) adapter;
  5. return budgetAdapter.budgetItemDataList.get(position).getDept_name();
  6. } else {
  7. return "";
  8. }
  9. }

--------------------- 本文来自 msquan 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/msquan/article/details/80270302?utm_source=copy

posted @ 2018-10-08 17:58  天涯海角路  阅读(257)  评论(0)    收藏  举报