本文介绍了AndroID Itemdecoration 实现分组索引列表的示例代码,分享给大家。具体如下:
先来看看效果:
我们要实现的效果主要涉及三个部分:
分组 Groupheader 分割线 SIDebar前两个部分涉及到一个Itemdecoration类,也是我们接下来的重点,该类是RecyclerVIEw的一个抽象静态内部类,主要作用就是给RecyclerVIEw的ItemVIEw绘制额外的装饰效果,例如给RecyclerVIEw添加分割线。
使用Itemdecoration时需要继承该类,根据需求可以重写如下三个方法,其它的方法已经deprecated了:
public class GroupheaderItemdecoration extends RecyclerVIEw.Itemdecoration { @OverrIDe public voID getItemOffsets(Rect outRect,VIEw vIEw,RecyclerVIEw parent,RecyclerVIEw.State state) { super.getItemOffsets(outRect,vIEw,parent,state); } @OverrIDe public voID onDraw(Canvas c,RecyclerVIEw.State state) { super.onDraw(c,state); } @OverrIDe public voID onDrawOver(Canvas c,RecyclerVIEw.State state) { super.onDrawOver(c,state); }}
然后将其添加到RecyclerVIEw中:
recyclerVIEw.addItemdecoration(new GroupheaderItemdecoration())
了解这个三个方法的作用,这样才能更好的实现我们想要的功能:
1、getItemOffsets()
给指定的ItemVIEw设置偏移量,具体怎么设置呢,咱们看图说话:
图中左边的是原始RecyclerVIEw列表,右边是设置了ItemVIEw偏移量的列表,其实相当于在ItemVIEw外部添加了一个矩形区域
其中left、top、right、bottom就是ItemVIEw在四个方向的偏移量,对应的设置代码如下:
outRect.set(left,top,right,bottom)
在我们的分组索引列表中,只需要对ItemVIEw设置顶部的偏移量,其它三个偏移量为0即可。这样就可以在ItemVIEw顶部预留出一定高度的区域,如下图:
2、onDraw()
在getItemOffsets()方法中,我们设置了偏移量,进而得到了对应的偏移区域,接下来在onDraw()中就可以给ItemVIEw绘制装饰效果了,所以我们在该方法中将分组索引列表中的Groupheader的内容绘制在ItemVIEw顶部偏移区域里。也就是绘制前边 gif 图里的A、B、C... Groupheader,虽然看起来像一个个独立的ItemVIEw,但并不是的哦!
注意该绘制 *** 作会在ItemVIEw的onDraw()前完成的!
3、onDrawOver()
该方法同样也是用来绘制的,但是它在Itemdecoration的onDraw()方法和ItemVIEw的onDraw()完成后才执行。所以其绘制的内容会遮挡在RecyclerVIEw上,因此我们可以在该方法中绘制分组索引列表中悬浮的Groupheader,也就是在列表顶部随着列表滚动切换的Groupheader。
一、分组Groupheader
三个方法的作用已经解释完了,接下来就是代码实现我们的效果了:
首先保证RecyclerVIEw的数据源已经按照某种规律进行了分组排序,具体什么规律你说了算,我们例子中按照数据源中指定字段的值的首字母升序排列,也就是常见通讯录的排序方式。然后在每个data中保存需要在Groupheader上显示的内容,可以使用tag字段,我们这里保存的是对应的首字母。这里没必要将整个数据源设置到Itemdecoration里边,所以我们只需要提取排序后数据源的tag保存到列表中,然后设置到Itemdecoration里边,后边的 *** 作就依赖设置的数据源了,根据tag的异同来决定是否绘制Groupheader等。
上边已经分析了,Groupheader只在列表中每组数据对应的第一个ItemVIEw顶部显示,只需要对ItemVIEw设置顶部的偏移量即可:
public class GroupheaderItemdecoration extends RecyclerVIEw.Itemdecoration { @OverrIDe public voID getItemOffsets(Rect outRect,state); RecyclerVIEw.LayoutManager manager = parent.getLayoutManager(); //只处理线性垂直类型的列表 if ((manager instanceof linearlayoutmanager) && linearlayoutmanager.VERTICAL != ((linearlayoutmanager) manager).getorIEntation()) { return; } int position = parent.getChildAdapterposition(vIEw); //ItemVIEw的position==0 或者 当前ItemVIEw的data的tag和上一个ItemVIEw的不相等,则为当前ItemVIEw设置top 偏移量 if (!Utils.ListIsEmpty(Tags) && (position == 0 || !Tags.get(position).equals(Tags.get(position - 1)))) { outRect.set(0,groupheaderHeight,0); } } @OverrIDe public voID onDraw(Canvas c,state); }}
其中Tags就是我们设置到Itemdecoration的数据源,是一个String集合。groupheaderHeight就是ItemVIEw的顶部偏移量。
之后就是在ItemVIEw的顶部偏移区域绘制Groupheader了:
public class GroupheaderItemdecoration extends RecyclerVIEw.Itemdecoration { @OverrIDe public voID getItemOffsets(Rect outRect,state); for (int i = 0; i < parent.getChildCount(); i++) { VIEw vIEw = parent.getChildAt(i); int position = parent.getChildAdapterposition(vIEw); String tag = Tags.get(position); //和getItemOffsets()里的条件判断类似,开始绘制分组的Groupheader if (!Utils.ListIsEmpty(Tags) && (position == 0 || !tag.equals(Tags.get(position - 1)))) { drawGroupheader(c,tag); } } } @OverrIDe public voID onDrawOver(Canvas c,state); } private voID drawGroupheader(Canvas c,String tag) { RecyclerVIEw.LayoutParams params = (RecyclerVIEw.LayoutParams) vIEw.getLayoutParams(); int left = parent.getpaddingleft(); int right = parent.getWIDth() - parent.getpaddingRight(); int bottom = vIEw.gettop() - params.topmargin; int top = bottom - groupheaderHeight; c.drawRect(left,bottom,mPaint); int x = left + groupheaderleftpadding; int y = top + (groupheaderHeight + Utils.getTextHeight(mTextPaint,tag)) / 2; c.drawText(tag,x,y,mTextPaint); }}
绘制Groupheader就是Canvasc *** 作,先绘制一个矩形框,再绘制相应的文字,当然绘制图片也是没问题的,其中groupheaderleftpadding是个可配置字段,代表绘制的文字或图片到列表左边沿的距离,也可以理解为Groupheader的左padding。
最后就是悬浮在顶部的Groupheader绘制了:
public class GroupheaderItemdecoration extends RecyclerVIEw.Itemdecoration { @OverrIDe public voID getItemOffsets(Rect outRect,state); if (!show) { return; } //列表第一个可见的ItemVIEw位置 int position = ((linearlayoutmanager) (parent.getLayoutManager())).findFirstVisibleItemposition(); String tag = Tags.get(position); VIEw vIEw = parent.findVIEwHolderForAdapterposition(position).itemVIEw; //当前ItemVIEw的data的tag和下一个itemVIEw的不相等,则代表将要重新绘制悬停的Groupheader boolean flag = false; if (!Utils.ListIsEmpty(Tags) && (position + 1) < Tags.size() && !tag.equals(Tags.get(position + 1))) { //如果第一个可见ItemVIEw的底部坐标小于groupheaderHeight,则执行Canvas向上位移 *** 作 if (vIEw.getBottom() <= groupheaderHeight) { c.save(); flag = true; c.translate(0,vIEw.getHeight() + vIEw.gettop() - groupheaderHeight); } } drawSuspensionGroupheader(c,tag); if (flag) { c.restore(); } } private voID drawSuspensionGroupheader(Canvas c,String tag) { int left = parent.getpaddingleft(); int right = parent.getWIDth() - parent.getpaddingRight(); int bottom = groupheaderHeight; int top = 0; c.drawRect(left,mTextPaint); }}
绘制 *** 作和onDraw中的类似,gif 中有一个悬浮Groupheader上移的动画,就是通过Canvas位移来实现的,注意在Canvas位移的前后进行save()和restore() *** 作。
我们给GroupheaderItemdecoration提供了设置Groupheader左padding、高度、背景色、文字颜色、尺寸、以及是否显示顶部悬浮Groupheader的方法,方便使用。
关于绘制 *** 作需要注意的是,Groupheader所在的偏移区域和ItemVIEw是相互独立的,不要把Groupheader当做ItemVIEw的一部分哦。到这里Groupheader的功能就实现了,只需要将GroupheaderItemdecoration添加到RecyclerVIEw即可。
至于如何通过layout或者VIEw来实现Groupheader,做过一些尝试,效果都不理想,期待大家的好想法哦!
这里先用一个接口,对外提供自定义绘制Groupheader的方法:
public interface OnDrawItemdecorationListener { /** * 绘制Groupheader * @param c * @param paint 绘制Groupheader区域的paint * @param textPaint 绘制文字的paint * @param params 共四个值left、top、right、bottom 代表Groupheader所在区域的四个坐标值 * @param position 原始数据源中的position */ voID onDrawGroupheader(Canvas c,Paint paint,TextPaint textPaint,int[] params,int position); /** * 绘制悬浮在列表顶部的Groupheader */ voID onDrawSuspensionGroupheader(Canvas c,int position);}
二、分割线
现在RecyclerVIEw还差一个分割线,当前最笨的办法可以在ItemVIEw的布局文件中设置,既然系统都提供了Itemdecoration,那用它来优雅的实现为何不可呢,我们只需要给列表中每组数据除了最后一项数据对应的ItemVIEw之外的添加分割线即可,也就是不给每组数据对应的最后一个ItemVIEw添加分割线。很简单,直接上核心代码:
public class divIDeItemdecoration extends RecyclerVIEw.Itemdecoration { @OverrIDe public voID getItemOffsets(Rect outRect,state); RecyclerVIEw.LayoutManager manager = parent.getLayoutManager(); //只处理线性垂直类型的列表 if ((manager instanceof linearlayoutmanager) && linearlayoutmanager.VERTICAL != ((linearlayoutmanager) manager).getorIEntation()) { return; } int position = parent.getChildAdapterposition(vIEw); if (!Utils.ListIsEmpty(Tags) && (position + 1) < Tags.size() && Tags.get(position).equals(Tags.get(position + 1))) { //当前ItemVIEw的data的tag和下一个ItemVIEw的不相等,则为当前ItemVIEw设置bottom 偏移量 outRect.set(0,divIDeHeight); } } @OverrIDe public voID onDraw(Canvas c,state); for (int i = 0; i < parent.getChildCount(); i++) { VIEw vIEw = parent.getChildAt(i); int position = parent.getChildAdapterposition(vIEw); //和getItemOffsets()里的条件判断类似 if (!Utils.ListIsEmpty(Tags) && (position + 1) < Tags.size() && Tags.get(position).equals(Tags.get(position + 1))) { drawdivIDe(c,vIEw); } } } @OverrIDe public voID onDrawOver(Canvas c,state); } private voID drawdivIDe(Canvas c,VIEw vIEw) { RecyclerVIEw.LayoutParams params = (RecyclerVIEw.LayoutParams) vIEw.getLayoutParams(); int left = parent.getpaddingleft(); int right = parent.getWIDth(); int top = vIEw.getBottom() + params.bottommargin; int bottom = top + divIDeHeight; c.drawRect(left,mPaint); }}
三、SIDebar
SIDebar就是 gif 图右边的垂直字符条,是一个自定义view。手指触摸选中一个字符,则列表会滚动到对应的分组头部位置。实现起来也蛮简单的,核心代码如下:
public class SIDebar extends VIEw { @OverrIDe protected voID onMeasure(int wIDthMeasureSpec,int heightmeasureSpec) { super.onMeasure(wIDthMeasureSpec,heightmeasureSpec); int wIDthSize = MeasureSpec.getSize(wIDthMeasureSpec); int wIDthMode = MeasureSpec.getMode(wIDthMeasureSpec); int heightSize = MeasureSpec.getSize(heightmeasureSpec); int heightmode = MeasureSpec.getMode(heightmeasureSpec); //重新计算SIDebar宽高 if (heightmode == MeasureSpec.AT_MOST || wIDthMode == MeasureSpec.AT_MOST) { getMaxTextSize(); if (heightmode == MeasureSpec.AT_MOST) { heightSize = (maxHeight + 15) * indexArray.length; } if (wIDthMode == MeasureSpec.AT_MOST) { wIDthSize = maxWIDth + 10; } } setMeasuredDimension(wIDthSize,heightSize); } @OverrIDe protected voID onDraw(Canvas canvas) { for (int i = 0; i < indexArray.length; i++) { String index = indexArray[i]; float x = (mWIDth - mTextPaint.measureText(index)) / 2; float y = mmargintop + mHeight * i + (mHeight + Utils.getTextHeight(mTextPaint,index)) / 2; //绘制字符 canvas.drawText(index,mTextPaint); } } @OverrIDe public boolean ontouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: // 选中字符的下标 int pos = (int) ((event.getY() - mmargintop) / mHeight); if (pos >= 0 && pos < indexArray.length) { setBackgroundcolor(touch_color); if (onSIDebartouchListener != null) { for (int i = 0; i < Tags.size(); i++) { if (indexArray[pos].equals(Tags.get(i))) { onSIDebartouchListener.ontouch(indexArray[pos],i); break; } else { onSIDebartouchListener.ontouch(indexArray[pos],-1); } } } } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: setBackgroundcolor(UNtouch_color); if (onSIDebartouchListener != null) { onSIDebartouchListener.ontouchend(); } break; } return true; }}
在onMeasure()方法里,如果SIDebar的宽、高测量模式为MeasureSpec.AT_MOST则重新计算SIDebar的宽、高。onDraw()方法则是遍历索引数组,并绘制字符索引。在ontouchEvent()方法里,我们根据手指在SIDebar上触摸坐标点的y值,计算出触摸的相应字符,以便在OnSIDebartouchListener接口进行后续 *** 作,例如列表的跟随滚动等等。
四、实例
前边已经完成了三大核心功能,最后来愉快的使用下吧:
public class MainActivity extends AppCompatActivity { @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); RecyclerVIEw recyclerVIEw = (RecyclerVIEw) findVIEwByID(R.ID.List); SIDebar sIDebar = (SIDebar) findVIEwByID(R.ID.sIDe_bar); final TextVIEw tip = (TextVIEw) findVIEwByID(R.ID.tip); final List<ItemData> datas = new ArrayList<>(); ItemData data = new ItemData("北京"); datas.add(data); ItemData data1 = new ItemData("上海"); datas.add(data1); ItemData data2 = new ItemData("广州"); datas.add(data2); . . . ItemData data34 = new ItemData("Hello China"); datas.add(data34); ItemData data35 = new ItemData("宁波"); datas.add(data35); SortHelper<ItemData> sortHelper = new SortHelper<ItemData>() { @OverrIDe public String sortFIEld(ItemData data) { return data.getTitle(); } }; sortHelper.sortByLetter(datas);//将数据源按指定字段首字母排序 List<String> Tags = sortHelper.getTags(datas);//提取已排序数据源的tag值 MyAdapter adapter = new MyAdapter(this,datas,false); final linearlayoutmanager layoutManager = new linearlayoutmanager(this); layoutManager.setorIEntation(linearlayoutmanager.VERTICAL); recyclerVIEw.setLayoutManager(layoutManager); //添加分割线 recyclerVIEw.addItemdecoration(new divIDeItemdecoration().setTags(Tags)); //添加Groupheader recyclerVIEw.addItemdecoration(new GroupheaderItemdecoration(this) .setTags(Tags)//设置tag集合 .setGroupheaderHeight(30)//设置Groupheader高度 .setGroupheaderleftpadding(20));//设置Groupheader 左padding recyclerVIEw.setAdapter(adapter); sIDebar.setonSIDebartouchListener(Tags,new OnSIDebartouchListener() { @OverrIDe public voID ontouch(String text,int position) { tip.setVisibility(VIEw.VISIBLE); tip.setText(text); if ("↑".equals(text)) { layoutManager.scrollTopositionWithOffset(0,0); return; } //滚动列表到指定位置 if (position != -1) { layoutManager.scrollTopositionWithOffset(position,0); } } @OverrIDe public voID ontouchend() { tip.setVisibility(VIEw.GONE); } }); }}
这也就是文章开头的 gif 效果。如果需要自定义ItemVIEw的绘制可以这样写:
recyclerVIEw.addItemdecoration(new GroupheaderItemdecoration(this) .setTags(Tags) .setGroupheaderHeight(30) .setGroupheaderleftpadding(20) .setonDrawItemdecorationListener(new OnDrawItemdecorationListener() { @OverrIDe public voID onDrawGroupheader(Canvas c,int position) { c.drawRect(params[0],params[1],params[2],params[3],paint); int x = params[0] + Utils.dip2px(context,20); int y = params[1] + (Utils.dip2px(context,30) + Utils.getTextHeight(textPaint,Tags.get(position))) / 2; Bitmap icon = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher,null); Bitmap icon1 = Bitmap.createScaledBitmap(icon,Utils.dip2px(context,20),true); c.drawBitmap(icon1,params[1] + Utils.dip2px(context,5),paint); c.drawText(Tags.get(position),x + Utils.dip2px(context,25),textPaint); } @OverrIDe public voID onDrawSuspensionGroupheader(Canvas c,paint); int x = params[0] + Utils.dip2px(context,textPaint); } }) );
坐标计算有点复杂了......0_o......
看下效果:
当然不止于此,更多的效果等待着机智的你去创造。
更多代码细节及用法可参考:https://github.com/Othershe/GroupIndexLib
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。
总结以上是内存溢出为你收集整理的Android ItemDecoration 实现分组索引列表的示例代码全部内容,希望文章能够帮你解决Android ItemDecoration 实现分组索引列表的示例代码所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)