主要功能:
1. 汉字带拼音
2. 关键词不同颜色显示
示例:
Note:此代码为作者为特定项目而使用的,如果想要复用请根据实际情况修改!
Java 自定义TextView代码:
package com.example.test.pinyin;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import com.example.test.R;
import java.util.ArrayList;
public class PinyinTextView extends androidx.appcompat.widget.AppCompatTextView {
private int fontSize;
private String[] pinyin;
private String[] hanzi;
private int color;
private int keyWordColor = Color.rgb(0, 0, 255);
private String keyWord;
private int keyWordIndex = -1;
private TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
//`TextPaint`是Android中的一个类,它继承自`Paint`类,并提供了一些额外的方法和属性,专门用于在绘制文本时进行设置和处理。
//
//`TextPaint`类提供了一些与文本相关的功能,例如:
//
//1. 文本大小和样式:`TextPaint`提供了`setTextSize()`方法来设置文本的大小,`setTypeface()`方法来设置文本的字体样式(如粗体、斜体等)。
//
//2. 文本颜色和效果:`TextPaint`提供了`setColor()`方法来设置文本的颜色,`setUnderlineText()`和`setStrikeThruText()`方法来设置文本的下划线和删除线效果。
//
//3. 文本测量和布局:`TextPaint`提供了`measureText()`方法来测量文本的宽度,`breakText()`方法来根据给定的宽度截断文本,以及一些其他的文本布局相关方法。
private Paint.FontMetrics fontMetrics;
//`Paint.FontMetrics`是Android中的一个类,它用于描述文本绘制时的字体度量信息。通过`Paint`类的`getFontMetrics()`方法,可以获取一个`Paint.FontMetrics`对象,该对象包含了关于字体的各种度量信息。
//
//`Paint.FontMetrics`类提供了以下属性:
//
//1. `ascent`:文本基线以上的最大距离,包括文本的上坡度和上方的空间。
//
//2. `descent`:文本基线以下的最大距离,包括文本的下坡度和下方的空间。
//
//3. `top`:文本的最高点到基线的距离,包括上坡度和上方的空间。
//
//4. `bottom`:文本的最低点到基线的距离,包括下坡度和下方的空间。
//
//5. `leading`:推荐的行间距,即行与行之间的额外空间。
//
//这些度量信息可以用于调整和布局文本绘制的位置和间距。例如,可以使用`ascent`和`descent`的差值来计算文本的高度,或者使用`leading`来设置行间距。
private final int paddingTop = 0;
private final int minHeight = 144;
private ArrayList<Integer> indexList = new ArrayList<>(); // 存储每行首个String位置
int line = 1;
float density;
public PinyinTextView(Context context) {
this(context, null);
}
public PinyinTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PinyinTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PinyinTextView);
color = typedArray.getColor(R.styleable.PinyinTextView_customColor, getCurrentTextColor());
fontSize = (int) typedArray.getDimension(R.styleable.PinyinTextView_customTextSize, getTextSize());
//回收资源
typedArray.recycle();
initTextPaint();
}
public void initTextPaint() {
textPaint.setColor(color);
density = getResources().getDisplayMetrics().density;//返回dpi单位
textPaint.setStrokeWidth(density * 2);
textPaint.setTextSize(fontSize);
fontMetrics = textPaint.getFontMetrics();
//1. `textPaint.getFontMetrics()`返回的是`FontMetrics`对象,其中包含了一些浮点数值,表示文本绘制
// 时的字体度量信息。`FontMetrics`的字段包括`top`、`ascent`、`descent`、`bottom`和`leading`,
// 它们分别表示字体的顶部、上坡度、下坡度、底部和行间距。
//
//2. `textPaint.getFontMetricsInt()`返回的是`FontMetricsInt`对象,其中包含了一些整型数值,
// 表示文本绘制时的字体度量信息。`FontMetricsInt`的字段包括`top`、`ascent`、`descent`、`bottom`和
// `leading`,它们的精度相对较高,可以直接用于像素级别的计算。
}
public void setPinyin(String[] pinyin) {
this.pinyin = pinyin;
}
public void setHanzi(String[] hanzi) {
this.hanzi = hanzi;
}
public void setColor(int color) {
this.color = color;
if (textPaint != null) {
textPaint.setColor(color);
}
}
public void setFontSize(int size) {
this.fontSize = size;
if (textPaint != null) {
textPaint.setTextSize(size);
}
}
public void setKeyWordColor(int color) {
this.keyWordColor = color;
}
public void setKeyWord(String keyWord) {
this.keyWord = keyWord;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//`onMeasure(int widthMeasureSpec, int heightMeasureSpec)`是`TextView`类中的一个方法,用于测量`TextView`的尺寸。
//
//在Android中,测量视图的尺寸是在视图绘制过程中的一个重要步骤。当`TextView`的尺寸需要确定时,系统会调用`onMeasure()`方法来测量其宽度和高度。
//
//`onMeasure()`方法接收两个参数:`widthMeasureSpec`和`heightMeasureSpec`,它们是32位的整数值,包含了测量模式和测量尺寸的信息。
//
//测量模式有三种:
//- `MeasureSpec.EXACTLY`:表示精确的尺寸要求,通常是使用固定的数值或`match_parent`属性指定的尺寸。
//- `MeasureSpec.AT_MOST`:表示最大尺寸限制,通常是使用`wrap_content`属性指定的尺寸。
//- `MeasureSpec.UNSPECIFIED`:表示没有限制,通常在父容器不对子视图施加限制时使用。
//
//测量尺寸是一个32位整数值,可以通过`MeasureSpec.getSize()`方法获取实际的尺寸值。
//
//在`onMeasure()`方法中,开发者需要根据测量模式和测量尺寸来计算并设置`TextView`的宽度和高度。通常,可以使用`MeasureSpec.getMode()`方法获取测量模式,然后根据不同的模式进行相应的处理。
//
//例如,当测量模式为`MeasureSpec.EXACTLY`时,可以直接使用`MeasureSpec.getSize()`来设置`TextView`的尺寸;当测量模式为`MeasureSpec.AT_MOST`时,可以根据内容的大小来动态计算尺寸;当测量模式为`MeasureSpec.UNSPECIFIED`时,可以根据自身的需要来设置尺寸。
//
//最后,在`onMeasure()`方法中,使用`setMeasuredDimension()`方法来设置测量后的宽度和高度,以便系统可以根据这些尺寸来绘制和布局`TextView`。
// 需要根据文本测量高度
int widthMode, heightMode;
int width = 0, height = 0;
indexList.clear();
widthMode = MeasureSpec.getMode(widthMeasureSpec);
heightMode = MeasureSpec.getMode(heightMeasureSpec);
//计算宽度
if (widthMode == MeasureSpec.EXACTLY) {
width = MeasureSpec.getSize(widthMeasureSpec);
Log.e("MyTest", "exactly " + width);
} else if (widthMode == MeasureSpec.AT_MOST) {
width = 700;
Log.e("MyTest", "at_most " + width);
} else {
width = 700;
Log.e("MyTest", "unspecified " + width);
}
//计算高度
if (heightMode == MeasureSpec.EXACTLY) {
height = MeasureSpec.getSize(heightMeasureSpec);
} else if (heightMode == MeasureSpec.AT_MOST) {
if (textPaint != null) {
if (pinyin != null && pinyin.length != 0) {
height = (int) ((pinyin.length / 10 + 1) * 1.6 * (fontMetrics.bottom - fontMetrics.top) + paddingTop);
//设置height为行数*行高+paddingTop
} else if (hanzi != null) {
height = (int) ((fontMetrics.bottom - fontMetrics.top) + paddingTop);
//设置height为行高+paddingTop
}
}
} else {
//如果为 MeasureSpec.UNSPECIFIED
if (textPaint != null) {
if (pinyin != null && pinyin.length != 0) {
float pinyinWidth = 0;
int lineCount = 1;
for (int index = 0; index < pinyin.length; index++) {
if (TextUtils.equals(pinyin[index], "null")) {
pinyinWidth = pinyinWidth + textPaint.measureText(hanzi[index]);
} else {
pinyinWidth = pinyinWidth + textPaint.measureText(pinyin[index]);
}
if (pinyinWidth > width) {
indexList.add(index);
lineCount++;
pinyinWidth = textPaint.measureText(pinyin[index]);
}
}
height = (int) Math.ceil((lineCount * 2) * (textPaint.getFontSpacing() + density * 1));
//向上取整 getFontSpacing() 返回以像素为单位的行间距
} else if (hanzi != null) {
height = (int) textPaint.getFontSpacing();
}
}
}
height = Math.max(height, minHeight);
setMeasuredDimension(width, height);
//`onMeasure()` 方法是用来测量视图的宽度和高度的。在自定义 `TextView` 中,您可以根据自己的需求重写 `onMeasure()` 方法,并在其中调用 `setMeasuredDimension(width, height)` 来设置测量后的宽度和高度。
}
private float pinyinWidth = 0;
private boolean isCenterHorizontal = false;
@Override
protected void onDraw(Canvas canvas) {
//keyword
keyWordIndex = getKeyWordIndex(keyWord, hanzi);
float widthMeasure = 0f;
if (indexList.isEmpty()) {
// 单行数据处理
if (isCenterHorizontal) {
if (pinyin != null && pinyin.length > 0) {
widthMeasure = (getWidth() - textPaint.measureText(combinePinyinEnd(0, pinyin.length))) / 2;
//得到剩余宽度的一半
Log.e("MyTest", "widthMeasure1: " + widthMeasure);
} else if (hanzi != null && hanzi.length > 0) {
widthMeasure = (getWidth() - textPaint.measureText(combineHanziEnd(0, hanzi.length))) / 2;
}
}
}
pinyinWidth = 0;
line = 1;
int trimIndex = 0;
if (pinyin != null && pinyin.length > 0) {
for (int index = 0; index < pinyin.length; index++) {
if (!TextUtils.equals(pinyin[index], "null") && !TextUtils.equals(pinyin[index], " ")) {
pinyinWidth = widthMeasure + textPaint.measureText(pinyin[index]);
if (pinyinWidth > getWidth()) {
line++;
widthMeasure = 0;
trimIndex = index;
}
Log.e("MyTest", "widthMeasure2: " + index + widthMeasure);
if (keyWordIndex != -1 && keyWord != null) {
if (keyWordIndex <= index && index <= keyWordIndex + keyWord.length() - 1) {
textPaint.setColor(keyWordColor);
} else {
textPaint.setColor(color);
}
}
if (index == trimIndex) {
String trimmed = pinyin[index].replaceFirst("^\\s+", "");
canvas.drawText(trimmed, widthMeasure, (line * 2 - 1) * (textPaint.getFontSpacing()), textPaint);
canvas.drawText(hanzi[index], widthMeasure + (textPaint.measureText(trimmed) - textPaint.measureText(hanzi[index])) / 2 - moveHalfIfNeed(trimmed, textPaint), (line * 2) * (textPaint.getFontSpacing()), textPaint);
widthMeasure = widthMeasure + textPaint.measureText(trimmed);
} else {
canvas.drawText(pinyin[index], widthMeasure, (line * 2 - 1) * (textPaint.getFontSpacing()), textPaint);
canvas.drawText(hanzi[index], widthMeasure + (textPaint.measureText(pinyin[index]) - textPaint.measureText(hanzi[index])) / 2 - moveHalfIfNeed(pinyin[index], textPaint), (line * 2) * (textPaint.getFontSpacing()), textPaint);
widthMeasure = widthMeasure + textPaint.measureText(pinyin[index]);
}
} else if (TextUtils.equals(pinyin[index], "null")) {
float hanziWidth = widthMeasure + textPaint.measureText(hanzi[index]);
if (hanziWidth > getWidth()) {
line++;
widthMeasure = 0;
}
canvas.drawText(hanzi[index], widthMeasure, (line * 2) * textPaint.getFontSpacing(), textPaint);
widthMeasure = widthMeasure + textPaint.measureText(hanzi[index]);
}
}
}
super.onDraw(canvas);
}
private float moveHalfIfNeed(String pinyinUnit, TextPaint paint) {
if (pinyinUnit.trim().length() % 2 == 0) {
return paint.measureText(" ") / 2;
} else {
return 0;
}
}
private String combinePinyinEnd(int index, int length) {
StringBuilder sb = new StringBuilder();
for (int subIndex = index; subIndex < length; subIndex++) {
String pendString = pinyin[subIndex];
sb.append(pendString);
}
return sb.toString();
}
private String combineHanziEnd(int index, int length) {
StringBuilder sb = new StringBuilder();
for (int subIndex = index; subIndex < length; subIndex++) {
sb.append(hanzi[subIndex]);
}
return sb.toString();
}
public static String[] splitHanziString(String str) {
ArrayList<String> arrayList = new ArrayList<>();
int i = 0;
while (i < str.length()) {
char c = str.charAt(i);
if (Character.isDigit(c) || (Character.isLetter(c) && Character.UnicodeBlock.of(c) == Character.UnicodeBlock.BASIC_LATIN)) {
StringBuilder sb = new StringBuilder();
sb.append(c);
while (i + 1 < str.length()) {
char nextChar = str.charAt(i + 1);
if ((Character.isDigit(c) && Character.isDigit(nextChar)) || ((Character.isLetter(c) && Character.UnicodeBlock.of(c) == Character.UnicodeBlock.BASIC_LATIN) && (Character.isLetter(nextChar) && Character.UnicodeBlock.of(nextChar) == Character.UnicodeBlock.BASIC_LATIN))) {
sb.append(nextChar);
i++;
} else {
break;
}
}
arrayList.add(sb.toString());
} else {
arrayList.add(String.valueOf(c));
}
i++;
}
return arrayList.toArray(new String[0]);
}
public static String[] splitPinyin(String str) {
String[] s = str.trim().split(" ");
ArrayList<String> arrayList = new ArrayList<>();
for (String s1 : s) {
if (!s1.equals(" ") && !s1.equals("")) {
arrayList.add(s1);
}
}
return arrayList.toArray(new String[0]);
}
public static String[] getFormattedPinyin(String pinyin, String[] hanzi) {
String[] splitPinyin = splitPinyin(pinyin);
ArrayList<String> newPinyin = new ArrayList<>();
ArrayList<String> formattedPinyin = new ArrayList<>();
int i = 0;
for (String item : hanzi) {
if (isChineseCharacter(item)) {
newPinyin.add(splitPinyin[i]);
i++;
} else {
newPinyin.add("null");
}
}
for (String s : newPinyin) {
if (!s.equals("null")) {
formattedPinyin.add(formatUnit(s));
} else {
formattedPinyin.add("null");
}
}
return formattedPinyin.toArray(new String[0]);
}
private static boolean isChineseCharacter(String str) {
if (str.length() == 1) {
char c = str.charAt(0);
return isChineseChar(c);
}
return false;
}
private static boolean isChineseChar(char c) {
Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
return block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|| block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
|| block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
|| block == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
|| block == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT;
}
private static String formatUnit(String unit) {
String result = unit;
switch (unit.length()) {
case 1:
result = " " + result + " ";
break;
case 2:
result = " " + result + " ";
break;
case 3:
result = " " + result + " ";
break;
case 4:
result = " " + result + " ";
break;
case 5:
result = result + " ";
break;
}
return result;
}
private static int getKeyWordIndex(String keyword, String[] hanzi) {
for (int i = 0; i < hanzi.length; i++) {
int flag = 0;
for (int j = 0; j < keyword.length(); j++) {
if (i + j < hanzi.length) {
if (hanzi[i + j].equals(String.valueOf(keyword.charAt(j)))) {
flag++;
}
}
}
if (flag == keyword.length()) {
return i;
}
}
return -1;
}
}
java 设置代码
String text = "一年有365天,我在IT行业工作,8乘以8等于64。";
String[] hanzi=PinyinTextView.splitHanziString(text);
String[] pinyin=PinyinTextView.getFormattedPinyin("yī nián yǒu tiān wǒ zài háng yè gōng zuò chéng yǐ děng yú",hanzi);
textView.setHanzi(hanzi);
textView.setColor(Color.rgb(0,255,0));
textView.setFontSize(35);
textView.setPinyin(pinyin);
textView.setKeyWord("行业");
textView.setKeyWordColor(Color.rgb(205,5,255));