android TextView文字换行内容末尾紧跟图标或其他控件的实现

项目中经常会遇到这样的需求:在一段文字后面跟一个图标或者其他效果。如果是单行文字的话很好解决使用简单的布局进行控件组合就能完成,但是如果遇到文字有换行的情况使用简单的布局组合可能实现起来就比较麻烦了甚至是实现不了。那么这个时候我们就需要自定义view来实现,或者重写TextView、或者重写View自己绘制、或者重写ViewGroup来实现。我这里介绍的实现方法就是重写ViewGroup的方式,自定义ViewGroup里子控件的位置来实现。

先看一下下面的效果图:
dis
上面的效果图上有三种效果,第一种是在一段文字后面跟一个有特殊效果的文字,第二种是在文字末尾跟一个图标,第三个是在文字末尾跟一个layout,layout里面包含两个ImageView。

话不多说直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package com.cm.demo;

import android.content.Context;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

/**
* Created by Cmad on 2015/11/17.
*/
public class CustomLayout extends ViewGroup {
//单行显示
private static final int SINGLE_LINE = 0x01;
//多行显示
private static final int MULTI_LINE = 0x02;
//显示到下一行
private static final int NEXT_LINE = 0x03;
//显示样式
private int type;
//绘制文字最后一行的顶部坐标
private int lastLineTop;
//绘制文字最后一行的右边坐标
private float lastLineRight;

public CustomLayout(Context context) {
super(context);
}

public CustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}

public CustomLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int childCount = getChildCount();
int w = MeasureSpec.getSize(widthMeasureSpec);
if (childCount == 2) {
TextView tv = null;
if(getChildAt(0) instanceof TextView){
tv = (TextView) getChildAt(0);
initTextParams(tv.getText(), tv.getMeasuredWidth(), tv.getPaint());
}else{
throw new RuntimeException("CustomLayout first child view not a TextView");
}

View sencodView = getChildAt(1);

//测量子view的宽高
measureChildren(widthMeasureSpec, heightMeasureSpec);

//两个子view宽度相加小于该控件宽度的时候
if (tv.getMeasuredWidth() + sencodView.getMeasuredWidth() <= w) {
int width = tv.getMeasuredWidth()+sencodView.getMeasuredWidth();
//计算高度
int height = Math.max(tv.getMeasuredHeight(), sencodView.getMeasuredHeight());
//设置该viewgroup的宽高
setMeasuredDimension(width, height);
type = SINGLE_LINE;
return;
}
if (getChildAt(0) instanceof TextView) {
//最后一行文字的宽度加上第二个view的宽度大于viewgroup宽度时第二个控件换行显示
if (lastLineRight + sencodView.getMeasuredWidth() > w) {
setMeasuredDimension(tv.getMeasuredWidth(), tv.getMeasuredHeight() + sencodView.getMeasuredHeight());
type = NEXT_LINE;
return;
}

int height = Math.max(tv.getMeasuredHeight(), lastLineTop + sencodView.getMeasuredHeight());
setMeasuredDimension(tv.getMeasuredWidth(), height);
type = MULTI_LINE;
}
} else {
throw new RuntimeException("CustomLayout child count must is 2");
}

}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (type == SINGLE_LINE || type == MULTI_LINE) {
TextView tv = (TextView) getChildAt(0);
View v1 = getChildAt(1);
//设置第二个view在Textview文字末尾位置
tv.layout(0, 0, tv.getMeasuredWidth(), tv.getMeasuredHeight());
int left = (int) lastLineRight;
int top = lastLineTop;
//最后一行的高度 注:通过staticLayout得到的行高不准确故采用这种方式
int lastLineHeight = tv.getBottom()-tv.getPaddingBottom() -lastLineTop;
//当第二view高度小于单行文字高度时竖直居中显示
if(v1.getMeasuredHeight() < lastLineHeight){
top = lastLineTop + (lastLineHeight - v1.getMeasuredHeight())/2;
}
v1.layout(left, top, left + v1.getMeasuredWidth(), top+v1.getMeasuredHeight());
} else if (type == NEXT_LINE) {
View v0 = getChildAt(0);
View v1 = getChildAt(1);
//设置第二个view换行显示
v0.layout(0, 0, v0.getMeasuredWidth(), v0.getMeasuredHeight());
v1.layout(0, v0.getMeasuredHeight(), v1.getMeasuredWidth(), v0.getMeasuredHeight() + v1.getMeasuredHeight());
}
}

/**
* 得到Textview绘制文字的基本信息
* @param text Textview的文字内容
* @param maxWidth Textview的宽度
* @param paint 绘制文字的paint
*/
private void initTextParams(CharSequence text, int maxWidth, TextPaint paint) {
StaticLayout staticLayout = new StaticLayout(text, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
int lineCount = staticLayout.getLineCount();
lastLineTop = staticLayout.getLineTop(lineCount - 1);
lastLineRight = staticLayout.getLineRight(lineCount - 1);
}

}

主要是重写了onMeasure和onLayout方法来确定ViewGroup的高度和子控件的位置,另外用到了StaticLayout来获取TextView里文字的信息,主要是获取TextView最后一行的位置信息来确定第二个view的位置。
使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<com.cm.demo.CustomLayout
android:layout_width="150dp"
android:layout_height="wrap_content"
android:background="#252525">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="android开发android开发android开发"
android:background="#de6565"
android:textSize="15sp">

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ab_search"
android:background="#3e95ec">

</com.cm.demo.CustomLayout>

可以把第二个的ImageView换成任意View包括ViewGroup。
需要注意的是:
1、CustomLayout里只能放两个直接子控件
2、CustomLayout里的第一个控件必须是TextView

如果这不能实现你想要的效果,可以自行修改源码

坚持原创技术分享,您的支持将鼓励我继续创作!