这真的不能使用简单的 Canvas 内置形状绘制方法来完成,因为您需要绘制凹角弧。但是,您可以使用Path 原始操作来构建所需的形状。
您可以创建一条围绕文本的路径是可以想象的,但我发现为每条线创建单独的路径并决定是否需要像普通圆角矩形(即凸形,正半径)一样转角还是转角更简单out(凹面,负半径)取决于线的边界与上方和下方的对齐方式。
我在下面提供了一个示例,说明如何将其作为派生 TextView 类的完整实现来完成。
关于此实现的一些额外注意事项:
生成路径的主要工作在onSizeChanged() 方法中完成:在draw() 方法中进行大量计算和/或分配是不好的做法。 draw() 方法只是在画布上绘制预先计算好的路径。
棘手的一点在于Path roundedRect() 方法,它为每一行创建路径。 RectF 参数是矩形的形状,如果所有角的半径都为零,每个 float 参数分别给出左上角、右上角、左下角和右下角中每个角的半径。正半径表示正常的圆角,负半径表示转出的凹角,零表示没有圆角的直角。
还可以添加自定义属性来控制自定义小部件的外观。我已经在示例attrs.xml 和小部件构造函数中展示了这一点,以指定要使用的最大角半径。您可能希望添加其他人来控制颜色、边距等,而不是像我在示例中所做的那样对它们进行硬编码。
我希望这会有所帮助。
BgColourTextView.java:
package com.example.bgcolourtextview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.widget.TextView;
import java.util.ArrayList;
public class BgColourTextView extends TextView
{
private static final float DEFAULT_MAX_CORNER_RADIUS_DIP = 10f;
private final Paint mPaint;
private final ArrayList<Path> mOutlines;
private final float mMaxCornerRadius;
public BgColourTextView(Context context) {
this(context, null, 0);
}
public BgColourTextView(Context context, AttributeSet attr) {
this(context, attr, 0);
}
public BgColourTextView(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mOutlines = new ArrayList<>();
TypedArray ta = context.getTheme().obtainStyledAttributes(attr, R.styleable.BgColourTextView, 0, defStyle);
try {
mMaxCornerRadius = ta.getDimension(R.styleable.BgColourTextView_maxCornerRadius,
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_MAX_CORNER_RADIUS_DIP,
context.getResources().getDisplayMetrics()));
} finally {
ta.recycle();
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mOutlines.clear();
final ArrayList<RectF> lineBounds = new ArrayList<>();
for (int i = 0; i < getLayout().getLineCount(); i++) {
RectF rect = new RectF(getLayout().getLineLeft(i), getLayout().getLineTop(i), getLayout().getLineRight(i), getLayout().getLineBottom(i));
rect.offset(getPaddingLeft(), getPaddingTop());
rect.inset(-getPaddingLeft() / 2f, 0f);
lineBounds.add(rect);
}
for (int i = 0; i < lineBounds.size(); i++) {
float rTl = limitRadius(i == 0 ? lineBounds.get(i).width() / 2f : (lineBounds.get(i - 1).left - lineBounds.get(i).left) / 2f);
float rTr = limitRadius(i == 0 ? lineBounds.get(i).width() / 2f : (lineBounds.get(i).right - lineBounds.get(i - 1).right) / 2f);
float rBl = limitRadius(i == lineBounds.size() - 1 ? lineBounds.get(i).width() / 2f : (lineBounds.get(i + 1).left - lineBounds.get(i).left) / 2f);
float rBr = limitRadius(i == lineBounds.size() - 1 ? lineBounds.get(i).width() / 2f : (lineBounds.get(i).right - lineBounds.get(i + 1).right) / 2f);
mOutlines.add(roundedRect(lineBounds.get(i), rTl, rTr, rBl, rBr));
}
}
@Override
public void draw(Canvas canvas) {
for (Path p: mOutlines) {
canvas.drawPath(p, mPaint);
}
super.draw(canvas);
}
/**
* Limit the corner radius to a maximum absolute value.
*/
private float limitRadius(float aRadius) {
return Math.abs(aRadius) < mMaxCornerRadius ? aRadius : mMaxCornerRadius * aRadius / Math.abs(aRadius);
}
/**
* Generate a rectangular path with rounded corners, where a positive corner radius indicates a normal convex corner and
* negative indicates a concave corner (turning out horizontally rather than round).
*/
private Path roundedRect(RectF aRect, float rTl, float rTr, float rBl, float rBr) {
Log.d("BgColourTextView", String.format("roundedRect(%s, %s, %s, %s, %s)", aRect, rTl, rTr, rBl, rBr));
Path path = new Path();
path.moveTo(aRect.right, aRect.top + Math.abs(rTr));
if (rTr > 0) {
path.arcTo(new RectF(aRect.right - 2 * rTr, aRect.top, aRect.right, aRect.top + 2 * rTr), 0, -90, false);
} else if (rTr < 0) {
path.arcTo(new RectF(aRect.right , aRect.top, aRect.right - 2 * rTr, aRect.top - 2 * rTr), 180, 90, false);
}
path.lineTo(aRect.left + 2 * Math.abs(rTl), aRect.top);
if (rTl > 0) {
path.arcTo(new RectF(aRect.left, aRect.top, aRect.left + 2 * rTl, aRect.top + 2 * rTl), 270, -90, false);
} else if (rTl < 0) {
path.arcTo(new RectF(aRect.left + 2 * rTl, aRect.top, aRect.left, aRect.top - 2 * rTl), 270, 90, false);
}
path.lineTo(aRect.left, aRect.bottom - 2 * Math.abs(rBl));
if (rBl > 0) {
path.arcTo(new RectF(aRect.left, aRect.bottom - 2 * rBl, aRect.left + 2 * rBl, aRect.bottom), 180, -90, false);
} else if (rBl < 0) {
path.arcTo(new RectF(aRect.left + 2 * rBl, aRect.bottom + 2 * rBl, aRect.left, aRect.bottom), 0, 90, false);
}
path.lineTo(aRect.right - 2 * Math.abs(rBr), aRect.bottom);
if (rBr > 0) {
path.arcTo(new RectF(aRect.right - 2 * rBr, aRect.bottom - 2 * rBr, aRect.right, aRect.bottom), 90, -90, false);
} else if (rBr < 0) {
path.arcTo(new RectF(aRect.right, aRect.bottom + 2 * rBr, aRect.right - 2 * rBr, aRect.bottom), 90, 90, false);
}
path.lineTo(aRect.right, aRect.top + Math.abs(rTr));
return path;
}
res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BgColourTextView">
<attr name="maxCornerRadius" format="dimension" />
</declare-styleable>
</resources>
res/layout/main.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<com.example.bgcolourtextview.BgColourTextView
android:text="Example\n10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:gravity="center_horizontal"
android:textSize="32sp"
app:maxCornerRadius="10dp" />
<com.example.bgcolourtextview.BgColourTextView
android:text="Example 15dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:gravity="center_horizontal"
android:textSize="32sp"
app:maxCornerRadius="15dp" />
<com.example.bgcolourtextview.BgColourTextView
android:text="Example\n20dp radius\ntext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:gravity="center_horizontal"
android:textSize="32sp"
app:maxCornerRadius="20dp" />
<com.example.bgcolourtextview.BgColourTextView
android:text="Example\ntext\n5dp radius\ntext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:gravity="center_horizontal"
android:textSize="32sp"
app:maxCornerRadius="5dp" />
</LinearLayout>
示例输出: