乐趣区

关于android:自定义View实现字母导航控件

明天分享一个以前实现的通讯录字母导航控件,上面自定义一个相似通讯录的字母导航 View,能够晓得须要自定义的几个因素,如绘制字母指示器、绘制文字、触摸监听、坐标计算等,自定义实现之后可能达到的性能如下:

  • 实现列表数据与字母之间的互相联动;
  • 反对布局文件属性配置;
  • 在布局文件中可能配置相干属性,如字母色彩、字母字体大小、字母指示器色彩等属性。

次要内容如下:

  1. 自定义属性
  2. Measure 测量
  3. 坐标计算
  4. 绘制
  5. 显示成果

自定义属性

在 value 上面创立 attr.xml,在外面配置须要自定义的属性,具体如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="LetterView">
        <!-- 字母色彩 -->
        <attr name="letterTextColor" format="color" />
        <!-- 字母字体大小 -->
        <attr name="letterTextSize" format="dimension" />
        <!-- 整体背景 -->
        <attr name="letterTextBackgroundColor" format="color" />
        <!-- 是否启用指示器 -->
        <attr name="letterEnableIndicator" format="boolean" />
        <!-- 指示器色彩 -->
        <attr name="letterIndicatorColor" format="color" />
    </declare-styleable>
</resources>

而后在相应的构造方法中获取这些属性并进行相干属性的设置,具体如下:

public LetterView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);
    // 获取属性
    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LetterView);
    int letterTextColor = array.getColor(R.styleable.LetterView_letterTextColor, Color.RED);
    int letterTextBackgroundColor = array.getColor(R.styleable.LetterView_letterTextBackgroundColor, Color.WHITE);
    int letterIndicatorColor = array.getColor(R.styleable.LetterView_letterIndicatorColor, Color.parseColor("#333333"));
    float letterTextSize = array.getDimension(R.styleable.LetterView_letterTextSize, 12);
    enableIndicator = array.getBoolean(R.styleable.LetterView_letterEnableIndicator, true);

    // 默认设置
    mContext = context;
    mLetterPaint = new Paint();
    mLetterPaint.setTextSize(letterTextSize);
    mLetterPaint.setColor(letterTextColor);
    mLetterPaint.setAntiAlias(true);

    mLetterIndicatorPaint = new Paint();
    mLetterIndicatorPaint.setStyle(Paint.Style.FILL);
    mLetterIndicatorPaint.setColor(letterIndicatorColor);
    mLetterIndicatorPaint.setAntiAlias(true);

    setBackgroundColor(letterTextBackgroundColor);

    array.recycle();}

Measure 测量

要想准确的管制自定义的尺寸以及坐标,必须要测量出以后自定义 View 的宽高,而后才能够通过测量到的尺寸计算相干坐标,具体测量过程就是继承 View 重写 omMeasure() 办法实现测量,要害代码如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    // 获取宽高的尺寸大小
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    //wrap_content 默认宽高
    @SuppressLint("DrawAllocation") Rect mRect = new Rect();
    mLetterPaint.getTextBounds("A", 0, 1, mRect);
    mWidth = mRect.width() + dpToPx(mContext, 12);
    int mHeight = (mRect.height() + dpToPx(mContext, 5)) * letters.length;

    if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT &&
            getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {setMeasuredDimension(mWidth, mHeight);
    } else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {setMeasuredDimension(mWidth, heightSize);
    } else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {setMeasuredDimension(widthSize, mHeight);
    }

    mWidth = getMeasuredWidth();
    int averageItemHeight = getMeasuredHeight() / 28;
    int mOffset = averageItemHeight / 30; // 界面调整
    mItemHeight = averageItemHeight + mOffset;
}

坐标计算

自定义 View 实际上就是在 View 上找到适合的地位,将自定义的元素有序的绘制进去即可,绘制过程最艰难的就是如何依据具体需要计算适合的右边,至于绘制都是 API 的调用,只有坐标地位计算好了,自定义 View 绘制这一块应该就没有问题了,上面的图示次要是标注了字母指示器绘制的核心地位坐标的计算以及文字绘制的终点地位计算,绘制过程中要保障文字在指示器核心地位,参考如下:

自定义字母导航

绘制

自定义 View 的绘制操作都是在 onDraw() 办法中进行的,这里次要应用到圆的绘制以及文字的绘制,具体就是 drawCircle() 和 drawText() 办法的应用,为防止文字被遮挡,需绘制字母指示器,而后再绘制字母,代码参考如下:

@Override
protected void onDraw(Canvas canvas) {
    // 获取字母宽高
    @SuppressLint("DrawAllocation") Rect rect = new Rect();
    mLetterPaint.getTextBounds("A", 0, 1, rect);
    int letterWidth = rect.width();
    int letterHeight = rect.height();

    // 绘制指示器
    if (enableIndicator){for (int i = 1; i < letters.length + 1; i++) {if (mTouchIndex == i) {canvas.drawCircle(0.5f * mWidth, i * mItemHeight - 0.5f * mItemHeight, 0.5f * mItemHeight, mLetterIndicatorPaint);
            }
        }
    }
    // 绘制字母
    for (int i = 1; i < letters.length + 1; i++) {canvas.drawText(letters[i - 1], (mWidth - letterWidth) / 2, mItemHeight * i - 0.5f * mItemHeight + letterHeight / 2, mLetterPaint);
    }
}

到此为止,能够说 View 的根本绘制完结了,当初应用自定义的 View 界面可能显示进去了,只是还没有增加相干的事件操作,上面将在 View 的触摸事件里实现相干逻辑。

Touch 事件处理

为了判断手指以后所在位置对应的是哪一个字母,须要获取以后触摸的坐标地位来计算字母索引,从新 onTouchEvent() 办法,监听 MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE 来计算索引地位,监听 MotionEvent.ACTION_UP 将取得后果回调进来,具体参考如下:

@Override
public boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
            isTouch = true;
            int y = (int) event.getY();
            Log.i("onTouchEvent","--y->" + y + "-y-dp-->" + DensityUtil.px2dp(getContext(), y));
            int index = y / mItemHeight;

            if (index != mTouchIndex && index < 28 && index > 0) {
                mTouchIndex = index;
                Log.i("onTouchEvent","--mTouchIndex->" + mTouchIndex + "--position->" + mTouchIndex);
            }

            if (mOnLetterChangeListener != null && mTouchIndex > 0) {mOnLetterChangeListener.onLetterListener(letters[mTouchIndex - 1]);
            }

            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            isTouch = false;
            if (mOnLetterChangeListener != null && mTouchIndex > 0) {mOnLetterChangeListener.onLetterDismissListener();
            }
            break;
    }
    return true;
}

到此为止,View 的自定义要害局部根本实现。

数据组装

字母导航的基本思路是将某个须要与字母匹配的字段转换为对应的字母,而后依照该字段对数据进行排序,最终使得通过某个数据字段的首字母就能够批匹配到雷同首字母的数据了,这里将汉字转化为拼音应用的是 pinyin4j-2.5.0.jar,而后对数据项依照首字母进行排序将数据展现到进去即可,汉字装换为拼音如下:

// 汉字转换为拼音
public static String getChineseToPinyin(String chinese) {StringBuilder builder = new StringBuilder();
    HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
    format.setCaseType(HanyuPinyinCaseType.UPPERCASE);
    format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);

    char[] charArray = chinese.toCharArray();
    for (char aCharArray : charArray) {if (Character.isSpaceChar(aCharArray)) {continue;}
        try {String[] pinyinArr = PinyinHelper.toHanyuPinyinStringArray(aCharArray, format);
            if (pinyinArr != null) {builder.append(pinyinArr[0]);
            } else {builder.append(aCharArray);
            }
        } catch (BadHanyuPinyinOutputFormatCombination badHanyuPinyinOutputFormatCombination) {badHanyuPinyinOutputFormatCombination.printStackTrace();
            builder.append(aCharArray);
        }
    }
    return builder.toString();}

至于数据排序应用 Comparator 接口即可,这里就不在赘述了,具体获取文末源码链接查看。

显示成果

显示成果如下:

我的库存,须要的小伙伴请点击我的 GitHub 收费支付

退出移动版