这个自定义View可以实现文字带拼音,根据当前VIew长度自动换行,实现一句带拼音文本的多行显示,如果是诗词或者其他多段落场景,可以结合RecyclerView,此View作为一个Item显示一句文本
public class PYTextWrapView extends ViewGroup {
private static final String TAG = PYTextWrapView.class.getName();
//子view水平方向padding
private int mPaddingHor = 0;
//子view垂直方向padding
private int mPaddingVer = 20;
//子view之间的水平间距
private int mMarginHor = 0;
//行间距
private int mMarginVer = 10;
//最多字个数
private int mNum = 0;
private final Context mContext;
public PYTextWrapView(Context context) {
super(context);
mContext = context;
}
public PYTextWrapView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttrs(context, attrs);
mContext = context;
}
public PYTextWrapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initAttrs(context, attrs);
mContext = context;
}
//获取属性值
@SuppressLint("CustomViewStyleable")
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.WordWrapView);
mPaddingHor = (int) ta.getDimension(R.styleable.WordWrapView_padding_hor, mPaddingHor);
mPaddingVer = (int) ta.getDimension(R.styleable.WordWrapView_padding_vertical, mPaddingVer);
mMarginHor = (int) ta.getDimension(R.styleable.WordWrapView_margin_hor, mMarginHor);
mMarginVer = (int) ta.getDimension(R.styleable.WordWrapView_margin_vertial, mMarginVer);
ta.recycle();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
int realWidth = r - l;//实际宽度
int x = 0;
// 每一行的宽度
HashMap<Integer, Integer> lineWidthMap = getAllLineWidth(realWidth);
int y;
int rows = 1;
// 初始化x时,计算左间距
if (lineWidthMap.get(rows) != null) {
x = (realWidth - lineWidthMap.get(rows)) / 2;
}
//判断累积高度
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
//超出宽度或文字有换行符则换行
TextView tvText = view.findViewById(R.id.tvText);
boolean isLineFeed = tvText != null && tvText.getText().toString().equals("\n");
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
//不计算换行符的宽度
if (!isLineFeed) {
x += width + mMarginHor;
}
if (isLineFeed || x > realWidth - mMarginHor) {
if (i != 0) {
x = width + mMarginHor;
rows++;
}
}
//当一个子view长度超出父view长度时
if (x > realWidth - mMarginHor) {
if (view instanceof TextView) {
//判断单个高度
TextView tv = (TextView) view;
if (mNum == 0) {
int wordNum = tv.getText().toString().length();
mNum = wordNum * (realWidth - 2 * mMarginHor - 2 * mPaddingHor) / (width - 2 * mPaddingHor) - 1;
}
String text = tv.getText().toString();
text = text.substring(0, mNum) + "...";
tv.setText(text);
}
x = realWidth - mMarginHor;
width = realWidth - 2 * mMarginHor;
}
y = rows * (height + mMarginVer);
//有换行符时,重置x位置
if (isLineFeed) {
// 初始化x时,计算左间距
if (lineWidthMap.get(rows) != null) {
x = (realWidth - lineWidthMap.get(rows)) / 2;
}
view.layout(x, y - height, x, y);
} else {
view.layout(x - width, y - height, x, y);
}
}
}
/**
* 获取所有行的宽度
*/
private HashMap<Integer, Integer> getAllLineWidth(int realWidth) {
HashMap<Integer, Integer> widthMap = new HashMap();
try {
int maxWidth = 0;
int childCount = getChildCount();
int x = 0;
int y;
int rows = 1;
//判断累积高度
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
//超出宽度或文字有换行符则换行
TextView tvText = view.findViewById(R.id.tvText);
boolean isLineFeed = tvText != null && tvText.getText().toString().equals("\n");
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
//不计算换行符的宽度
if (!isLineFeed) {
x += width + mMarginHor;
maxWidth = Math.max(x, maxWidth);
widthMap.put(rows, maxWidth);
}
if (isLineFeed || x > realWidth - mMarginHor) {
if (i != 0) {
x = width + mMarginHor;
rows++;
}
}
//当一个子view长度超出父view长度时
if (x > realWidth - mMarginHor) {
if (view instanceof TextView) {
//判断单个高度
TextView tv = (TextView) view;
if (mNum == 0) {
int wordNum = tv.getText().toString().length();
mNum = wordNum * (realWidth - 2 * mMarginHor - 2 * mPaddingHor) / (width - 2 * mPaddingHor) - 1;
}
String text = tv.getText().toString();
text = text.substring(0, mNum) + "...";
tv.setText(text);
}
x = realWidth - mMarginHor;
width = realWidth - 2 * mMarginHor;
}
y = rows * (height + mMarginVer);
//有换行符时,重置x位置
if (isLineFeed) {
x = 0;
maxWidth = 0;
view.layout(x, y - height, x, y);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return widthMap;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//所有行最大的宽度
int maxWidth = 0;
int x = 0;
int y = 0;
int rows = 1;
// 控件最大宽度
int realWith = MeasureSpec.getSize(widthMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
child.setPadding(mPaddingHor, mPaddingVer, mPaddingHor, mPaddingVer);
child.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
try {
TextView tvText = child.findViewById(R.id.tvText);
//超出宽度或文字有换行符则换行
boolean isLineFeed = tvText != null && tvText.getText().toString().equals("\n");
//不计算换行符的宽度
if (!isLineFeed) {
x += width + mMarginHor;
}
//判断一下最大宽度
maxWidth = Math.max(x, maxWidth);
//最后一行时不需要换行
if ((isLineFeed && i != childCount - 1) || x > realWith - mMarginHor) {
if (i != 0) {
x = 0;
rows++;
}
}
y = rows * (height + mMarginVer);
} catch (Exception e) {
Logger.e(TAG, e.getMessage());
}
}
if (maxWidth > realWith) {
setMeasuredDimension(realWith, y + mMarginVer);
} else {
realWith = maxWidth + mMarginHor;
setMeasuredDimension(realWith, y + mMarginVer);
}
}
@SuppressLint("InflateParams")
public void setData(List<PYTextWrapper> pyTextWrapperList) {
if (pyTextWrapperList == null || pyTextWrapperList.isEmpty()) {
return;
}
removeAllViews();
//整句是否无拼音 解决无拼音时高度问题
boolean hasNoPy = true;
for (PYTextWrapper pyTextWrapper : pyTextWrapperList) {
if (!TextUtils.isEmpty(pyTextWrapper.mPY)) {
hasNoPy = false;
}
}
for (PYTextWrapper pyTextWrapper : pyTextWrapperList) {
View itemPYText = LayoutInflater.from(mContext).inflate(R.layout.item_pic_recite_ping_ying_text, null);
setPYData(itemPYText, pyTextWrapper, hasNoPy);
addView(itemPYText);
}
}
private void setPYData(View view, PYTextWrapper pyTextWrapper, Boolean hasNoPy) {
TextView pyText = view.findViewById(R.id.tvPY);
TextView tvText = view.findViewById(R.id.tvText);
Typeface typeFace = Typeface.createFromAsset(UIUtils.getAppContext().getAssets(), "fonts/cn_py.ttf");
pyText.setTypeface(typeFace);
pyText.setText(pyTextWrapper.mPY);
tvText.setText(pyTextWrapper.mText);
if (hasNoPy && TextUtils.isEmpty(pyTextWrapper.mPY)) {
pyText.setVisibility(GONE);
} else {
pyText.setVisibility(VISIBLE);
}
pyText.setTextColor(UIUtils.getColor(mTextColor));
tvText.setTextColor(UIUtils.getColor(mTextColor));
}
private int mTextColor = R.color.color_555555;
/**
* 设置文字颜色
*/
public void setTextColor(int textColor) {
mTextColor = textColor;
}
}
涉及的布局 item_pic_recite_ping_ying_text
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tvPY"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:minWidth="30dp"
android:textColor="@color/color_888888"
android:textSize="9sp" />
<TextView
android:id="@+id/tvText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="0.5dp"
android:gravity="center"
android:minWidth="30dp"
android:textColor="@color/color_888888"
android:textSize="15sp" />
</LinearLayout>
数据类的定义
public class PYTextWrapper implements Serializable {
//拼音
public String mPY;
//文字
public String mText;
public PYTextWrapper(String mPY, String mText) {
this.mPY = mPY;
this.mText = mText;
}
}