recyclview-系列效果(分组,吸顶,分割线)

recycleView

基本的使用与说明不再赘述,本文主要是通过ItemDecration 实现 分割线,分组 吸顶等效果;学习前你需要知道getTop getBottom 都是指的什么,如果还存在疑惑,可以看看这篇文章
[view坐标系详解](https://blog.youkuaiyun.com/u013872857/article/details/53750682)

主要的方法

要实现上述功能,需要通过I自定义itemDecration ;首先你需要了解这些方法:

  1. getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state);
    –item的偏移,通过该方法,可以设置item之间预留位置,一般设置分割线需要先在该方法下设置好偏移量;

  2. onDraw(Canvas c, RecyclerView parent, RecyclerView.State state);
    –绘制前调用的,参数有canva 所以可以发现可以绘制一些内容,上个方法只能预留出位置,展示的颜色只能是背景色,想要绘制更加丰富的内容需要重写这个方法;需要注意的是** 该方法绘制的内容,如果计算出现问题,是会被itemView覆盖的**

  3. onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state);
    –绘制后调用,会覆盖itemView,一般用于可以用来绘制分组和吸顶效果

分割线

   @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int pos = parent.getChildAdapterPosition(view);
        //只要不是第一组就需要设置分割线
        if (pos != 0) {
            outRect.top = 1;
        } 
    }
  • 首先需要 在该方法里面设置好偏移量, outRect.top 表示从第二个item开始距离上衣个item预留出1的高度;其实就这样也可以满足要求,只是分割线的颜色就是背景色,想要定制这条线,可以使用下例;
public class SectionDecoration<T extends IndexIml> extends RecyclerView.ItemDecoration {
    private static final String TAG = "SectionDecoration";
    private List<T> dataList;
    private DecorationCallback callback;
    private Paint paint;
    private int dvidedHeight;

    /**
     * @param dataList           数据源 需继承 IndexIml
     * @param context
     * @param decorationCallback
     * @param resId              悬浮条的背景色值
     */
    public SectionDecoration(List<T> dataList, Context context, DecorationCallback decorationCallback, int resId) {
        Resources res = context.getResources();
        this.dataList = dataList;
        this.callback = decorationCallback;
        //设置悬浮栏的画笔---paint
        paint = new Paint();
        try {
            paint.setColor(res.getColor(resId));
        } catch (Resources.NotFoundException e) {
            e.printStackTrace();
        }
    }

  @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int pos = parent.getChildAdapterPosition(view);
        //只要不是第一组就需要设置分割线
        if (pos != 0) {
            outRect.top = 1;
            dvidedHeight=1;
        } 
    }

  @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View view = parent.getChildAt(i);
            int position = parent.getChildAdapterPosition(view);
          if(position ==0){
          continue;
          }
          float top=view.getTop()-dvidedHeight;
          float bottom=view.getTop();
           c.drawRect(left ,top,right ,bottom,paint);
        }
    }
    }

–通过onDraw 在预留出的位置绘画出分割线,也可以拓展出其他,比如在左侧预留出一个位置,来绘画出一张图片,都可以实现(本文不考虑layout实现)

实现分组

如果考虑到拓展,数据是需要统一实现一个接口,来获取一个分组名,接口稍后提供;

public class SectionDecoration<T extends IndexIml> extends RecyclerView.ItemDecoration {
    private static final String TAG = "SectionDecoration";
    private List<T> dataList;
    private DecorationCallback callback;
    private TextPaint textPaint;
    private Paint paint;
    private int topGap;
    private int alignBottom;
    private Paint.FontMetrics fontMetrics;
    /**
     * @param dataList           数据源 需继承 IndexIml
     * @param context
     * @param decorationCallback
     * @param resId              悬浮条的背景色值
     */
    public SectionDecoration(List<T> dataList, Context context, DecorationCallback decorationCallback, int resId) {
        Resources res = context.getResources();
        this.dataList = dataList;
        this.callback = decorationCallback;
        //设置悬浮栏的画笔---paint
        paint = new Paint();
        try {
            paint.setColor(res.getColor(resId));
        } catch (Resources.NotFoundException e) {
            e.printStackTrace();
        }

        //设置悬浮栏中文本的画笔
        textPaint = new TextPaint();
        textPaint.setAntiAlias(true);
        textPaint.setTextSize(SaDensityUtils.dp2px(context, 20));
        Typeface font = Typeface.create(Typeface.DEFAULT, Typeface.BOLD);
        textPaint.setTypeface(font);
        textPaint.setColor(context.getResources().getColor(R.color.black_all));
        textPaint.setTextAlign(Paint.Align.LEFT);
        fontMetrics = new Paint.FontMetrics();
        //决定悬浮栏的高度等
        topGap = res.getDimensionPixelSize(R.dimen.sectioned_top);
        //决定文本的显示位置等
        alignBottom = res.getDimensionPixelSize(R.dimen.sectioned_alignBottom);
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int pos = parent.getChildAdapterPosition(view);
        String groupId = callback.getGroupId(pos);
        //只有是同一组的第一个才显示悬浮栏
        if (pos == 0 || isFirstInGroup(pos)) {
            outRect.top = topGap;
        } else {
            outRect.top = 0;
        }
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        //开启这段代码可以绘制分组-单纯绘制分组
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View view = parent.getChildAt(i);
            int position = parent.getChildAdapterPosition(view);
            String groupId = callback.getGroupId(position);
            String textLine = callback.getGroupFirstLine(position);
            if (textLine == "") {
                float top = view.getTop();
                float bottom = view.getTop();
                c.drawRect(left, top, right, bottom, paint);
                return;
            } else {
            //第一组数据或者分组的第一条数据
                if (position == 0 || isFirstInGroup(position)) {
                    float top = view.getTop() - topGap;
                    float bottom = view.getTop();
                    //绘制分组条
                    c.drawRect(left, top, right, bottom, paint);
                    //绘制文本
                    c.drawText(textLine, left + 2 * alignBottom, bottom - alignBottom, textPaint);
                }
            }
        }
    }
/**
     * 判断是不是组中的第一个位置
     *
     * @param pos
     * @return
     */
    private boolean isFirstInGroup(int pos) {
        if (pos == 0) {
            return true;
        } else {
            // 因为是根据 字符串内容的相同与否 来判断是不是同意组的,所以此处的标记id 要是String类型
            // 如果你只是做联系人列表,悬浮框里显示的只是一个字母,则标记id直接用 int 类型就行了
            String prevGroupId = callback.getGroupId(pos - 1);
            String groupId = callback.getGroupId(pos);
            //判断前一个字符串 与 当前字符串 是否相同
            if (prevGroupId.equals(groupId)) {
                return false;
            } else {
                return true;
            }
        }
    }

–以上代码就可以实现分组处理了,如果要实现吸顶呢?前文说了,ondraw()方法是绘画前的操作,而吸顶操作是需要覆盖在recyclView上面的,如果继续使用该方法,达不到我们的预期,只会被itemView覆盖住

吸顶处理

这里就用到了 onDrawOver()方法,这个方法调用是在绘制完itemView之后的,因此可以满足我们的需求;
当然前提也是一样的,首先需要在getItemOffsets()方法里面预留下我们的分组条位置;代码一样就不再贴出;主要是接下来的代码:

 @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        //获取所有的item数量
        int itemCount = state.getItemCount();
        //当前页面展示的item数量
        int childCount = parent.getChildCount();
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        String preGroupId = "";
        //数据源做了处理,如果是-1表示没有分组数据,与此例无关
        String 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.equals("-1") || groupId.equals(preGroupId)) {
                continue;
            }

            String textLine = callback.getGroupFirstLine(position);
            if (TextUtils.isEmpty(textLine)) {
                continue;
            }
            //接下来的是重点
            int viewBottom = view.getBottom();
            float textY = Math.max(topGap, view.getTop());
            //下一个和当前不一样移动当前
            if (position + 1 < itemCount) {
                String nextGroupId = callback.getGroupId(position + 1);
                //组内最后一个view进入了header
                if (!TextUtils.equals(nextGroupId, groupId) && viewBottom < textY) {
                    textY = viewBottom;
                }
            }
            //textY - topGap决定了悬浮栏绘制的高度和位置
            c.drawRect(left, textY - topGap, right, textY, paint);
            //left+2*alignBottom 决定了文本往右偏移的多少(加-->向左移)
            //textY-alignBottom  决定了文本往上偏移的多少  (减-->向上移)
            c.drawText(textLine, left + 2 * alignBottom, textY - alignBottom, textPaint);
        }
    }

– 要达到的预期效果是,

  1. 屏幕的顶端,无论是否是分组的第一个,都要展示出分组头
  2. 当新的分组滑动到悬浮窗下部时,需要将上一个分组顶起来,而此时悬浮窗需要向上面移动,滑出屏幕,那么滑出屏幕的开始是什么时候呢,是悬浮窗的底部与itemView的底部重合的时候开始的,仔细想想对不对?
    这个时候有个特点就是 view.getBottom 的值会开始小于topGap,而此时的 topGap会开始大于view.getTop(),因为view已经一部分滑出屏幕了;
    所以有 Math.max(topGap, view.getTop()),正常情况下,值为view.getTop(),只有在顶端滑动出界面的时候才会是topGap;
    这是那个数据接口
public interface IndexIml {
    /**
     * 获取分组号
     *
     * @return
     */
    String getIndexName();
}

– 以上就是这些。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值