RecyclerView.ItemDecoration可以对item添加分割线及添加视图!
下面我们进行分析!
public class linearSpacingItemDecoration extends RecyclerView.ItemDecoration {
pr
/**
* 可以实现类似padding的效果,但是如果以重写这个方法设置下bottom,那么他的背景颜色是父view的背景颜色
*
* @param outRect
* @param view
* @param parent
* @param state
*/
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = spacing;
outRect.left = spacing;
outRect.top = spacing;
outRect.right = spacing;
}
/**
* 可以实现类似绘制背景的效果,内容在上面,但是如果这个方法结合getItemOffsets()那就不一样了,他会把绘制的padding
* 绘制自己想要的颜色!
*
* @param c
* @param parent
* @param state
*/
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
int size = parent.getChildCount();
for (int i = 0; i < size; i++) {
View view = parent.getChildAt(i);
c.drawRect(view.getLeft(),view.getBottom(), view.getRight(), view.getBottom() + spacing * 2, mPaint);
}
}
/**
* 可以绘制在内容的上面,覆盖内容
* 可以绘制标签之类的附加属性
*
* @param c
* @param parent
* @param state
*/
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int size = parent.getChildCount();
for (int i = 0; i < size; i++) {
View view = parent.getChildAt(i);
c.drawText("onDrawOver", view.getLeft(), view.getBottom() - spacing, mPaintText);
}
}
}
我们只需要写的类继承 RecyclerView.ItemDecoration就ok了,
你怎么用呢?
mRecyclerView.addItemDecoration(new linearSpacingItemDecoration());
这样就可以用我们的ItemDecoration了!
我们只要重写上面的方法就可以做到我们想要的效果了!
那么RecyclerView内部是怎么实现的呢?
addItemDecoration()这一方法,
public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
this.addItemDecoration(decor, -1);
}
public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor, int index) {
if (this.mLayout != null) {
this.mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or layout");
}
if (this.mItemDecorations.isEmpty()) {
this.setWillNotDraw(false);
}
if (index < 0) {
this.mItemDecorations.add(decor);
} else {
this.mItemDecorations.add(index, decor);
}
this.markItemDecorInsetsDirty();
this.requestLayout();
}
final ArrayList<RecyclerView.ItemDecoration> mItemDecorations;
所以说当我们去addItemDecoration的时候,会加到RecyclerView本身的数组中!
而对也我们重写的RecyclerView.ItemDecoration的3个方法是什么时候调用的呢?(都是在RecyclerView中!)
首先我们先看getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state)这一方法
public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right, int bottom) {
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)child.getLayoutParams();
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);
}
而这个insets就是上面重写的getItemOffsets()这一方法中的 Rect outRect,这面就是对childView的摆放!
这面应该会有问题?
ItemDecoration 应该是数组啊!为啥会只返回一个Rect呢?正因为如下所示Offsets是被叠加的。
Rect getItemDecorInsetsForChild(View child) {
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)child.getLayoutParams();
if (!lp.mInsetsDirty) {
return lp.mDecorInsets;
} else if (this.mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
return lp.mDecorInsets;
} else {
Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
int decorCount = this.mItemDecorations.size();
for(int i = 0; i < decorCount; ++i) {
this.mTempRect.set(0, 0, 0, 0);
((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).getItemOffsets(this.mTempRect, child, this, this.mState);
insets.left += this.mTempRect.left;
insets.top += this.mTempRect.top;
insets.right += this.mTempRect.right;
insets.bottom += this.mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}
}
然后再看onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) 这一方法
public void onDraw(Canvas c) {
super.onDraw(c);
int count = this.mItemDecorations.size();
for(int i = 0; i < count; ++i) {
((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).onDraw(c, this, this.mState);
}
}
在绘制childView前会进行绘制ItemDecorations的onDraw()方法!
最后看onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) 这一方法
public void draw(Canvas c) {
super.draw(c);
int count = this.mItemDecorations.size();
for(int i = 0; i < count; ++i) {
((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).onDrawOver(c, this, this.mState);
}
............
}
在绘制之后会进行绘制ItemDecorations的onDrawOver()方法!
好的这面方法就分析好了。
那下面给大家带来一一个仿通讯录粘性列表:
自定义RecyclerView.ItemDecoration
public class LinearMountingItemDecoration extends RecyclerView.ItemDecoration {
private int spacing; //分割线的高度
private Paint mPaintSpacing; //分割线的画笔
private Paint mPaintText; //展示文字的画笔
Paint.FontMetrics fontMetrics;
//通过接口获取当前下标所展示的文本内容
private OnGetTextListener onGetTextListener;
public OnGetTextListener getOnGetTextListener() {
return onGetTextListener;
}
public void setOnGetTextListener(OnGetTextListener onGetTextListener) {
this.onGetTextListener = onGetTextListener;
}
public LinearMountingItemDecoration(int spacing) {
this.spacing = spacing;
mPaintSpacing = new Paint();
mPaintSpacing.setColor(Color.YELLOW);
mPaintText = new Paint();
mPaintText.setColor(Color.parseColor("#80ff0000"));
mPaintText.setTextAlign(Paint.Align.LEFT);
mPaintText.setTextSize(Utils.dp2px(28));
fontMetrics = new Paint.FontMetrics();
mPaintText.getFontMetrics(fontMetrics);
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (isFirstInGroup(parent.getChildAdapterPosition(view))) {
//绘制分割线
outRect.top = spacing;
}
;
}
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
int size = parent.getChildCount();
for (int i = 0; i < size; i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
String content = onGetTextListener.getText(position).substring(0, 1);
if (isFirstInGroup(position)) {
//绘制分割线的颜色
c.drawRect(view.getLeft(), view.getTop() - spacing, view.getRight(), view.getTop(), mPaintSpacing);
c.drawText(content, 0, view.getTop() - fontMetrics.descent, mPaintText);
}
}
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
//绘制上面粘性的布局!
View view = parent.getChildAt(0);
int position = parent.getChildAdapterPosition(view);
String content = onGetTextListener.getText(position).substring(0,1);
if (view.getBottom() <= spacing && isFirstInGroup(position + 1)) {
//如果第一个view展示的bottom小于spacing并且下一个item内容的首字母也是第一次出现,则用第一个view的bottom高度进行绘制,
c.drawRect(0, 0, view.getRight(), view.getBottom(), mPaintSpacing);
c.drawText(content, 0, view.getBottom() - fontMetrics.descent, mPaintText);
} else {
c.drawRect(0, 0, view.getRight(), spacing, mPaintSpacing);
c.drawText(content, 0, spacing - fontMetrics.descent, mPaintText);
}
}
//回调接口,通过该回调获取item的昵称
public interface OnGetTextListener {
String getText(int position);
}
//item的内容首字母是否是第一次出现
private boolean isFirstInGroup(int position) {
if (position == 0) return true;
String lastName = onGetTextListener.getText(position - 1);
String currentName = onGetTextListener.getText(position);
if (lastName.substring(0, 1).equals(currentName.substring(0, 1))) {
return false;
} else {
return true;
}
}
}
entity实例
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User(String name) {
this.name = name;
}
}
数据源
private void initData() {
arrayList.add(new User("a"));
arrayList.add(new User("ab"));
arrayList.add(new User("abb"));
arrayList.add(new User("abbb"));
arrayList.add(new User("abbbb"));
arrayList.add(new User("abbbb"));
arrayList.add(new User("b"));
arrayList.add(new User("bc"));
arrayList.add(new User("bcc"));
arrayList.add(new User("bccc"));
arrayList.add(new User("bcccc"));
arrayList.add(new User("bccccc"));
arrayList.add(new User("c"));
arrayList.add(new User("cd"));
arrayList.add(new User("cdd"));
arrayList.add(new User("cddd"));
arrayList.add(new User("cdddd"));
arrayList.add(new User("cddddd"));
arrayList.add(new User("d"));
arrayList.add(new User("de"));
arrayList.add(new User("dee"));
arrayList.add(new User("deee"));
arrayList.add(new User("deeee"));
arrayList.add(new User("deeeee"));
arrayList.add(new User("e"));
arrayList.add(new User("ef"));
arrayList.add(new User("eff"));
arrayList.add(new User("efff"));
arrayList.add(new User("effff"));
arrayList.add(new User("efffff"));
}
public class MountingRyActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private ArrayList<User> arrayList = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mounting_rv);
initData();
initView();
}
private void initView() {
mRecyclerView = findViewById(R.id.rv);
LinearMountingItemDecoration linearMountingItemDecoration = new LinearMountingItemDecoration(Utils.dp2px(30));
linearMountingItemDecoration.setOnGetTextListener(new LinearMountingItemDecoration.OnGetTextListener() {
@Override
public String getText(int position) {
return arrayList.get(position).getName();
}
});
mRecyclerView.addItemDecoration(linearMountingItemDecoration);
MyAdapter myAdapter = new MyAdapter(arrayList, this);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(myAdapter);
}
private void initData() {
arrayList.add(new User("a"));
arrayList.add(new User("ab"));
arrayList.add(new User("abb"));
arrayList.add(new User("abbb"));
arrayList.add(new User("abbbb"));
arrayList.add(new User("abbbb"));
arrayList.add(new User("b"));
arrayList.add(new User("bc"));
arrayList.add(new User("bcc"));
arrayList.add(new User("bccc"));
arrayList.add(new User("bcccc"));
arrayList.add(new User("bccccc"));
arrayList.add(new User("c"));
arrayList.add(new User("cd"));
arrayList.add(new User("cdd"));
arrayList.add(new User("cddd"));
arrayList.add(new User("cdddd"));
arrayList.add(new User("cddddd"));
arrayList.add(new User("d"));
arrayList.add(new User("de"));
arrayList.add(new User("dee"));
arrayList.add(new User("deee"));
arrayList.add(new User("deeee"));
arrayList.add(new User("deeeee"));
arrayList.add(new User("e"));
arrayList.add(new User("ef"));
arrayList.add(new User("eff"));
arrayList.add(new User("efff"));
arrayList.add(new User("effff"));
arrayList.add(new User("efffff"));
}
private class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private ArrayList<User> arrayList; //数据源
private Context mContext; //上下文
public MyAdapter(ArrayList<User> arrayList, Context mContext) {
this.arrayList = arrayList;
this.mContext = mContext;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.from(mContext).inflate(R.layout.item_rv_mounting, viewGroup, false);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder myViewHolder, int i) {
myViewHolder.mTvName.setText(arrayList.get(i).getName());
}
@Override
public int getItemCount() {
return arrayList.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView mTvName;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
mTvName = itemView.findViewById(R.id.tv_name);
}
}
}
}
大体思路就是通过绘制分割线,及控制最上面view绘制的时机就可以做到这个效果了。
IMG_2872.JPG IMG_2873.JPG IMG_2874.JPG IMG_2875.JPG会以这个形式粘性展示!
网友评论