在上一篇文章RecyclerView自定义ItemDecoration绘制分割线,简单的实现了通用的分割线,同样,我们可以利用ItemDecoration来实现类似通讯录的分组粘性效果。
一、实现类似通讯录的分组粘性布局,分组布局中只有文字,直接上代码。
/**
* 类似通讯录的分组粘性布局,分组布局中只有文字
*/
public class SectionDecoration extends RecyclerView.ItemDecoration {
private DecorationCallback callback;
private TextPaint textPaint;
private Paint paint;
private float stickyHeight;
private Paint.FontMetrics fontMetrics;
public SectionDecoration(Context context, DecorationCallback callback){
this.callback = callback;
paint = new Paint();
paint.setColor(context.getResources().getColor(R.color.colorAccent));
stickyHeight = (int) context.getResources().getDimension(R.dimen.setion_height);
textPaint = new TextPaint();
textPaint.setAntiAlias(true);
textPaint.setTextSize(80);
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
textPaint.getFontMetrics(fontMetrics);
fontMetrics = new Paint.FontMetrics();
textPaint.setTextAlign(Paint.Align.LEFT);
textPaint.setColor(Color.WHITE);
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (!isLinearAndVertical(parent)){
return;
}
int position = parent.getChildAdapterPosition(view);
if (isFirstInGroup(position)){
outRect.top = (int) stickyHeight;
}
}
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
if (!isLinearAndVertical(parent)){
return;
}
float left = parent.getPaddingLeft();
float right = parent.getWidth() - parent.getPaddingRight();
int itemCount = parent.getChildCount();
for(int i=0;i < itemCount;i++){
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
if (isFirstInGroup(position)){
float top = view.getTop() - stickyHeight;
float bottom = view.getTop();
c.drawRect(left,top,right,bottom,paint);
c.drawText(callback.getGroupFirstChar(position).toUpperCase(),left,bottom,textPaint);
}
}
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
if (!isLinearAndVertical(parent)){
return;
}
int itemCount = state.getItemCount();//recycle列表的总item数
int childCount = parent.getChildCount();//recycle控件包好的第一层控件数
float left = parent.getPaddingLeft();
float right = parent.getWidth() - parent.getPaddingRight();
long preGroupId, groupId = -1;
for (int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
preGroupId = groupId;
groupId = callback.getGroupId(position);
if (groupId < 0 || groupId == preGroupId) continue;//上一个id和当前id相同,跳出循环
float viewBottom = view.getBottom();
float textY = Math.max(stickyHeight, view.getTop());
if (position + 1 < itemCount) {
long nextGroupId = callback.getGroupId(position + 1);
if (nextGroupId != groupId && viewBottom < textY ) {//下一个和当前不一样,移动当前
textY = viewBottom;//组内最后一个view进入了header
}
}
c.drawRect(left, textY - stickyHeight, right, textY, paint);
c.drawText(callback.getGroupFirstChar(position).toUpperCase(), left, textY, textPaint);
}
}
/**
* 判断LayoutManager类型,目前GroupItemDecoration仅支持LinearLayoutManager.VERTICAL
*/
public boolean isLinearAndVertical(RecyclerView parent){
RecyclerView.LayoutManager manager = parent.getLayoutManager();
if (!(manager instanceof LinearLayoutManager)){
return false;
}else {
if (((LinearLayoutManager)manager).getOrientation() != LinearLayoutManager.VERTICAL){
return false;
}
}
return true;
}
/**
* 是否为每个分组的第一个item
* @param pos
* @return
*/
private boolean isFirstInGroup(int pos){
if (callback.getGroupId(pos) < 0)return false;
if (pos == 0){
return true;
}else {
long preGroupId = callback.getGroupId(pos - 1);
long groupId = callback.getGroupId(pos);
return groupId != preGroupId;
}
}
public interface DecorationCallback {
long getGroupId(int position);//得到]组的id
String getGroupFirstChar(int position);//得到通讯录的首字母
}
}
二、自定义分组粘性布局,布局中的图片暂时只支持本地图片
1.自定义布局需要计算分组控件的高度
if (view.getLayoutParams() == null) {
view.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
int childWidth = ViewGroup.getChildMeasureSpec(widthSpec,
parent.getPaddingLeft() + parent.getPaddingRight(), view.getLayoutParams().width);
int childHeight;
if(view.getLayoutParams().height > 0){
childHeight = View.MeasureSpec.makeMeasureSpec(view.getLayoutParams().height, View.MeasureSpec.EXACTLY);
} else {
childHeight = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);//未指定
}
view.measure(childWidth, childHeight);
view.layout(0,0,view.getMeasuredWidth(),view.getMeasuredHeight());
groupViewHeight = view.getMeasuredHeight();
2.DecorationCallback接口中的方法,改成
public interface DecorationCallback {
long getGroupId(int position);//得到分组的id
void buildGroupView(View groupView, GroupItem groupItem);//构建GroupView,设置组控件的值
}
3.在getItemOffsets预留相应的高度空间
int position = parent.getChildAdapterPosition(view);
if(isFirstInGroup(position) && groupList.get(position) != null){
measureView(groupView,parent);//绘制View需要先测量View的大小及相应的位置
//为其预留相应的高度空间
outRect.top = (int) groupViewHeight;
}
4.在onDraw中,绘制每个组控件,关键代码如下
int childCount = parent.getChildCount();
for (int i=0;i<childCount;i++){
View childView = parent.getChildAt(i);
float left = childView.getLeft();
float top = childView.getTop();
int position = parent.getChildAdapterPosition(childView);
if (groupList.get(position) != null){
c.save();
c.translate(left,top - groupViewHeight);//将画布起点移动到之前预留空间的左上角
drawGroupView(c,parent,position);
}
}
5.在onDrawOver中绘悬浮在最上面的头布局,核心代码
int itemCount = state.getItemCount();//recycle列表的总item数
int childCount = parent.getChildCount();//recycle控件包好的第一层控件数
float left = parent.getPaddingLeft();
long preGroupId, groupId = -1;
for (int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
preGroupId = groupId;
groupId = callback.getGroupId(position);
if (groupId < 0 || groupId == preGroupId) continue;//上一个id和当前id相同,跳出循环
float viewBottom = view.getBottom();
float textY = Math.max(groupViewHeight, view.getTop());
if (position + 1 < itemCount) {
long nextGroupId = callback.getGroupId(position + 1);
if (nextGroupId != groupId && viewBottom < textY ) {//下一个和当前不一样,移动当前
textY = viewBottom;//组内最后一个view进入了header
}
}
c.save();
c.translate(left,textY - groupViewHeight);//将画布起点移动到之前预留空间的左上角
drawGroupView(c,parent,position);
private void drawGroupView(Canvas c, RecyclerView parent, int position) {
callback.buildGroupView(groupView,groupList.get(position));
measureView(groupView,parent);//因为内部控件设置了数据,所以需要重新测量View
groupView.draw(c);
c.restore();//重新绘制
}
效果图晚点再放
写本篇博客的目的在于加深学习记忆,写的不明白的地方请参考RecyclerView之ItemDecoration由浅入深