效果图:
代码实现(kotlin版,java 版本在最后):
MainActivity:
package com.tencent.myapplication
import android.annotation.SuppressLint
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Paint.FontMetricsInt
import android.os.Bundle
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ReplacementSpan
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
fun dp2px(dpValue: Float): Int {
val scale = Resources.getSystem().displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
fun view2Bitmap(view: View?): Bitmap? {
if (view == null) return null
val drawingCacheEnabled = view.isDrawingCacheEnabled
val willNotCacheDrawing = view.willNotCacheDrawing()
view.isDrawingCacheEnabled = true
view.setWillNotCacheDrawing(false)
var drawingCache = view.drawingCache
val bitmap: Bitmap
if (null == drawingCache || drawingCache.isRecycled) {
view.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
view.buildDrawingCache()
drawingCache = view.drawingCache
if (null == drawingCache || drawingCache.isRecycled) {
bitmap = Bitmap.createBitmap(
view.measuredWidth,
view.measuredHeight,
Bitmap.Config.RGB_565
)
val canvas = Canvas(bitmap)
view.draw(canvas)
} else {
bitmap = Bitmap.createBitmap(drawingCache)
}
} else {
bitmap = Bitmap.createBitmap(drawingCache)
}
view.setWillNotCacheDrawing(willNotCacheDrawing)
view.isDrawingCacheEnabled = drawingCacheEnabled
return bitmap
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val tagStr = "热门"
val name = "名称特别特别特别特别特别特别特别特别特别特别特别特别特别特别特别特别特别特别特别特别长"
val stateStr = "进行中"
val title = tagStr + name + stateStr
val spannableStr = SpannableString(title)
// 设置 tag。
spannableStr.setSpan(
TagSpan(tagStr),
title.indexOf(tagStr),
title.indexOf(tagStr) + tagStr.length,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
// 设置 state。
spannableStr.setSpan(
StateSpan(stateStr),
title.indexOf(stateStr),
title.indexOf(stateStr) + stateStr.length,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
val tvTitle = findViewById<TextView>(R.id.text_view)
tvTitle.text = spannableStr
}
@SuppressLint("InflateParams")
inner class TagSpan(tagStr: String) : ReplacementSpan() {
private val view: View =
LayoutInflater.from(this@MainActivity).inflate(R.layout.layout_tag, null)
private val marginEnd: Int = dp2px(5f)
private val mWidth: Int
init {
val tvName = view.findViewById<TextView>(R.id.tv_name_tag)
tvName.text = tagStr
val width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
val height = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
view.measure(width, height)
mWidth = view.measuredWidth // 获取宽度
}
override fun getSize(
paint: Paint,
text: CharSequence,
start: Int,
end: Int,
fm: FontMetricsInt?
): Int {
return mWidth + marginEnd
}
override fun draw(
canvas: Canvas,
text: CharSequence,
start: Int,
end: Int,
x: Float,
top: Int,
y: Int, bottom: Int,
paint: Paint
) {
paint.isAntiAlias = true
val bmp: Bitmap? = view2Bitmap(view)
bmp?.let {
canvas.drawBitmap(bmp, x, y + paint.ascent(), paint)
}
}
}
@SuppressLint("InflateParams")
inner class StateSpan(stateStr: String) : ReplacementSpan() {
private val view: TextView = LayoutInflater.from(this@MainActivity)
.inflate(R.layout.layout_state, null) as TextView
private val marginStart: Int = dp2px(5f)
private val mWidth: Int
init {
val tvName = view.findViewById<TextView>(R.id.tv_name_state)
tvName.text = stateStr
val width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
val height = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
view.measure(width, height)
mWidth = view.measuredWidth // 获取宽度
}
override fun getSize(
paint: Paint,
text: CharSequence,
start: Int,
end: Int,
fm: FontMetricsInt?
): Int {
return mWidth + marginStart
}
override fun draw(
canvas: Canvas,
text: CharSequence,
start: Int,
end: Int,
x: Float,
top: Int,
y: Int, bottom: Int,
paint: Paint
) {
paint.isAntiAlias = true
val bmp: Bitmap? = view2Bitmap(view)
bmp?.let {
canvas.drawBitmap(bmp, x + marginStart, y + paint.ascent(), paint)
}
}
}
}
布局文件:
layout_tag:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_tag_bg"
android:paddingHorizontal="6dp"
android:paddingVertical="2dp">
<ImageView
android:id="@+id/iv_icon_tag"
android:layout_width="14dp"
android:layout_height="14dp"
android:src="@drawable/ic_tag_hot"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_name_tag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="热门"
android:textColor="#FFFFFF"
android:textSize="12dp"
app:layout_constraintBottom_toBottomOf="@id/iv_icon_tag"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/iv_icon_tag"
app:layout_constraintTop_toTopOf="@id/iv_icon_tag" />
</androidx.constraintlayout.widget.ConstraintLayout>
layout_state:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_name_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_state_bg"
android:paddingHorizontal="6dp"
android:text="进行中"
android:textColor="#D1291A" />
shape_tag_bg:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:endColor="#D1291A"
android:startColor="#FC3B16" />
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="8dp"
android:topLeftRadius="8dp"
android:topRightRadius="0dp" />
</shape>
shape_state_bg:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="99dp" />
<stroke
android:width="1dp"
android:color="#D1291A" />
</shape>
代码实现(java版),布局文件跟上面一样:
package com.tencent.myapplication;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ReplacementSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity2 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
TextView tvTitle = findViewById(R.id.text_view);
String tagStr = "热门";
String name = "名称特别特别特别特别特别特别特别特别特别特别特别特别特别特别特别特别特别特别特别特别长";
String stateStr = "进行中";
String title = tagStr + name + stateStr;
SpannableString spannableStr = new SpannableString(title);
// 设置 tag。
spannableStr.setSpan(new TagSpan(tagStr), title.indexOf(tagStr), title.indexOf(tagStr) + tagStr.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
// 设置 state。
spannableStr.setSpan(new StateSpan(stateStr), title.indexOf(stateStr), title.indexOf(stateStr) + stateStr.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
tvTitle.setText(spannableStr);
}
public class TagSpan extends ReplacementSpan {
private final View view;
private final int marginEnd = dp2px(5);
private final int mWidth;
public TagSpan(String tagStr) {
view = LayoutInflater.from(MainActivity2.this).inflate(R.layout.layout_tag, null);
TextView tvName = view.findViewById(R.id.tv_name_tag);
tvName.setText(tagStr);
int width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int height = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(width, height);
mWidth = view.getMeasuredWidth(); // 获取宽度
}
@Override
public int getSize(@NonNull Paint paint,
CharSequence text,
int start,
int end,
Paint.FontMetricsInt fm) {
return mWidth + marginEnd;
}
@Override
public void draw(@NonNull Canvas canvas,
CharSequence text,
int start,
int end,
float x,
int top,
int y, int bottom,
@NonNull Paint paint) {
paint.setAntiAlias(true);
Bitmap bmp = view2Bitmap(view);
canvas.drawBitmap(bmp, x, y + paint.ascent(), paint);
}
}
public class StateSpan extends ReplacementSpan {
private final TextView view;
private final int marginStart = dp2px(5);
private final int mWidth;
public StateSpan(String stateStr) {
view = (TextView) LayoutInflater.from(MainActivity2.this).inflate(R.layout.layout_state, null);
view.setText(stateStr);
int width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int height = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(width, height);
mWidth = view.getMeasuredWidth(); // 获取宽度
}
@Override
public int getSize(@NonNull Paint paint,
CharSequence text,
int start,
int end,
Paint.FontMetricsInt fm) {
return mWidth + marginStart;
}
@Override
public void draw(@NonNull Canvas canvas,
CharSequence text,
int start,
int end,
float x,
int top,
int y, int bottom,
@NonNull Paint paint) {
paint.setAntiAlias(true);
Bitmap bmp = view2Bitmap(view);
canvas.drawBitmap(bmp, x + marginStart, y + paint.ascent(), paint);
}
}
public static int dp2px(final float dpValue) {
final float scale = Resources.getSystem().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
public static Bitmap view2Bitmap(final View view) {
if (view == null) {
return null;
}
boolean drawingCacheEnabled = view.isDrawingCacheEnabled();
boolean willNotCacheDrawing = view.willNotCacheDrawing();
view.setDrawingCacheEnabled(true);
view.setWillNotCacheDrawing(false);
Bitmap drawingCache = view.getDrawingCache();
Bitmap bitmap;
if (null == drawingCache || drawingCache.isRecycled()) {
view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
view.buildDrawingCache();
drawingCache = view.getDrawingCache();
if (null == drawingCache || drawingCache.isRecycled()) {
bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
} else {
bitmap = Bitmap.createBitmap(drawingCache);
}
} else {
bitmap = Bitmap.createBitmap(drawingCache);
}
view.setWillNotCacheDrawing(willNotCacheDrawing);
view.setDrawingCacheEnabled(drawingCacheEnabled);
return bitmap;
}
}