最近开发遇到一个下面这种布局结构,关系到Textview的换行,并且第二行与前端图片对齐,后段追加时间的样式。
![image.png](https://upload-images.jianshu.io/upload_images/9625409-e3b9c20ffa0c2b36.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
我们都知道在Android中所有的控件都是矩形的,所以想通过单个Textview实现评论内容换行,并且与前段对齐市无法实现的,那么我们换个思路如果我们能够知道评论内容第一行最多能显示几个字符,那么剩下的就可以通过将内容拆分成两个Textview来实现,而关于后面追加的时间样式可以SpannableStringBuilder对第二个Textview进行修饰。
首先我们来看看怎么获取第一行最多显示多少字符。
xml布局
```
android:layout_width="100dp"
android:maxLines="1"
android:text="dsadsadsasfreqrwqewqewqewqewqewq"
android:layout_height="wrap_content"/>
```
这里我们限制了宽度和最大行数,内容必然会被截断。
kotlin
```
v_text.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
v_text.viewTreeObserver.removeOnGlobalLayoutListener(this)
val layout = v_text.layout
val lineEnd = layout.getLineEnd(0)
}
})
```
layout.getLineEnd(0)返回的就是第一行最后一个字符的位置。
思路有了,那么我们就 使用组合自定义控件方式来实现,以便复用:
布局部分:
```
android:layout_height="wrap_content">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="13dp"
android:layout_marginTop="3dp"
android:layout_marginRight="7dp"
android:background="@color/colorPrimary"
android:drawablePadding="2dp"
android:gravity="center_vertical"
android:paddingLeft="3dp"
android:paddingRight="3dp"
android:text="热评"
android:textColor="#ff333333"
android:textSize="11sp"
android:textStyle="bold" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_icon3" />
android:maxLines="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/v_hot_text" />
android:layout_marginTop="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/v_content_line1" />
```
kotlin部分
```
package com.lucas.test
import android.content.Context
import android.graphics.Color
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.AbsoluteSizeSpan
import android.text.style.ForegroundColorSpan
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView
/**
* @package CommentChildView.kt
* @author luan
* @date 2019-11-06
* @des 评论内容区
*/
class CommentChildView : FrameLayout {
constructor(context: Context) : super(context) {
initView(context)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
initView(context)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
initView(context)
}
var rootView: ViewGroup? = null
var contentStr: String = ""
fun initView(context: Context) {
rootView = LayoutInflater.from(context).inflate(
R.layout.view_comment_child,
null,
false
) as ViewGroup
addView(rootView)
}
fun setContent(content: String) {
contentStr = content
rootView?.apply {
val line1 = findViewById
line1.text = content
//获取剩余未显示的字符串
val lineEnd = line1.layout.getLineEnd(0)
if (content.length == lineEnd) {//第一行已显示完,无需使用第二行显示
} else {//启用第二行显示剩余字符
val substring = content.substring(lineEnd, content.length)
findViewById
}
}
}
fun isHot(isHot: Boolean) {
rootView?.apply {
if (isHot) {
findViewById
findViewById
} else {
findViewById
findViewById
}
}
}
//追加时间样式--调用该方法前需先调用setContent,否则追加时间位置会出错
fun setTime(time: String) {
rootView?.apply {
val line1 = findViewById
val line2 = findViewById
//判断追加第几行
if (line2.text.toString().isEmpty()) {//追加第一行
line1.text = "${line1.text} ${time}"
//如果追加后超出一行,则放弃,改为追加到二行
if (isSingleLine(line1)) {
line1.text = contentStr
initTimeStyle(line2, time)
}else{
line1.text = contentStr
initTimeStyle(line1, time)
}
} else {//追加第二行
line1.text = contentStr
initTimeStyle(line2, time)
}
}
}
private fun initTimeStyle(line2: TextView, time: String) {
line2.text = "${line2.text} $time"
//修改时间样式
val line2Str = line2.text
val timeIndex = line2Str.indexOf(time)
val builder = SpannableStringBuilder(line2Str)
builder.setSpan(
ForegroundColorSpan(Color.parseColor("#999999")),
timeIndex,
timeIndex + time.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
builder.setSpan(
AbsoluteSizeSpan(11, true),
timeIndex,
timeIndex + time.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
line2.text = builder
}
//判断textview是否超出一行
private fun isSingleLine(textView: TextView): Boolean {
val lineEnd = textView.layout.getLineEnd(0)
return textView.text.length > lineEnd
}
}
```
具体运行效果:
![image.png](https://upload-images.jianshu.io/upload_images/9625409-c34d0db076b91b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
其中有点需要注意的是,在Textview没有绘制完成前textview.getLayout方法返回的是空的,使用时需要在合适的时机使用例如:
```
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
android:layout_width="200dp"
android:layout_height="wrap_content"/>
```
```
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
v_comment.viewTreeObserver.addOnGlobalLayoutListener(object :ViewTreeObserver.OnGlobalLayoutListener{
override fun onGlobalLayout() {
v_comment.viewTreeObserver.removeOnGlobalLayoutListener(this)
v_comment.setContent("法规和冯绍峰dasdadasdauguyguihiuh")
v_comment.isHot(true)
v_comment.setTime("12-11")
}
})
}
}
```