欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android自定义字母导航栏

程序员文章站 2022-09-02 09:27:56
本文实例为大家分享了android字母导航栏的具体代码,供大家参考,具体内容如下 效果 实现逻辑 明确需求 字母导航栏在实际开发中还是比较多见的,城市选择、名称选择等等...

本文实例为大家分享了android字母导航栏的具体代码,供大家参考,具体内容如下

效果

Android自定义字母导航栏

实现逻辑

明确需求

字母导航栏在实际开发中还是比较多见的,城市选择、名称选择等等可能需要到。 现在要做到的就是在滑动控件过程中可以有内容以及 下标的回调,方便处理其他逻辑!

整理思路

1、确定控件的尺寸,防止内容显示不全。相关的逻辑在onmeasure()方法中处理;
2、绘制显示的内容,在按下和抬起不同状态下文字、背景的颜色。相关逻辑在ondraw()方法中;
3、滑动事件的处理以及事件回调。相关逻辑在ontouchevent()方法中;

动手实现

在需求明确、思路清晰的情况下就要开始动手实现(需要了解自定义view的一些基础api)。核心代码就ondraw()中。在代码中有思路和注释,可以结合代码一起看看。如果有疑惑、优化、错误的地方请在评论区提出,共同进步!

完整代码

/**
 * 自定义字母导航栏
 * 
 * 总的来说就四步
 * 1、测量控件尺寸{@link #onmeasure(int, int)}
 * 2、绘制显示内容(背景以及字符){@link #ondraw(canvas)}
 * 3、处理滑动事件{@link #ontouchevent(motionevent)}
 * 4、暴露接口{@link #setonnavigationscrollerlistener(onnavigationscrollerlistener)}
 *
 * @attr customtextcolordefault //导航栏默认文字颜色
 * @attr customtextcolordown //导航栏按下文字颜色
 * @attr custombackgroundcolordown //导航栏按下背景颜色
 * @attr customletterdivheight //导航栏内容高度间隔
 * @attr customtextsize //导航栏文字尺寸
 * @attr custombackgroundangle //导航栏背景角度
 */
public class customletternavigationview extends view {
  private static final string tag = "customletternavigation";
  //导航内容
  private string[] mnavigationcontent;
  //导航栏内容间隔
  private float mcontentdiv;
  //导航栏文字大小
  private float mcontenttextsize;
  //导航栏文字颜色
  private int mcontenttextcolor;
  //导航栏按下时背景颜色
  private int mbackgroundcolor;
  //导航栏按下时圆角度数
  private int mbackgroundangle = 0;
  //导航栏按下时文字颜色
  private int mdowncontenttextcolor;
  private textpaint mtextpaint;
  private paint mpaintbackgrount;
  private boolean meventactionstate = false;
  private string mcurrentletter = "";
  private onnavigationscrollerlistener monnavigationscrollerlistener;
  private final rectf mrectf = new rectf();
  public customletternavigationview(context context) {
    this(context, null);
  }

  public customletternavigationview(context context, @nullable attributeset attrs) {
    this(context, attrs, 0);
  }

  public customletternavigationview(context context, @nullable attributeset attrs, int defstyleattr) {
    super(context, attrs, defstyleattr);
    initdefaultdata();//初始化默认数据
    initattrs(context, attrs);
  }

  @override
  protected void ondraw(canvas canvas) {
    /**
     * <p>
     *   这里我们分为两步
     *
     *   1、绘制背景
     *    这里简单,直接调用canvas的drawroundrect()方法直接绘制
     *   2、绘制显示文本
     *    绘制文字首先要定位,定位每个字符的坐标
     *    x轴简单,宽度的一半
     *    y轴坐标通过每个字符的高heightshould乘以已绘制字符的数目
     * </p>
     */
    int mviewwidth = getwidth();
    //绘制背景
    mrectf.set(0, 0, mviewwidth, getheight());
    if (meventactionstate) {
      mtextpaint.setcolor(mdowncontenttextcolor);
      mpaintbackgrount.setcolor(mbackgroundcolor);
      canvas.drawroundrect(mrectf, mbackgroundangle, mbackgroundangle, mpaintbackgrount);
    } else {
      mtextpaint.setcolor(mcontenttextcolor);
      mpaintbackgrount.setcolor(color.transparent);
      drawable mbackground = getbackground();
      if (mbackground instanceof colordrawable) {
        mpaintbackgrount.setcolor(((colordrawable) mbackground).getcolor());
      }
      canvas.drawroundrect(mrectf, mbackgroundangle, mbackgroundangle, mpaintbackgrount);
    }
    //绘制文本
    float textx = mviewwidth / 2;
    //x轴坐标
    int contentlenght = getcontentlength();
    //y轴坐标(这里在测量的时候多加入了两个间隔高度要减去,同时还有padding值)
    float heightshould = (getheight() - mcontentdiv * 2 - getpaddingtop() - getpaddingbottom()) / contentlenght;
    for (int i = 0; i < contentlenght; i++) {
      //计算y轴的坐标
      float starty = ((i + 1) * heightshould) + getpaddingtop();
      //绘制文字
      canvas.drawtext(mnavigationcontent[i], textx, starty, mtextpaint);
    }
  }

  @override
  public boolean ontouchevent(motionevent event) {
    /**
     * 这里主要处理手指滑动事件
     */
    float meventy = event.gety();
    switch (event.getaction()) {
      case motionevent.action_down:
        //手指按下的时候,修改enent状态、重绘背景、触发回调
        meventactionstate = true;
        invalidate();
        if (monnavigationscrollerlistener != null) {
          monnavigationscrollerlistener.ondown();
        }
        scrollcount(meventy);
        break;
      case motionevent.action_move:
        scrollcount(meventy);
        break;
      case motionevent.action_cancel:
      case motionevent.action_up:
        //手指离开的时候,修改enent状态、重绘背景、触发回调
        meventactionstate = false;
        invalidate();
        if (monnavigationscrollerlistener != null) {
          monnavigationscrollerlistener.onup();
        }
        break;
    }
    return true;
  }


  @override
  protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
    super.onmeasure(widthmeasurespec, heightmeasurespec);
    /**
     * <p>
     *   这里做了简单的适应,其目的就是为了能够正常的显示我们的内容
     *
     *   不管设置的是真实尺寸或者是包裹内容,都会以内容的最小尺寸为
     *   基础,如果设置的控件尺寸大于我们内容的最小尺寸,就使用控件
     *   尺寸,反之使用内容的最小尺寸!
     * </p>
     */
    int widhtmode = measurespec.getmode(widthmeasurespec);
    int heightmode = measurespec.getmode(heightmeasurespec);
    //获取控件的尺寸
    int actualwidth = measurespec.getsize(widthmeasurespec);
    int actualheight = measurespec.getsize(heightmeasurespec);
    int contentlegth = getcontentlength();
    //计算一个文字的尺寸
    rect mrect = measuretextsize();
    //内容的最小宽度
    float contentwidth = mrect.width() + mcontentdiv * 2;
    //内容的最小高度
    float contentheight = mrect.height() * contentlegth + mcontentdiv * (contentlegth + 3);
    if (measurespec.at_most == widhtmode) {
      //宽度包裹内容
      actualwidth = (int) contentwidth + getpaddingleft() + getpaddingright();
    } else if (measurespec.exactly == widhtmode) {
      //宽度限制
      if (actualwidth < contentwidth) {
        actualwidth = (int) contentwidth + getpaddingleft() + getpaddingright();
      }
    }
    if (measurespec.at_most == heightmode) {
      //高度包裹内容
      actualheight = (int) contentheight + getpaddingtop() + getpaddingbottom();
    } else if (measurespec.exactly == widhtmode) {
      //高度限制
      if (actualheight < contentheight) {
        actualheight = (int) contentheight + getpaddingtop() + getpaddingbottom();
      }
    }
    setmeasureddimension(actualwidth, actualheight);
  }


  /**
   * 初始化默认数据
   */
  private void initdefaultdata() {
    mnavigationcontent = new string[]{"搜", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"};
    mcontentdiv = typedvalue.applydimension(typedvalue.complex_unit_dip, 5, getresources().getdisplaymetrics());
    mcontenttextsize = typedvalue.applydimension(typedvalue.complex_unit_sp, 14, getresources().getdisplaymetrics());
    mcontenttextcolor = color.parsecolor("#333333");
    mdowncontenttextcolor = color.white;
    mbackgroundcolor = color.parsecolor("#d7d7d7");
    mbackgroundangle = 0;
    //绘制文字画笔
    mtextpaint = new textpaint();
    mtextpaint.setantialias(true);
    mtextpaint.settextsize(mcontenttextsize);
    mtextpaint.setcolor(mcontenttextcolor);
    mtextpaint.settextalign(paint.align.center);
    //绘制背景画笔
    mpaintbackgrount = new paint();
    mpaintbackgrount.setantialias(true);
    mpaintbackgrount.setstyle(paint.style.fill);
  }

  /**
   * 初始化自定义属性
   *
   * @param context
   * @param attrs
   */
  private void initattrs(context context, attributeset attrs) {
    typedarray mtypedarray = context.obtainstyledattributes(attrs, r.styleable.customletternavigationview);
    mcontenttextcolor = mtypedarray.getcolor(r.styleable.customletternavigationview_customtextcolordefault, mcontenttextcolor);
    mbackgroundcolor = mtypedarray.getcolor(r.styleable.customletternavigationview_custombackgroundcolordown, mbackgroundcolor);
    mdowncontenttextcolor = mtypedarray.getcolor(r.styleable.customletternavigationview_customtextcolordown, mdowncontenttextcolor);
    mcontenttextsize = mtypedarray.getdimension(r.styleable.customletternavigationview_customtextsize, mcontenttextsize);
    mcontentdiv = mtypedarray.getfloat(r.styleable.customletternavigationview_customletterdivheight, mcontentdiv);
    mbackgroundangle = mtypedarray.getint(r.styleable.customletternavigationview_custombackgroundangle, mbackgroundangle);
    mtypedarray.recycle();
  }


  /**
   * 获取内容长度
   *
   * @return 内容长度
   */
  private int getcontentlength() {
    if (mnavigationcontent != null) {
      return mnavigationcontent.length;
    }
    return 0;
  }

  /**
   * 滑动计算
   *
   * @param meventy y轴滑动距离
   */
  private void scrollcount(float meventy) {
    //滑动的时候利用滑动距离和每一个字符高度进行取整,获取到index
    rect mrect = measuretextsize();
    int index = (int) ((meventy - getpaddingtop() - getpaddingbottom() - mcontentdiv * 2) / (mrect.height() + mcontentdiv));
    //防止越界
    if (index >= 0 && index < getcontentlength()) {
      string newletter = mnavigationcontent[index];
      //防止重复触发回调
      if (!mcurrentletter.equals(newletter)) {
        mcurrentletter = newletter;
        if (monnavigationscrollerlistener != null) {
          monnavigationscrollerlistener.onscroll(mcurrentletter, index);
        }
      }
    }
  }

  /**
   * 测量文字的尺寸
   *
   * @return 一个汉字的矩阵
   */
  public rect measuretextsize() {
    rect mrect = new rect();
    if (mtextpaint != null) {
      mtextpaint.gettextbounds("田", 0, 1, mrect);
    }
    return mrect;
  }


  /**
   * 设置导航栏滑动监听
   *
   * @param onnavigationscrollerlistener
   */
  public void setonnavigationscrollerlistener(onnavigationscrollerlistener onnavigationscrollerlistener) {
    this.monnavigationscrollerlistener = onnavigationscrollerlistener;
  }

  /**
   * 设置导航栏显示内容
   *
   * @param content 需要显示的内容
   */
  public void setnavigationcontent(string content) {
    if (!textutils.isempty(content)) {
      mnavigationcontent = null;
      mnavigationcontent = new string[content.length()];
      for (int i = 0; i < content.length(); i++) {
        mnavigationcontent[i] = string.valueof(content.charat(i));
      }
    }
    //需要重新测量
    requestlayout();
  }

  public interface onnavigationscrollerlistener {
    //按下
    void ondown();

    //滑动
    void onscroll(string letter, int position);

    //离开
    void onup();

  }
}

自定义属性

<declare-styleable name="customletternavigationview">
    <attr name="customtextcolordefault" format="color" />
    <attr name="customtextcolordown" format="color" />
    <attr name="custombackgroundcolordown" format="color" />
    <attr name="customletterdivheight" format="dimension" />
    <attr name="customtextsize" format="dimension" />
    <attr name="custombackgroundangle" format="integer" />
</declare-styleable>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。