RecyclerView学习(四)----ItemDecoration实现的城市导航列表(下)

之前用RecyclerView实现了写过一篇城市导航列表:

动手写一个城市导航列表

关于自定义的导航条,滑动监听,汉字转拼音等零碎知识,大家可以查看我之前那篇博客。

今天主要说的是悬停列表的实现,之前的实现方式是每一个RecyclerView的item的布局里面都包含一个头部布局,然后判断当前item和上一个item的头部布局里的索引字母是否相同,来决定是否展示item的头部布局。这种实现方式显得布局冗余,效率降低,而且不够优雅。今天这里我用ItemDecoration来实现城市列表的头部悬停。

ItemDecoration是用来修饰RecyclerView里的Item,ItemDecoration类主要有三个方法:

public void onDraw(Canvas c, RecyclerView parent, State state)
public void onDrawOver(Canvas c, RecyclerView parent, State state)
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)

这三个方法作用依次是:
onDraw():可以实现类似绘制背景的效果,item的内容在上面

onDrawOver():可以绘制在内容的上面,覆盖item的内容

getItemOffsets():可以实现类似padding的效果

onDraw()的绘制会先于子ItemView的绘制,如果你在onDraw()方法中绘制的东西在子ItemView边界内,就会被ItemView盖住,所以我们一般先调用getItemOffsets()创造空间。而onDrawOver()会在子ItemView绘制之后再绘制,绘制的内容会覆盖在子ItemView上。

执行顺序是先执行ItemDecoration的onDraw()、再执行子ItemView的onDraw()、再执行ItemDecoration的onDrawOver()。

下面我们通过改造这个例子来分析这三个方法:

1.getItemOffsets()

我移除了之前项目里用到的悬停,侧边导航栏与滑动监听还保留,看下现在的效果:

RecyclerView学习(四)----ItemDecoration实现的城市导航列表(下)

ok,现在我们来自定义一个ItemDecoration:

public class StickyDecoration extends RecyclerView.ItemDecoration {
    private int topHeight;

    public StickyDecoration(Context context) {
        Resources res = context.getResources();
        topHeight = res.getDimensionPixelSize(R.dimen.top);
    }

    @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 (isFirstInGroup(position)) {
            outRect.top = topHeight;
        } else {
            outRect.top = 0;
        }

    }


    private boolean isFirstInGroup(int position) {
        boolean isFirst;
        if (position == 0) {
            isFirst = true;
        } else {
            if (CityActivity.cityList.get(position).getFirstPinYin().
                    equals(CityActivity.cityList.get(position - 1).getFirstPinYin())) {
                isFirst = false;
            } else {
                isFirst = true;
            }
        }
        return isFirst;
    }
}

继承自RecyclerView.ItemDecoration,然后重写构造方法,并且重写getItemOffsets()方法。进行判断,该item如果是第一次出现该字母,就给这个item的上方绘制一个40dp的高度,否则就不处理。Activity中使用:

  recyclerView.addItemDecoration(new StickyDecoration(getApplicationContext()));

最后我们看一下效果:

RecyclerView学习(四)----ItemDecoration实现的城市导航列表(下)

可以看到,在每一个字母首次出现的item上,都多了一个40dp的空间,这里用来放我们的索引字母。

2.onDraw()

onDraw()可以实现类似绘制背景的效果,item的内容在上面,我们通过getItemOffsets()创造了空间,然后用onDraw()方法绘制字母索引:


    private TextPaint textPaint;
    private Paint paint;
    private int topHeight;

    public StickyDecoration(Context context) {
        Resources res = context.getResources();

        paint = new Paint();
        paint.setColor(res.getColor(R.color.colorAccent));
        textPaint = new TextPaint();
        textPaint.setAntiAlias(true);
        textPaint.setTextSize(60);
        textPaint.setColor(Color.WHITE);
        topHeight = res.getDimensionPixelSize(R.dimen.top);
    }

    @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 (isFirstInGroup(position)) {
            outRect.top = topHeight;
        } 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 textLine = CityActivity.cityList.get(position).getFirstPinYin();
            if (position == 0 || isFirstInGroup(position)) {
                float top = view.getTop() - topHeight;
                float bottom = view.getTop();
                c.drawRect(left, top, right, bottom, paint);//绘制红色矩形
                c.drawText(textLine, left + 30, bottom - 30, textPaint);//绘制文本
            }
        }
    }

这里的onDraw()方法与自定义View的onDraw()一样,只不过这里绘制的对象是RecyclerView子item的view。我们看下最后实现的效果:

RecyclerView学习(四)----ItemDecoration实现的城市导航列表(下)

OK,在首次出现该字母的item上都出现了字母索引,接下来就是悬停的实现了。

3.onDrawOver()

onDrawOver()方法会绘制在内容的上面,覆盖item的内容,所以我们拿来做悬停很合适。

    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        int position = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        c.drawRect(left, 0, right, topHeight, paint);//绘制红色矩形
        String text = CityActivity.cityList.get(position).getFirstPinYin();
        c.drawText(text, 30, topHeight - 30, textPaint);//绘制文本
    }

实现的代码其实很简单,拿到RecyclerView可见区域第一个item的position,得到大写字母。依次绘制红色背景与文字即可。最后实现的效果如下:

RecyclerView学习(四)----ItemDecoration实现的城市导航列表(下)

OK,和之前实现的效果还是没什么区别的。

项目完整源码已经上传到我的github上,源码地址:

https://github.com/18722527635/MyRecyclerView

欢迎Star,fork,提issues,一起进步!

上一篇:ASP.NET-GridView分页排序显示


下一篇:[转载]在C#用HttpWebRequest中发送GET/HTTP/HTTPS请求