本篇文章主要记录学习ItemDecoration的要点
1、目前recyclerView的ItemDecoration是个抽象类,我们学习它的时候可以去看它的实现类DividerItemDecoration
这里面主要有三个方法:
onDraw、onDrawOver、getItemOffsets
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
if (mDivider == null) {
Log.w(TAG, "@android:attr/listDivider was not set in the theme used for this "
+ "DividerItemDecoration. Please set that attribute all call setDrawable()");
}
a.recycle();
setOrientation(orientation);
}
上面的代码中: mDivider = a.getDrawable(0); 可以看到分割线就是一个Drawable
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (parent.getLayoutManager() == null || mDivider == null) {
return;
}
if (mOrientation == VERTICAL) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
上面的方向判断里面的逻辑其实差不多,我们都可以猜到其实就是根据不同的方向来绘制分割线,我们看一下: drawVertical(c, parent);
private void drawVertical(Canvas canvas, RecyclerView parent) {
canvas.save();
final int left;
final int right;
//noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
//是否设置了内间距
if (parent.getClipToPadding()) {
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
canvas.clipRect(left, parent.getPaddingTop(), right,
parent.getHeight() - parent.getPaddingBottom());
} else {
left = 0;
right = parent.getWidth();
}
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
parent.getDecoratedBoundsWithMargins(child, mBounds);
//child.getTranslationY()与父容器Y轴的偏移量
final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
//getIntrinsicHeightdivider的固定高度
final int top = bottom - mDivider.getIntrinsicHeight();
//设置分割线的边界
mDivider.setBounds(left, top, right, bottom);
//进行绘制
mDivider.draw(canvas);
}
canvas.restore();
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
if (mDivider == null) {
outRect.set(0, 0, 0, 0);
return;
}
if (mOrientation == VERTICAL) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
outRect根据水平还是垂直留出画分割线的空白
此次要绘制的效果非常简单,当下面的item滑动上来,没一种类型有20条数据,每一组的组头是快乐家族0,其实是个分割线,我们为它预留了更大的空间并绘制文字快乐家族x,并固定在顶部,当下一组的快乐家族1滑动上来就把快乐家族0顶上去,实现吸顶效果。 目前不支持上传视频只能这样描述了。
我们先来看一下执行顺序:
1、 recyclerView.addItemDecoration(new StarDecoration(this));
2、 public void addItemDecoration(@NonNull ItemDecoration decor) {
addItemDecoration(decor, -1);
}
3、requestLayout
4、measureChildWithMargins->
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
5、可以看到我们的分割线预留是可以定义多个的,它是一个数组。
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
6、在布局的时候:layoutDecoratedWithMargins 把我们的分割线都设置进去了
public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right,
int bottom) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = lp.mDecorInsets;
child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
right - insets.right - lp.rightMargin,
bottom - insets.bottom - lp.bottomMargin);
}
而onDraw调用之后才是itemView绘制之后再调用onDrawOver
所以我们先在getItemOffsets预留出位置,之后在onDraw绘制分割线,之后在onDrawOver绘制吸顶。
bean
public class Star {
private String name;
private String groundName;
public Star(String name, String groundName) {
this.name = name;
this.groundName = groundName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGroundName() {
return groundName;
}
public void setGroundName(String groundName) {
this.groundName = groundName;
}
}
adapter
public class StarAdapter extends RecyclerView.Adapter<StarAdapter.StarViewHolder> {
private Context context;
private List<Star> starList;
public StarAdapter(Context context, List<Star> starList) {
this.context = context;
this.starList = starList;
}
/**
* 是否是组的第一个item
*
* @param position
* @return
*/
public boolean isGourpHeader(int position) {
if (position == 0) {
return true;
} else {
String currentGroupName = getGroupName(position);
String preGroupName = getGroupName(position - 1);
if (preGroupName.equals(currentGroupName)) {
return false;
} else {
return true;
}
}
}
public String getGroupName(int position) {
return starList.get(position).getGroundName();
}
@NonNull
@Override
public StarAdapter.StarViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.rv_item_star, null);
return new StarViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull StarAdapter.StarViewHolder holder, int position) {
holder.tv.setText(starList.get(position).getName());
}
@Override
public int getItemCount() {
return starList == null ? 0 : starList.size();
}
public class StarViewHolder extends RecyclerView.ViewHolder {
private TextView tv;
public StarViewHolder(@NonNull View itemView) {
super(itemView);
tv = itemView.findViewById(R.id.tv_star);
}
}
}
调用
public class MainActivity extends AppCompatActivity {
private List<Star> starList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
RecyclerView recyclerView = findViewById(R.id.rv_list);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
// 自定义分割线
recyclerView.addItemDecoration(new StarDecoration(this));
recyclerView.setAdapter(new StarAdapter(this, starList));
}
private void init() {
starList = new ArrayList<>();
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 20; j++) {
if (i % 2 == 0) {
starList.add(new Star("何炅" + j, "快乐家族" + i));
} else {
starList.add(new Star("汪涵" + j, "天天兄弟" + i));
}
}
}
}
}
自定义分割线
public class StarDecoration extends RecyclerView.ItemDecoration {
private int groupHeaderHeight;
private Paint headPaint;
private Paint textPaint;
private Rect textRect;
public StarDecoration(Context context) {
groupHeaderHeight = dp2px(context, 100);
headPaint = new Paint();
headPaint.setColor(Color.RED);
textPaint = new Paint();
textPaint.setTextSize(50);
textPaint.setColor(Color.WHITE);
textRect = new Rect();
}
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
if (parent.getAdapter() instanceof StarAdapter) {
StarAdapter adapter = (StarAdapter) parent.getAdapter();
// 当前屏幕的item个数
int count = parent.getChildCount();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < count; i++) {
// 获取对应i的View
View view = parent.getChildAt(i);
// 获取View的布局位置
int position = parent.getChildLayoutPosition(view);
// 是否是头部
boolean isGroupHeader = adapter.isGourpHeader(position);
if (isGroupHeader && view.getTop() - groupHeaderHeight - parent.getPaddingTop() >= 0) {
c.drawRect(left, view.getTop() - groupHeaderHeight, right, view.getTop(), headPaint);
String groupName = adapter.getGroupName(position);
textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
c.drawText(groupName, left + 20, view.getTop() -
groupHeaderHeight / 2 + textRect.height() / 2, textPaint);
} else if (view.getTop() - groupHeaderHeight - parent.getPaddingTop() >= 0) {
// 分割线
c.drawRect(left, view.getTop() - 4, right, view.getTop(), headPaint);
}
}
}
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
if (parent.getAdapter() instanceof StarAdapter) {
StarAdapter adapter = (StarAdapter) parent.getAdapter();
// 返回可见区域内的第一个item的position
int position = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
// 获取对应position的View
View itemView = parent.findViewHolderForAdapterPosition(position).itemView;
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int top = parent.getPaddingTop();
// 当第二个是组的头部的时候
boolean isGroupHeader = adapter.isGourpHeader(position + 1);
if (isGroupHeader) {
int bottom = Math.min(groupHeaderHeight, itemView.getBottom() - parent.getPaddingTop());
c.drawRect(left, top, right, top + bottom, headPaint);
String groupName = adapter.getGroupName(position);
textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
// 绘制文字的高度不能超出区域
c.clipRect(left, top, right, top + bottom);
c.drawText(groupName, left + 20, top + bottom
- groupHeaderHeight / 2 + textRect.height() / 2, textPaint);
} else {
c.drawRect(left, top, right, top + groupHeaderHeight, headPaint);
String groupName = adapter.getGroupName(position);
textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
c.drawText(groupName, left + 20, top + groupHeaderHeight / 2 + textRect.height() / 2, textPaint);
}
}
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (parent.getAdapter() instanceof StarAdapter) {
StarAdapter adapter = (StarAdapter) parent.getAdapter();
int position = parent.getChildLayoutPosition(view);
boolean isGroupHeader = adapter.isGourpHeader(position);
// 怎么判断 itemView是头部
if (isGroupHeader) {
// 如果是头部,预留更大的地方
outRect.set(0, groupHeaderHeight, 0, 0);
} else {
// 1像素
outRect.set(0, 4, 0, 0);
}
}
}
private int dp2px(Context context, float dpValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale * 0.5f);
}
}