Android ItemDecoration 实现分组索引列表的示例代码

Android ItemDecoration 实现分组索引列表的示例代码,第1张

概述本文介绍了AndroidItemDecoration实现分组索引列表的示例代码,分享给大家。具体如下:

本文介绍了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 实现分组索引列表的示例代码所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1143963.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存