最新公告
  • 欢迎您光临AA分享网,一个高级程序员的学习、分享的分享平台!立即加入我们
  • 【Android】RecyclerView:打造悬浮效果


    悬浮效果

    先看个效果
    效果
    这是一个City列表,每个City都有所属的Province,需要在滑动的时候,将对应的Province悬浮在顶部。悬浮顶部的Province需要根据列表的滑动而适当改变位置,实现“顶上去”的效果。

    实现思路:

    • 利用RecyclerView.ItemDecoration绘制Province(就像绘制分割线一样)
    • 同一组的City,只绘制一个Province
    • 计算偏移,将当前Province固定在顶部
    • 根据列表滑动,实现偏移效果

    ItemDecoration

    既然是利用RecyclerView.ItemDecoration实现的悬浮效果,那么有必要了解下它。
    ItemDecoration字面意思:Item的装饰。是的!是装饰!不只是画分割线。
    其实ItemDecoration的功能非常强大,而我们平时只是用它来实现分割线的效果(至少我是这样)。因此,可能很多同学认为ItemDecoration就是用来绘制分割线的。其实不然,ItemDecoration的功能远不止是分割线的绘制。

    先看下RecyclerView.ItemDecoration的源码(部分):

    public static abstract class ItemDecoration {
        ...
        public void onDraw(Canvas c, RecyclerView parent, State state) {
            onDraw(c, parent);
        }
        public void onDrawOver(Canvas c, RecyclerView parent, State state) {
            onDrawOver(c, parent);
        }
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
            getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                    parent);
        }
    }

    里面是我们常用的三个方法:

    • getItemOffsets:通过Rect为每个Item设置偏移,用于绘制Decoration
    • onDraw:通过该方法,在Canvas上绘制内容,在绘制Item之前调用。(如果没有通过getItemOffsets设置偏移的话,Item的内容会将其覆盖)
    • onDrawOver:通过该方法,在Canvas上绘制内容,在Item之后调用。(画的内容会覆盖在item的上层)

    RecyclerView 的背景、onDraw绘制的内容、ItemonDrawOver绘制的内容,各层级关系如下:
    层级关系

    绘制分割线

    先看看一般的分割线绘制。

    • 定义分割线高度

      private int mHeight = 5; //分割线高度
    • 通过getItemOffsets预留空间

      @Override
      public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
      super.getItemOffsets(outRect, view, parent, state);
      int position = parent.getChildAdapterPosition(view);
      if (position != 0) {
          //第一个item预留空间
          outRect.top = mHeight;
      }
      }
    • 然后在onDraw中绘制
      @Override
      public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
      super.onDraw(c, parent, state);
      final int left = parent.getLeft();
      final int right = parent.getRight();
      final int childCount = parent.getChildCount();
      for (int i = 0; i < childCount; i++) {
          final View childView = parent.getChildAt(i);
          final int bottom = childView.getTop();
          final int top = bottom - mHeight;
          c.drawRect(left, top, right, bottom, mPaint);
      }
      }

      获取当前RecyclerViewItem数量,遍历每个Item。在对应的位置绘制一个高度为mHeight的矩形 ,从而实现分割线的效果。
      效果
      (详情代码见底部链接)

    打造悬浮效果

    这是一个城市列表,根据省份分组,相同的城市只会显示一个省份。滚动城市列表时,省份会悬浮在顶部。效果如下:
    效果1

    实现
    由于需要悬浮效果,所以需要在onDrawOver中绘制分组。

    • 定义一个interface,根据position通过接口方法getGroupName获取当前省名(由Activity实现)

      public interface GroupListener {
      String getGroupName(int position);
      }
    • 创建StickyDecoration继承RecyclerView.ItemDecoration
      • 定义isFirstInGroup方法。根据前一个省份,判断当前是否为新的省份
        //判断是不是组中的第一个位置
        //根据前一个组名,判断当前是否为新的组
        private boolean isFirstInGroup(int pos) {
        if (pos == 0) {
        return true;
        } else {
        String prevGroupId = getGroupName(pos - 1);
        String groupId = getGroupName(pos);
        return !TextUtils.equals(prevGroupId, groupId);
        }
        }

        通过position,对比上一个省份名称,判断当前省是否为第一个

      • 重写getItemOffsets方法,为悬浮栏预留空间
        @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 = getGroupName(pos);
        if (groupId == null) return;
        //只有是同一组的第一个才显示悬浮栏
        if (pos == 0 || isFirstInGroup(pos)) {
        outRect.top = mGroupHeight;
        }
        }

        只有第一个Item或者新的省份才为悬浮栏预留空间

      • 重写onDrawOver方法(重点)
        @Override
        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        final int itemCount = state.getItemCount();
        final int childCount = parent.getChildCount();
        final int left = parent.getLeft() + parent.getPaddingLeft();
        final int right = parent.getRight() - parent.getPaddingRight();
        String preGroupName;      //标记上一个item对应的Group
        String currentGroupName = null;       //当前item对应的Group
        for (int i = 0; i < childCount; i++) {
        View view = parent.getChildAt(i);
        int position = parent.getChildAdapterPosition(view);
        preGroupName = currentGroupName;
        currentGroupName = getGroupName(position);
        if (currentGroupName == null || TextUtils.equals(currentGroupName, preGroupName))
            continue;
        int viewBottom = view.getBottom();
        float top = Math.max(mGroupHeight, view.getTop());//top 决定当前顶部第一个悬浮Group的位置
        if (position + 1 < itemCount) {
            //获取下个GroupName
            String nextGroupName = getGroupName(position + 1);
            //下一组的第一个View接近头部
            if (!currentGroupName.equals(nextGroupName) && viewBottom < top) {
                top = viewBottom;
            }
        }
        //根据top绘制group
        c.drawRect(left, top - mGroupHeight, right, top, mGroutPaint);
        Paint.FontMetrics fm = mTextPaint.getFontMetrics();
        //文字竖直居中显示
        float baseLine = top - (mGroupHeight - (fm.bottom - fm.top)) / 2 - fm.bottom;
        c.drawText(currentGroupName, left + mLeftMargin, baseLine, mTextPaint);
        }
        }

        通过变量preGroupIdcurrentGroupId来保存当前分组名和上一个分组名。当前Item与上一个Item为同一个分组时,跳过该Item的绘制。
        其中代码:

        float top = Math.max(mGroupHeight, view.getTop());

        根据当前Item的位置确定绘制分组的位置。top将在mGroupHeightview.getTop()中取最大值,也就是说top将不会小于mGroupHeight,这样就能实现吸顶效果。
        其中代码:

        if (!currentGroupName.equals(nextGroupName) && viewBottom < top) {
            top = viewBottom;
        }

        当下个分组的顶部(当前Item的底部viewBottom可近似认为下个Item的顶部)距离RecyclerView顶部小于top时,偏移当前分组位置。实现下一组上滑时候,当前分组上移;上一组下滑的时候,当前分组下移。
        最后计算baseLine,并绘制背景和文字。

    到目前为止,一个带有悬浮功能的列表就实现了。
    (详细代码见底部链接)
    --

    进阶

    当我们利用ItemDecoration实现文字的悬浮的时候,是不是还可以搞点事情~ ~我有个大胆的想法
    只有文字的悬浮怎么行!我还希望可以再来个icon?再来几个TextView?来个自定义布局?那就来个自定义布局吧。

    实现

    实现的原理跟上面一样,由于需要自定义布局,所以需要在接口中添加一个获取View的方法。

    • 定义PowerGroupListener
      public interface PowerGroupListener {
      String getGroupName(int position);
      View getGroupView(int position);
      }

      相比之前,多了个getGroupView方法,用来获取View

    • onDrawOver中绘制
      @Override
      public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
      ...
      for (int i = 0; i < childCount; i++) {
          ...
          //根据position获取View
          View groupView = mGroupListener.getGroupView(position);
          if (groupView == null) return;
          ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mGroupHeight);
      groupView.setLayoutParams(layoutParams);
          groupView.setDrawingCacheEnabled(true);
          groupView.measure(
                  View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                  View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
          //指定高度、宽度的groupView
          groupView.layout(0, 0, right, mGroupHeight);
          groupView.buildDrawingCache();
          Bitmap bitmap = groupView.getDrawingCache();
          c.drawBitmap(bitmap, left, top - mGroupHeight, null);
      }
      }

      在原来的基础上做了点修改,通过接口的getGroupView方法获取需要绘制的分组View,将得到的View绘制到指定位置。

    效果:
    效果2
    (详细代码见底部链接)

    源码链接

    已封装成库,欢迎来提Issues

    repositories {
        jcenter()// If not already there
    }
    dependencies {
        compile 'com.gavin.com.library:stickyDecoration:1.0.2'
    }

    详细用法级源码请看Github

    参考

     Android-使用RecyclerView的ItemDecoration 实现炫酷的 吸顶效果
    RecycleView的学习(一篇不错的博客,没有投稿,所以看得人不多)
     Android RecyclerView 使用完全解析 体验艺术般的控件

    AA分享网一个高级程序员的学习、分享的IT资源分享平台
    AA分享网-企业网站源码-PHP源码-网站模板-视频教程-IT技术教程 » 【Android】RecyclerView:打造悬浮效果
    • 262会员总数(位)
    • 5946资源总数(个)
    • 4本周发布(个)
    • 0 今日发布(个)
    • 570稳定运行(天)

    提供最优质的资源集合

    立即查看 了解详情