国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android ItemDecoration 實現分組索引列表的示例代碼

2019-12-12 01:47:59
字體:
來源:轉載
供稿:網友

本文介紹了Android ItemDecoration 實現分組索引列表的示例代碼,分享給大家。具體如下:

先來看看效果:


我們要實現的效果主要涉及三個部分:

  1. 分組 GroupHeader
  2. 分割線
  3. 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 parent, RecyclerView.State state) {    super.onDraw(c, parent, state);  }  @Override  public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {    super.onDrawOver(c, parent, 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, View view, RecyclerView parent, RecyclerView.State state) {    super.getItemOffsets(outRect, view, parent, 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, 0);    }  }  @Override  public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {    super.onDraw(c, parent, state);  }  @Override  public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {    super.onDrawOver(c, parent, state);  }}

其中tags就是我們設置到ItemDecoration的數據源,是一個String集合。groupHeaderHeight就是ItemView的頂部偏移量。

之后就是在ItemView的頂部偏移區域繪制GroupHeader了:

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 parent, RecyclerView.State state) {    super.onDraw(c, parent, 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, parent, view, tag);      }    }  }  @Override  public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {    super.onDrawOver(c, parent, state);  }  private void drawGroupHeader(Canvas c, RecyclerView parent, View view, 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, top, right, 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, View view, RecyclerView parent, RecyclerView.State state) {    super.getItemOffsets(outRect, view, parent, state);  }  @Override  public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {    super.onDraw(c, parent, state);  }  @Override  public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {    super.onDrawOver(c, parent, 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, parent, tag);    if (flag) {      c.restore();    }  }  private void drawSuspensionGroupHeader(Canvas c, RecyclerView parent, String tag) {    int left = parent.getPaddingLeft();    int right = parent.getWidth() - parent.getPaddingRight();    int bottom = groupHeaderHeight;    int top = 0;    c.drawRect(left, top, right, bottom, mPaint);    int x = left + groupHeaderLeftPadding;    int y = top + (groupHeaderHeight + Utils.getTextHeight(mTextPaint, tag)) / 2;    c.drawText(tag, x, y, 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, Paint paint, TextPaint textPaint, int[] params, int position);}

二、分割線

現在RecyclerView還差一個分割線,當前最笨的辦法可以在ItemView的布局文件中設置,既然系統都提供了ItemDecoration,那用它來優雅的實現為何不可呢,我們只需要給列表中每組數據除了最后一項數據對應的ItemView之外的添加分割線即可,也就是不給每組數據對應的最后一個ItemView添加分割線。很簡單,直接上核心代碼:

public class DivideItemDecoration extends RecyclerView.ItemDecoration {  @Override  public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {    super.getItemOffsets(outRect, view, parent, 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, 0, 0, divideHeight);    }  }  @Override  public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {    super.onDraw(c, parent, 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, parent, view);      }    }  }  @Override  public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {    super.onDrawOver(c, parent, state);  }  private void drawDivide(Canvas c, RecyclerView parent, 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, top, right, bottom, 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, x, y, 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, Paint paint, TextPaint textPaint, int[] params, 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), Utils.dip2px(context, 20), true);            c.drawBitmap(icon1, x, params[1] + Utils.dip2px(context, 5), paint);            c.drawText(tags.get(position), x + Utils.dip2px(context, 25), y, textPaint);          }          @Override          public void onDrawSuspensionGroupHeader(Canvas c, Paint paint, TextPaint textPaint, int[] params, 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), Utils.dip2px(context, 20), true);            c.drawBitmap(icon1, x, params[1] + Utils.dip2px(context, 5), paint);            c.drawText(tags.get(position), x + Utils.dip2px(context, 25), y, textPaint);          }        })    );

坐標計算有點復雜了......0_o......

看下效果:

當然不止于此,更多的效果等待著機智的你去創造。

更多代碼細節及用法可參考:https://github.com/Othershe/GroupIndexLib

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 岳池县| 金川县| 崇阳县| 宾阳县| 新干县| 桐柏县| 堆龙德庆县| 衡山县| 武城县| 黎川县| 华池县| 彩票| 阿城市| 白沙| 内丘县| 合作市| 苍溪县| 都兰县| 景东| 彝良县| 右玉县| 黄冈市| 泗阳县| 饶阳县| 高雄县| 微山县| 京山县| 泗水县| 铜梁县| 建湖县| 门源| 正阳县| 金门县| 荣成市| 庄河市| 巩义市| 新河县| 安西县| 广河县| 建阳市| 潜江市|