RecyclerView没有像之前ListView提供divider属性,而是提供了方法

recyclerView.addItemDecoration()

其中ItemDecoration需要我们自己去定制重写,一开始可能有人会觉得麻烦不好用,最后你会发现这种可插拔设计不仅好用,而且功能强大。

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)

官方源码虽然都写的很清楚,但还不少小伙伴不知道怎么理解,怎么用或用哪个方法,下面我画个简单的图来帮你们理解一下。

图画的丑请见谅,首先我们假设绿色区域代表的是我们的内容,红色区域代表我们自己绘制的装饰,可以看到:

  • 图1:代表了getItemOffsets(),可以实现类似padding的效果

  • 图2:代表了onDraw(),可以实现类似绘制背景的效果,内容在上面

  • 图3:代表了onDrawOver(),可以绘制在内容的上面,覆盖内容

注意上面是我个人从应用角度的看法,事实上实现上面的效果可能三个方法每个方法都可以实现。只不过这种方法更好理解。

 
下面是我们没有添加任何ItemDecoration的界面

主页布局界面很简单,背景设成灰色

<?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@color/gray">//灰色背景    <android.support.design.widget.AppBarLayout        android:layout_width="match_parent"        android:layout_height="wrap_content">        <android.support.v7.widget.Toolbar            android:id="@+id/toolbar"            android:layout_width="match_parent"            android:layout_height="?attr/actionBarSize"            android:background="?attr/colorPrimary"            app:layout_scrollFlags="scroll|enterAlways"/>    </android.support.design.widget.AppBarLayout>    <android.support.v7.widget.RecyclerView        android:id="@+id/recycler_view"        android:layout_width="match_parent"        android:layout_height="match_parent"        app:layout_behavior="@string/appbar_scrolling_view_behavior"        /></android.support.design.widget.CoordinatorLayout>

ok 接下来,让我们来实现实际开发中常遇到的场景。

padding

从前面的图可以看到实现这个效果,需要重写getItemOffsets方法。

public class SimplePaddingDecoration extends RecyclerView.ItemDecoration {         private int dividerHeight;    public SimplePaddingDecoration(Context context) {             dividerHeight = context.getResources().getDimensionPixelSize(R.dimen.divider_height);    }    @Override    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {             super.getItemOffsets(outRect, view, parent, state);        outRect.bottom = dividerHeight;//类似加了一个bottom padding    }}

没错,就这么2行代码,然后添加到RecyclerView

recyclerView.addItemDecoration(new SimplePaddingDecoration(this));

实现效果:

分割线

分割线在app中是经常用到的,用ItemDecoration怎么实现呢,其实上面padding改成1dp就实现了分割线的效果,但是分割线的颜色只能是背景灰色,所以不能用这种方法。
 

要实现分割线效果需要 getItemOffsets()和 onDraw()2个方法,首先用 getItemOffsets给item下方空出一定高度的空间(例子中是1dp),然后用onDraw绘制这个空间

public class SimpleDividerDecoration extends RecyclerView.ItemDecoration {         private int dividerHeight;    private Paint dividerPaint;    public SimpleDividerDecoration(Context context) {             dividerPaint = new Paint();        dividerPaint.setColor(context.getResources().getColor(R.color.colorAccent));        dividerHeight = context.getResources().getDimensionPixelSize(R.dimen.divider_height);    }    @Override    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {             super.getItemOffsets(outRect, view, parent, state);        outRect.bottom = dividerHeight;    }    @Override    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {             int childCount = parent.getChildCount();        int left = parent.getPaddingLeft();        int right = parent.getWidth() - parent.getPaddingRight();        for (int i = 0; i < childCount - 1; i++) {                 View view = parent.getChildAt(i);            float top = view.getBottom();            float bottom = view.getBottom() + dividerHeight;            c.drawRect(left, top, right, bottom, dividerPaint);        }    }}

实现效果:

标签

现在很多电商app会给商品加上一个标签,比如“推荐”,“热卖”,“秒杀”等等,可以看到这些标签都是覆盖在内容之上的,这就可以用onDrawOver()来实现,我们这里简单实现一个有趣的标签

public class LeftAndRightTagDecoration extends RecyclerView.ItemDecoration {         private int tagWidth;    private Paint leftPaint;    private Paint rightPaint;    public LeftAndRightTagDecoration(Context context) {             leftPaint = new Paint();        leftPaint.setColor(context.getResources().getColor(R.color.colorAccent));        rightPaint = new Paint();        rightPaint.setColor(context.getResources().getColor(R.color.colorPrimary));        tagWidth = context.getResources().getDimensionPixelSize(R.dimen.tag_width);    }    @Override    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {             super.onDrawOver(c, parent, state);        int childCount = parent.getChildCount();        for (int i = 0; i < childCount; i++) {                 View child = parent.getChildAt(i);            int pos = parent.getChildAdapterPosition(child);            boolean isLeft = pos % 2 == 0;            if (isLeft) {                     float left = child.getLeft();                float right = left + tagWidth;                float top = child.getTop();                float bottom = child.getBottom();                c.drawRect(left, top, right, bottom, leftPaint);            } else {                     float right = child.getRight();                float left = right - tagWidth;                float top = child.getTop();                float bottom = child.getBottom();                c.drawRect(left, top, right, bottom, rightPaint);            }        }    }}

实现效果:

组合

不要忘记的是ItemDecoration是可以叠加的

 recyclerView.addItemDecoration(new LeftAndRightTagDecoration(this));recyclerView.addItemDecoration(new SimpleDividerDecoration(this));

我们把上面2个ItemDecoration同时添加到RecyclerView看下什么效果

是不是有种狂拽炫酷吊炸天的赶脚。。。

三个方法都用了一遍,你以为这就结束了?呵呵 并没有

section

这个是什么呢,先看下我们实现的效果

一看这个就很熟悉吧,手机上面的通讯录联系人,知乎日报都是这样效果,可以叫分组,也可以叫section分块 先不管它叫什么。
 

这个怎么实现呢? 其实和实现分割线是一样的道理 ,只是不是所有的item都需要分割线,只有同组的第一个需要。

 
我们首先定义一个接口给activity进行回调用来进行数据分组和获取首字母

public interface DecorationCallback {             long getGroupId(int position);        String getGroupFirstLine(int position);    }

然后再来看我们的ItemDecoration

public class SectionDecoration extends RecyclerView.ItemDecoration {         private static final String TAG = "SectionDecoration";    private DecorationCallback callback;    private TextPaint textPaint;    private Paint paint;    private int topGap;    private Paint.FontMetrics fontMetrics;    public SectionDecoration(Context context, DecorationCallback decorationCallback) {             Resources res = context.getResources();        this.callback = decorationCallback;        paint = new Paint();        paint.setColor(res.getColor(R.color.colorAccent));        textPaint = new TextPaint();        textPaint.setTypeface(Typeface.DEFAULT_BOLD);        textPaint.setAntiAlias(true);        textPaint.setTextSize(80);        textPaint.setColor(Color.BLACK);        textPaint.getFontMetrics(fontMetrics);        textPaint.setTextAlign(Paint.Align.LEFT);        fontMetrics = new Paint.FontMetrics();        topGap = res.getDimensionPixelSize(R.dimen.sectioned_top);//32dp    }    @Override    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {             super.getItemOffsets(outRect, view, parent, state);        int pos = parent.getChildAdapterPosition(view);        Log.i(TAG, "getItemOffsets:" + pos);        long groupId = callback.getGroupId(pos);        if (groupId < 0) return;        if (pos == 0 || isFirstInGroup(pos)) {     //同组的第一个才添加padding            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);            long groupId = callback.getGroupId(position);            if (groupId < 0) return;            String textLine = callback.getGroupFirstLine(position).toUpperCase();            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, bottom, textPaint);//绘制文本            }        }    }        private boolean isFirstInGroup(int pos) {             if (pos == 0) {                 return true;        } else {                 long prevGroupId = callback.getGroupId(pos - 1);            long groupId = callback.getGroupId(pos);            return prevGroupId != groupId;        }    }    public interface DecorationCallback {             long getGroupId(int position);        String getGroupFirstLine(int position);    }}

可以看到和divider实现一样,都是重写getItemOffsets()和onDraw()2个方法,不同的是根据数据做了处理。
 
在Activity中使用

 recyclerView.addItemDecoration(new SectionDecoration(this, new SectionDecoration.DecorationCallback() {                 @Override            public long getGroupId(int position) {                     return Character.toUpperCase(dataList.get(position).getName().charAt(0));            }            @Override            public String getGroupFirstLine(int position) {                     return dataList.get(position).getName().substring(0, 1).toUpperCase();            }        }));

干净舒服,不少github类似的库都是去adapter进行处理 侵入性太强 或许ItemDecoration是个更好的选择,可插拔,可替换。

到这里细心的人就会发现了,header不会动啊,我手机上的通讯录可是会随的滑动而变动呢,这个可以实现么?

StickyHeader

这个东西怎么叫我也不知道啊 粘性头部?英文也有叫 pinned section 取名字真是个麻烦事。
 
先看下我们简单实现的效果

首先一看到图,我们就应该想到header不动肯定是要绘制item内容之上的,需要重写onDrawOver()方法,其他地方和section实现一样。

public class PinnedSectionDecoration extends RecyclerView.ItemDecoration {         private static final String TAG = "PinnedSectionDecoration";    private DecorationCallback callback;    private TextPaint textPaint;    private Paint paint;    private int topGap;    private Paint.FontMetrics fontMetrics;    public PinnedSectionDecoration(Context context, DecorationCallback decorationCallback) {             Resources res = context.getResources();        this.callback = decorationCallback;        paint = new Paint();        paint.setColor(res.getColor(R.color.colorAccent));        textPaint = new TextPaint();        textPaint.setTypeface(Typeface.DEFAULT_BOLD);        textPaint.setAntiAlias(true);        textPaint.setTextSize(80);        textPaint.setColor(Color.BLACK);        textPaint.getFontMetrics(fontMetrics);        textPaint.setTextAlign(Paint.Align.LEFT);        fontMetrics = new Paint.FontMetrics();        topGap = res.getDimensionPixelSize(R.dimen.sectioned_top);    }    @Override    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {             super.getItemOffsets(outRect, view, parent, state);        int pos = parent.getChildAdapterPosition(view);        long groupId = callback.getGroupId(pos);        if (groupId < 0) return;        if (pos == 0 || isFirstInGroup(pos)) {                 outRect.top = topGap;        } else {                 outRect.top = 0;        }    }    @Override    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {             super.onDrawOver(c, parent, state);        int itemCount = state.getItemCount();        int childCount = parent.getChildCount();        int left = parent.getPaddingLeft();        int right = parent.getWidth() - parent.getPaddingRight();        float lineHeight = textPaint.getTextSize() + fontMetrics.descent;        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;            String textLine = callback.getGroupFirstLine(position).toUpperCase();            if (TextUtils.isEmpty(textLine)) continue;            int viewBottom = view.getBottom();            float textY = Math.max(topGap, view.getTop());            if (position + 1 < itemCount) {      //下一个和当前不一样移动当前                long nextGroupId = callback.getGroupId(position + 1);                if (nextGroupId != groupId && viewBottom < textY ) {     //组内最后一个view进入了header                    textY = viewBottom;                }            }            c.drawRect(left, textY - topGap, right, textY, paint);            c.drawText(textLine, left, textY, textPaint);        }    }}

好了,现在发现ItemDecoration有多强大了吧! 当然还有更多就需要你自己去发现了。

更多相关文章

  1. Android学习系列(43)--使用事件总线框架EventBus和Otto
  2. Android之旅十三 android中的数据传递方法
  3. 【Frida Hook 学习记录】Frida Hook Android(安卓)常用方法
  4. Android笔记——Day1 *Activity生命周期 *Activiity切换效果
  5. Android(安卓)Service 服务—— bindService与remoteService
  6. android 异步加载
  7. Android(安卓)数据持久化方式
  8. Android(安卓)View事件派发机制详解与源码分析
  9. Android实现掷骰子效果

随机推荐

  1. android 条形码的应用
  2. Android开发人员的10大抱怨
  3. Android基础笔记(一)-快速入门
  4. Android样式开发——layer-list篇
  5. Android应用程序请求SurfaceFlinger服务
  6. Android(安卓)Measure 体系简单总结
  7. Android实现全屏显示的方法 固定横屏或者
  8. 玩转Android---UI篇---TextView(文本框)
  9. Android(安卓)Property System | Android
  10. Android:ANT打包常见问题简述