[英]How to draw background color that wraps text length in textview?
我想创建一个多行文本视图,其背景颜色跟随文本长度并具有圆角。 就像instagram一样。
这是我到目前为止一直在尝试的
public class BgColorTextView extends TextView {
...
@Override
public void draw(Canvas canvas) {
int lineCount = getLayout().getLineCount();
RectF rect = new RectF();
Paint paint = new Paint();
paint.setColor(bgColor);
for (int i = 0; i < lineCount; i++) {
rect.top = (int) (getLayout().getLineTop(i)+20);
rect.left = (int) (getLayout().getLineLeft(i)+10);
rect.right = (int) (getLayout().getLineRight(i)+40);
rect.bottom = (int) (getLayout().getLineBottom(i) - ((i + 1 == lineCount) ? 0 : getLayout().getSpacingAdd())+40);
canvas.drawRoundRect(rect, 25, 25, paint);
}
super.draw(canvas);
}
这是我得到的结果:
有人可以指出我该如何解决吗? 唯一的问题是其他两个角落没有变圆。
使用简单的Canvas内置形状绘制方法实际上无法做到这一点,因为您需要绘制凹角弧。 但是,您可以使用Path原语操作来构建所需的形状。
可以想象您可以创建一条路径来环绕文本,但是我发现为每条线创建单独的路径并确定是否需要像普通的圆角矩形(即凸形,正半径)那样上角或将弯角(凹形上)变得更简单。 (负半径),具体取决于线边界与上下边界的对齐方式。
我在下面提供了一个示例,说明如何将其作为派生的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 /布局/ 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>
输出示例:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.