this is what i want to achieve
我想创建一个多行文本视图,其背景颜色遵循文本长度并具有圆角。这就像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);
}
这是我得到的结果:
有人能指出如何解决这个问题吗?唯一的问题是其他两个角落没有四舍五入。
答案 0 :(得分:0)
使用简单的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 /值/ 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>
示例输出: