TextView 實現(xiàn)跟隨標(biāo)簽
一、前言
在設(shè)計中經(jīng)常出現(xiàn)一個長度不確定的文本,后面或是前面跟隨一個或是多個標(biāo)簽。在網(wǎng)上查了很多資料,大部分的實現(xiàn)方式都是通過富文本來實現(xiàn),不能滿足多變的要求,標(biāo)簽要可以是圖片,還可以是不同的文字,或者是一個布局,有時會加多個標(biāo)簽。后來想到了一種解決方法。將整個部分拆分成多個布局,自由組合,可以滿足各種需求,無論標(biāo)簽有多少個,放在前面還是后面都可以實現(xiàn)。
二、具體實現(xiàn)
2.1 文字限制一行時,標(biāo)簽在文字后面
效果圖:
文字較少時就像第一行這樣,文字較多顯示不下時就像二三行那樣省略。

- 實現(xiàn)方法一,使用線性布局實現(xiàn)
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="不寬度,不確定字?jǐn)?shù)"
android:singleLine="true"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跟隨標(biāo)簽"
android:background="@color/yellow_FF9B52"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跟隨標(biāo)簽2"
android:background="@color/blue_74D3FF"/>
</LinearLayout>
- 實現(xiàn)方法二,使用約束布局實現(xiàn)
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/refund_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/refund_mark_num"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/refund_mark_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/yellow_FF9B52"
android:text="跟隨標(biāo)簽"
android:gravity="center"
app:layout_constrainedWidth="true"
app:layout_constraintLeft_toRightOf="@+id/refund_name"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
2.2 文字多行有限制或者不限制行數(shù),標(biāo)簽在文字后面
效果圖:
- 文字較少,只夠一行時效果:

- 文字顯示一行半時效果

- 文字兩行顯示不下時

- 實現(xiàn)思路:
以上面效果圖為例,上面限制文字顯示兩行,第一行是一個單獨(dú)的 TextView,第二行就是上面2.1 里面一行文字時實現(xiàn)的效果。當(dāng)文字較少時,只顯示一行,第一行的 TextView 隱藏即可。當(dāng)文字超過一行少于兩行時或者超過兩行時,第一行的 TextView 設(shè)置顯示一行就行,第二行的 TextView 設(shè)置顯示剩下的文字內(nèi)容。
- 具體的實現(xiàn)代碼如下:
xml 文件:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="限制兩行,后面跟隨標(biāo)簽"
android:textColor="@color/white"
android:background="@color/red"
/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/m15">
<TextView
android:id="@+id/tv_rl_test_tagOne"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)"
android:maxLines="1"
android:textSize="13dp"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="@+id/tv_rl_test_tagTwo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)長數(shù)據(jù)"
app:layout_constrainedWidth="true"
android:textSize="13dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/tv_rl_test_twoTag"
app:layout_constraintTop_toBottomOf="@+id/tv_rl_test_tagOne" />
<TextView
android:id="@+id/tv_rl_test_twoTag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/yellow_FF9B52"
android:layout_marginLeft="3dp"
android:layout_marginTop="2dp"
android:gravity="center"
android:paddingLeft="3dp"
android:paddingTop="1dp"
android:paddingRight="3dp"
android:paddingBottom="1dp"
android:text="標(biāo)簽"
android:textSize="12dp"
app:layout_constrainedWidth="true"
app:layout_constraintLeft_toRightOf="@+id/tv_rl_test_tagTwo"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_rl_test_tagOne" />
</androidx.constraintlayout.widget.ConstraintLayout>
activity 部分:
//要顯示的數(shù)據(jù)源
String tagSrc = "文字是人類用表意符號記錄表達(dá)信息以傳之久遠(yuǎn)的方式和工具。現(xiàn)代文字大多是記錄語言的工具。人類往往先有口頭的語言后產(chǎn)生書面文字,很多小語種,有語言但沒有文字。文字的不同體現(xiàn)了國家和民族的書面表達(dá)的方式和思維不同。文字使人類進(jìn)入有歷史記錄的文明社會。";
TextView tvTagOnes = (TextView) findViewById(R.id.tv_rl_test_tagOne);
TextView tvTagTwo = (TextView) findViewById(R.id.tv_rl_test_tagTwo);
tvTagOnes.setText(tagSrc);
tvTestOne.setText(tagSrc);
//判斷第一行是否可以完整顯示
tvTagOnes.post(new Runnable() {
@Override
public void run() {
//獲取第一個 textview 顯示的內(nèi)容
String lineContent = Utils.INSTANCE.getTextLineContent(tvTagOnes, 0, tagSrc);
LogUtils.e(tagSrc + "---------" + lineContent);
if (TextUtils.equals(tagSrc,lineContent)){//可以顯示完整
tvTagOnes.setVisibility(View.GONE);
tvTagTwo.setText(tagSrc);
}else {//顯示不完整,需要分行
tvTagOnes.setVisibility(View.VISIBLE);
String srcTwoContent = tagSrc.substring(lineContent.length(), tagSrc.length());
tvTagTwo.setText(srcTwoContent);
}
}
});
獲取 TextView 顯示的內(nèi)容:
/**
* 獲取textview某行內(nèi)容
*/
fun getTextLineContent(textView: TextView?, line: Int, src: String?): String {
var result: String = ""
if (textView == null || src.isNullOrEmpty()) {
return result
}
LogUtils.e("$line--line-->${textView.lineCount}" )
if (line > textView.lineCount) {
return result
}
val layout = textView.layout
val sb = StringBuilder(src)
LogUtils.e("--start-${layout.getLineStart(line)}----end---${layout.getLineEnd(line)}")
return sb.subSequence(layout.getLineStart(line), layout.getLineEnd(line)).toString()
}
2.3 標(biāo)簽在文字前面時
效果圖:

實現(xiàn)思路和上面標(biāo)簽在文字后面一樣。第一行的標(biāo)簽是一個控件,標(biāo)簽后面的文字是一個單獨(dú)的 TextView,第二行也是一個單獨(dú)的 TextView。如果想限制文字行數(shù),直接對第二行的 TextView 限制就行。
xml 布局:
<!-- 前面跟隨標(biāo)簽-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="前面跟隨標(biāo)簽"
android:textColor="@color/white"
android:background="@color/red"
/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/m15">
<TextView
android:id="@+id/tv_rl_test_tagFront"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="標(biāo)簽"
android:background="@color/yellow_FF9B52"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<com.kiwilss.xview.widget.textview.AlignTextView
android:id="@+id/tv_rl_test_frontOne"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="任意顯示一行任意顯示一行任意顯示一行任意顯示一行任意顯示一行"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/tv_rl_test_tagFront"
android:maxLines="1"
/>
<com.kiwilss.xview.widget.textview.AlignTextView
android:id="@+id/tv_rl_test_frontTwo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="任意顯示一行任意顯示一行任意顯示一行任意顯示一行任意顯示一行"
app:layout_constraintTop_toBottomOf="@+id/tv_rl_test_tagFront"/>
</androidx.constraintlayout.widget.ConstraintLayout>
activity 部分:
//前面加標(biāo)簽
TextView tvFront = (TextView) findViewById(R.id.tv_rl_test_tagFront);
TextView tvFrontOne = findViewById(R.id.tv_rl_test_frontOne);
TextView tvFrontTwo = findViewById(R.id.tv_rl_test_frontTwo);
tvFrontOne.setText(tagSrc);
//獲取tvFrontOne顯示的內(nèi)容
tvFrontOne.post(new Runnable() {
@Override
public void run() {
//獲取第一行顯示的內(nèi)容
String lineContent = Utils.INSTANCE.getTextLineContent(tvFrontOne, 0, tagSrc);
if (TextUtils.equals(lineContent,tagSrc)){
//一行可以完整顯示
tvFrontTwo.setVisibility(View.GONE);
}else {
//需要多行才能顯示
tvFrontTwo.setVisibility(View.VISIBLE);
String nextContent = tagSrc.substring(lineContent.length(), tagSrc.length());
tvFrontTwo.setText(nextContent);
}
}
});
三、解決對齊問題

如圖,圖中第一行和第二行并沒有對齊,使用 TextView 時,當(dāng)文字顯示不行時就會自動換行,所以會無法對齊,不對齊就會不夠美觀。解決方法是讓文字兩端對齊。如下圖:

網(wǎng)上有很多方法可以實現(xiàn)文字兩端對齊,這里介紹一種自定義 View 的方法,感覺還可以,中英文混合也可以對齊:
AlignTextView:
public class AlignTextView extends AppCompatTextView {
private boolean alignOnlyOneLine;
public AlignTextView(Context context) {
this(context, null);
}
public AlignTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AlignTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AlignTextView);
alignOnlyOneLine = typedArray.getBoolean(R.styleable.AlignTextView_alignOnlyOneLine, false);
typedArray.recycle();
setTextColor(getCurrentTextColor());
}
@Override
public void setTextColor(int color) {
super.setTextColor(color);
getPaint().setColor(color);
}
protected void onDraw(Canvas canvas) {
CharSequence content = getText();
if (!(content instanceof String)) {
super.onDraw(canvas);
return;
}
String text = (String) content;
Layout layout = getLayout();
for (int i = 0; i < layout.getLineCount(); ++i) {
int lineBaseline = layout.getLineBaseline(i) + getPaddingTop();
int lineStart = layout.getLineStart(i);
int lineEnd = layout.getLineEnd(i);
if (alignOnlyOneLine && layout.getLineCount() == 1) {//只有一行
String line = text.substring(lineStart, lineEnd);
float width = StaticLayout.getDesiredWidth(text, lineStart, lineEnd, getPaint());
this.drawScaledText(canvas, line, lineBaseline, width);
} else if (i == layout.getLineCount() - 1) {//最后一行
canvas.drawText(text.substring(lineStart), getPaddingLeft(), lineBaseline, getPaint());
break;
} else {//中間行
String line = text.substring(lineStart, lineEnd);
float width = StaticLayout.getDesiredWidth(text, lineStart, lineEnd, getPaint());
this.drawScaledText(canvas, line, lineBaseline, width);
}
}
}
private void drawScaledText(Canvas canvas, String line, float baseLineY, float lineWidth) {
if (line.length() < 1) {
return;
}
float x = getPaddingLeft();
boolean forceNextLine = line.charAt(line.length() - 1) == 10;
int length = line.length() - 1;
if (forceNextLine || length == 0) {
canvas.drawText(line, x, baseLineY, getPaint());
return;
}
float d = (getMeasuredWidth() - lineWidth - getPaddingLeft() - getPaddingRight()) / length;
for (int i = 0; i < line.length(); ++i) {
String c = String.valueOf(line.charAt(i));
float cw = StaticLayout.getDesiredWidth(c, this.getPaint());
canvas.drawText(c, x, baseLineY, this.getPaint());
x += cw + d;
}
}
}
attrs:
顯示一行時使用,感覺沒什么用處。
<declare-styleable name="AlignTextView">
<attr name="alignOnlyOneLine" format="boolean"/>
</declare-styleable>
四、其他實現(xiàn)方式
查找了很多資料,網(wǎng)上也有其他的實現(xiàn)方式,各有優(yōu)缺點(diǎn),可以用來參考。
android TextView添加標(biāo)簽,一個或多個
TextView多行文字超出時如何在省略號后添加圖標(biāo)