1、效果图
GitHub:https://github.com/aositeluoke/LetterView
2、总体分析
2.1、动态添加View实现
通过在代码中动态添加View的方式,虽然能够实现,但是UI层级比较多且添加View时逻辑也比较复杂,性能略逊一筹。
2.2、自定义View
本文采用的方案是自定义View。
3、详细分析
3.1初始状态使用下划线占位
3.2每次点击某个字母时,将该字母添加到LetterView中
3.3每次点击删除按钮时,移除最后一次加入的字母
3.4支持换行
4、代码实现
4.1、测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
this.mWidthMeasureSpec = widthMeasureSpec;
this.mHeightMeasureSpec = heightMeasureSpec;
int wSize = MeasureSpec.getSize(mWidthMeasureSpec);
int hSize = MeasureSpec.getSize(mHeightMeasureSpec);
int hMode = MeasureSpec.getMode(mHeightMeasureSpec);
singleBoxW = (int) (mUnderLineWidth + mLineLineDistance);
singleBoxH = (int) (mFontMetrics.bottom - mFontMetrics.top + mLineTextDistance);
columnNum = wSize / singleBoxW;//列数
mTotalLength = mAllLetters.size();
rowNum = mTotalLength / columnNum;
rowNum = mTotalLength % columnNum != 0 ? rowNum + 1 : rowNum;//行数
switch (hMode) {
case MeasureSpec.AT_MOST:
hSize = rowNum * singleBoxH;
break;
case MeasureSpec.EXACTLY:
break;
}
setMeasuredDimension(wSize, hSize);
}
在测量方法中,计算出该单词需要几行几列才能绘制完,计算出单个字母占用区域的宽高备用。
4.2、绘制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mAllLetters.size(); i++) {
int row = i / columnNum;
int column = i % columnNum;
float startX = (column * singleBoxW) + mLineLineDistance / 2f;
float startY = (row + 1) * singleBoxH - mUnderLineHeight / 2f;
float stopX = (column * singleBoxW) + mLineLineDistance / 2f + mUnderLineWidth;
float stopY = (row + 1) * singleBoxH - mUnderLineHeight / 2f;
canvas.drawLine(startX,
startY,
stopX,
stopY,
mLinePaint);
if (mCurLetters.size() > i) {
mLetterPaint.getTextBounds(mCurLetters.get(i) + "", 0, 1, mTextRect);
canvas.drawText(mCurLetters.get(i) + "", column * singleBoxW + (singleBoxW - mTextRect.width()) / 2f, row * singleBoxH + singleBoxH / 2f + baseYOff, mLetterPaint);
}
}
}
在绘制过程中,重点在于当前字母所在的坐标(行数和列数),得到坐标后,一切都好办了,通过坐标和字母盒子的宽高即可完成绘制。在这里需要谨记的是通过列表下标和总列数获取他的二维坐标(行数和列数)的计算公式。
int row = i / columnNum;
int column = i % columnNum;
5、提供一些方法
5.1、初始化
public void initWord(String text) {
if (TextUtils.isEmpty(text)) {
return;
}
this.mText = text;
char[] letters = mText.toCharArray();
mAllLetters.clear();
for (char arra : letters) {
mAllLetters.add(arra);
}
requestLayout();
invalidate();
}
通过initWord方法告知LetterView当前单词的长度,间接的计算出总行数和总列数。
5.2、新增字母
/**
* 新增
*
* @param c
*/
public void append(char c) {
if (mText.length() <= mCurLetters.size()) {
return;
}
mCurLetters.add(c);
requestLayout();
invalidate();
}
5.3、删除字母
/**
* 删除
*/
public void del() {
if (mCurLetters.size() > 0) {
mCurLetters.remove(mCurLetters.size() - 1);
requestLayout();
invalidate();
}
}
6、属性说明
属性名 | 默认值 | 备注 |
---|---|---|
text | 单词 | |
text_color | Color.WHITE | 字母颜色 |
underline_width | 60 | 字体下划线宽度 |
underline_height | 4 | 下划线高度 |
underline_color | Color.WHITE | 下划线颜色 |
line_line_distance | 10 | 下划线之间的间距 |
line_text_distance | 20 | 下划线与字母的间距 |
line_type | LINE_TYPE_SQUARE | 下划线类型 圆角或方角 |
7、总结
自定义View,听起来有些可怕,但是写起来感觉没有想象中的那么艰难,可能是因为没有加入触摸事件或动画才会有这样的感觉吧(笑脸)!不过,遇到需要自定义View才能解决问题时,也不要害怕,坐下来喝杯茶(躺下来也可以,哈哈),分析测量和绘制流程,寻找动画规律等,当你分析的非常详细的时候,就再也没有你解决不了的事儿了,因为粒度越细,事情越简单。